Postgres 10 查詢等待 IO:BufFileWrite 導致無法獲取新的數據庫連接
我有一個查詢(附有查詢計劃),每 5-7 秒從執行我們儀表板的每個設備(通常在高峰時間有 500 多個設備)執行一次。該查詢一開始看起來像是在等待狀態中花費時間
IO:BufFileWrite
。從 AWS Aurora Performance Insights 儀表板中,可以看到有問題的查詢在
IO: BUfFileWrite
等待狀態下花費了更多時間(圖中的天藍色)Postgres 配置/詳細資訊:
- AWS 極光 PostgreSQL 10.6
- R5.4X 大型實例(128 GB RAM)
work_mem = 80MB
- 我使用 hikari 連接池來管理數據庫連接。
一周前,我開始在我的應用伺服器上看到許多錯誤:
Failed to get database connection errors
和/或
java.sql.SQLTimeoutException: Timeout after 30000ms of waiting for a connection
在 PGAdmin 的幫助下進行了一些調試,發現大多數連接都在等待
IO: BufFileWrite
,因此意識到預設的4MBwork_mem
不足以進行查詢。在 PgBadger 的幫助下,看到平均臨時文件大小為 70MB,因此將其更新
work_mem
為 80 MB。這絕對有幫助,我開始看到較少的數據庫連接問題,但它並沒有完全消失。
我使用Postgres Visualize Analyzer和Explain Depesz來了解查詢計劃,發現僅索引掃描的**總成本為 4984972.45,計劃行數為 111195272。**此表 (
students_points
) 實際上有 100M+ 行,大小約為 15GB,並且未分區。我嘗試添加部分索引 (
create index students_latest_point_idx ON students_points (student_id, evaluation_date desc)
),希望上述掃描的成本會提高,但徒勞無功。我已經在所涉及的表上執行,但沒有明顯的性能改進
VACUUM ( FULL, ANALYZE, VERBOSE);
。REINDEX
我需要以下幫助
never executed
查詢計劃的部分是什麼意思?- 我檢查了網上的文獻,但除了 Postgres 引擎認為它不相關/返回 0 行之外,沒有令人滿意的解釋。- 我是否應該從查詢計劃中查看/擔心總成本 4984972.45 和計劃行 111195272.,即使它說從未執行?
- 什麼會導致在等待狀態 BufFileWrite 中花費過多的時間?據我了解,當應用排序/過濾器時,會使用臨時文件,這顯示為 BufFileWrite 等待狀態。監控_Postgres
- 您建議我從哪裡開始,以減少查詢在 BufFileWrite 的 IO 等待狀態下花費的時間?- 我嘗試過,真空,重新索引,添加新的部分索引 - 但沒有幫助。
- 我想到的一件事是,不要使用
students_points
表格(它有 1 行,對於每個學生,對於每次測試,他每週都參加,超過 4 年)所以它建立得很快,而是創建一個只容納每個學生的最新點(因此只有與學生一樣多的行)並在查詢中使用它。任何幫助表示讚賞。提前致謝。如果您需要更多資訊,我很樂意提供。
查詢和計劃
EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, TIMING, SUMMARY) SELECT SUM(CASE WHEN just_pass = 1 THEN 1 ELSE 0 END) point, SUM(CASE WHEN week_of_birthday = TRUE THEN 1 ELSE 0 END) birthday, SUM(CASE WHEN fresh_grad = 1 THEN 1 ELSE 0 end) fresher, SUM(CASE WHEN intervention_type::INT = 2 OR intervention_type::INT = 3 THEN 1 ELSE 0 END) attention FROM ( SELECT checkins.student_id, intercepts.intervention_type ,max(evaluation_date), just_pass, compute_week_of_birthday(student_birthdate, 4 , 'US/Central') as week_of_birthday, CASE WHEN student_enrolment_date NOTNULL AND student_enrolment_date >= '2017-01-29' AND student_enrolment_date < '2016-11-30' THEN 1 ELSE 0 END AS fresh_grad FROM ( SELECT student_id FROM checkin_table WHERE house_id = 9001 AND timestamp> '2019-06-11 01:00:40' AND timestamp<= '2019-06-11 01:00:50' GROUP BY student_id ) checkins LEFT JOIN students ON checkins.student_id = students.student_id LEFT JOIN students_points points ON checkins.student_id = points.student_id LEFT JOIN ( select record_id, student_id, intervention_type, intervention_date FROM intervention_table WHERE intervention_date IN ( SELECT MAX(intervention_date) FROM intervention_table GROUP BY student_id ) ) AS intercepts ON checkins.student_id = intercepts.student_id WHERE date_part('year',age(student_birthdate)) >=18 AND lower(registration_type_description) !~* '.*temporary.*' GROUP BY checkins.student_id, students.student_enrolment_date, student_birthdate, just_pass, intercepts.intervention_type ) AS result WHERE max IN ( SELECT evaluation_date FROM students_points ORDER BY evaluation_date DESC LIMIT 1 ) OR max ISNULL; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Aggregate (cost=74433.83..74433.84 rows=1 width=32) (actual time=0.081..0.081 rows=1 loops=1) Output: sum(CASE WHEN (result.just_pass = 1) THEN 1 ELSE 0 END), sum(CASE WHEN result.week_of_birthday THEN 1 ELSE 0 END), sum(CASE WHEN (result.fresh_grad = 1) THEN 1 ELSE 0 END), sum(CASE WHEN (((result.intervention_type)::integer = 2) OR ((result.intervention_type)::integer = 3)) THEN 1 ELSE 0 END) Buffers: shared hit=20 -> Subquery Scan on result (cost=74412.92..74432.98 rows=34 width=15) (actual time=0.079..0.079 rows=0 loops=1) Output: result.student_id, result.intervention_type, result.max, result.just_pass, result.week_of_birthday, result.fresh_grad, students.student_enrolment_date, students.student_birthdate Filter: ((hashed SubPlan 1) OR (result.max IS NULL)) Buffers: shared hit=20 -> GroupAggregate (cost=74412.31..74431.52 rows=68 width=35) (actual time=0.079..0.079 rows=0 loops=1) Output: checkin_table.student_id, intervention_table.intervention_type, max(points.evaluation_date), points.just_pass, compute_week_of_birthday(students.student_birthdate, 4, 'US/Central'::text), CASE WHEN ((students.student_enrolment_date IS NOT NULL) AND (students.student_enrolment_date >= '2017-01-29'::date) AND (students.student_enrolment_date < '2016-11-30'::date)) THEN 1 ELSE 0 END, students.student_enrolment_date, students.student_birthdate Group Key: checkin_table.student_id, students.student_enrolment_date, students.student_birthdate, points.just_pass, intervention_table.intervention_type Buffers: shared hit=20 -> Sort (cost=74412.31..74412.48 rows=68 width=30) (actual time=0.078..0.078 rows=0 loops=1) Output: checkin_table.student_id, intervention_table.intervention_type, points.just_pass, students.student_enrolment_date, students.student_birthdate, points.evaluation_date Sort Key: checkin_table.student_id, students.student_enrolment_date, students.student_birthdate, points.just_pass, intervention_table.intervention_type Sort Method: quicksort Memory: 25kB Buffers: shared hit=20 -> Nested Loop Left Join (cost=70384.64..74410.24 rows=68 width=30) (actual time=0.035..0.035 rows=0 loops=1) Output: checkin_table.student_id, intervention_table.intervention_type, points.just_pass, students.student_enrolment_date, students.student_birthdate, points.evaluation_date Buffers: shared hit=6 -> Nested Loop Left Join (cost=70384.08..74151.91 rows=1 width=22) (actual time=0.035..0.035 rows=0 loops=1) Output: checkin_table.student_id, intervention_table.intervention_type, students.student_birthdate, students.student_enrolment_date Buffers: shared hit=6 -> Nested Loop (cost=8.90..25.46 rows=1 width=16) (actual time=0.034..0.034 rows=0 loops=1) Output: checkin_table.student_id, students.student_birthdate, students.student_enrolment_date Buffers: shared hit=6 -> Group (cost=8.46..8.47 rows=2 width=8) (actual time=0.034..0.034 rows=0 loops=1) Output: checkin_table.student_id Group Key: checkin_table.student_id Buffers: shared hit=6 -> Sort (cost=8.46..8.47 rows=2 width=8) (actual time=0.033..0.033 rows=0 loops=1) Output: checkin_table.student_id Sort Key: checkin_table.student_id Sort Method: quicksort Memory: 25kB Buffers: shared hit=6 -> Append (cost=0.00..8.45 rows=2 width=8) (actual time=0.027..0.027 rows=0 loops=1) Buffers: shared hit=6 -> Seq Scan on public.checkin_table (cost=0.00..0.00 rows=1 width=8) (actual time=0.002..0.002 rows=0 loops=1) Output: checkin_table.student_id Filter: ((checkin_table.checkin_time > '2019-06-11 01:00:40+00'::timestamp with time zone) AND (checkin_table.checkin_time <= '2019-06-11 01:00:50+00'::timestamp with time zone) AND (checkin_table.house_id = 9001)) -> Index Scan using checkins_y2019_m6_house_id_timestamp_idx on public.checkins_y2019_m6 (cost=0.43..8.45 rows=1 width=8) (actual time=0.024..0.024 rows=0 loops=1) Output: checkins_y2019_m6.student_id Index Cond: ((checkins_y2019_m6.house_id = 9001) AND (checkins_y2019_m6.checkin_time > '2019-06-11 01:00:40+00'::timestamp with time zone) AND (checkins_y2019_m6.checkin_time <= '2019-06-11 01:00:50+00'::timestamp with time zone)) Buffers: shared hit=6 -> Index Scan using students_student_id_idx on public.students (cost=0.43..8.47 rows=1 width=16) (never executed) Output: students.student_type, students.house_id, students.registration_id, students.registration_status, students.registration_type_status, students.total_non_core_subjects, students.registration_source, students.total_core_subjects, students.registration_type_description, students.non_access_flag, students.address_1, students.address_2, students.city, students.state, students.zipcode, students.registration_created_date, students.registration_activation_date, students.registration_cancellation_request_date, students.registration_termination_date, students.cancellation_reason, students.monthly_dues, students.student_id, students.student_type, students.student_status, students.student_first_name, students.student_last_name, students.email_address, students.student_enrolment_date, students.student_birthdate, students.student_gender, students.insert_time, students.update_time Index Cond: (students.student_id = checkin_table.student_id) Filter: ((lower((students.registration_type_description)::text) !~* '.*temporary.*'::text) AND (date_part('year'::text, age((CURRENT_DATE)::timestamp with time zone, (students.student_birthdate)::timestamp with time zone)) >= '18'::double precision)) -> Nested Loop (cost=70375.18..74126.43 rows=2 width=14) (never executed) Output: intervention_table.intervention_type, intervention_table.student_id Join Filter: (intervention_table.intervention_date = (max(intervention_table_1.intervention_date))) -> HashAggregate (cost=70374.75..70376.75 rows=200 width=8) (never executed) Output: (max(intervention_table_1.intervention_date)) Group Key: max(intervention_table_1.intervention_date) -> HashAggregate (cost=57759.88..63366.49 rows=560661 width=16) (never executed) Output: max(intervention_table_1.intervention_date), intervention_table_1.student_id Group Key: intervention_table_1.student_id -> Seq Scan on public.intervention_table intervention_table_1 (cost=0.00..46349.25 rows=2282125 width=16) (never executed) Output: intervention_table_1.record_id, intervention_table_1.student_id, intervention_table_1.intervention_type, intervention_table_1.intervention_date, intervention_table_1.house_id, intervention_table_1.teacher_id, intervention_table_1.expiration_date, intervention_table_1.point_at_intervention -> Index Scan using intervention_table_student_id_idx on public.intervention_table (cost=0.43..18.70 rows=4 width=22) (never executed) Output: intervention_table.record_id, intervention_table.student_id, intervention_table.intervention_type, intervention_table.intervention_date, intervention_table.house_id, intervention_table.teacher_id, intervention_table.expiration_date, intervention_table.point_at_intervention Index Cond: (checkin_table.student_id = intervention_table.student_id) -> Index Scan using students_latest_points_idx on public.students_points points (cost=0.57..257.65 rows=68 width=16) (never executed) Output: points.record_id, points.student_id, points.registration_id, points.house_id, points.evaluation_date, points.just_pass, points.five_star, points.star1, points.star2, points.star3, points.star4, points.updatedate Index Cond: (checkin_table.student_id = points.student_id) SubPlan 1 -> Limit (cost=0.57..0.61 rows=1 width=4) (never executed) Output: students_points.evaluation_date -> Index Only Scan Backward using students_points_evaluation_date_idx on public.students_points (cost=0.57..4984972.45 rows=111195272 width=4) (never executed) Output: students_points.evaluation_date Heap Fetches: 0 Planning time: 23.993 ms Execution time: 17.648 ms (72 rows)
PS:出於隱私考慮,替換了表的名稱和屬性。一開始,我似乎可以
students_points
按年份對錶格進行分區,但這不是團隊願意接受的選項,原因我無法指定,而且根據年份對錶格進行分區也沒有意義,因為大多數我們的連接已經打開student_id
,並且分區student_id
將導致 1M+ 分區。編輯以解決 Jjanes評論。
checkin_table
似乎是空的-checkin_table
是一個分區表。查詢實際上命中了分區 -checkins_y2019_m6
,該分區實際上有數據。- 是什麼讓你認為這個查詢是罪魁禍首?- 在高峰時間使用 PGBadger 時,看到 40 個 DB 連接中有 30 個處於等待狀態。查看這些連接的查詢,它與上面描述的查詢相同 - 但具有不同的
house_id
和組合checkin_time
。此外,根據 RDS 洞察(上圖 1),如果您查看螢幕截圖的底部,它在**按等待載入 (AAS)**下方有條形圖,您可以看到條形圖的 2/3 是淺藍色 (IOWait) 和 1/3 是綠色 (CPU) 和相應的查詢。查看附加的 pgbadger 視圖(編輯查詢詳細資訊)。此查詢是最耗時的查詢。pg_stat_statements
- 是的,我看過它這是關於 total_time desc 的最高查詢,它與 PG Badger 一致。- **
auto_explain
**看起來可行。只有一個問題——它會以任何方式妨礙性能嗎?- 關於 IO Churn 和最慢的查詢- 我同意,但我遇到了死胡同並且沒有想法了。就像你指出的那樣,我可能會誤解一些事情。我沒有查看寫入臨時文件的所有查詢,這可能會佔用緩衝區,導致此處出現 IOWait。
編輯添加解決方案:
後端團隊重新編寫了sql查詢,性能有所提升。這將查詢執行時間降低到毫秒。
“evaluation_date”的值只需要從 GroupAggregate 中過濾掉行。由於不返回任何行,因此無需執行找到“evaluation_date”值的子計劃。
您不應該擔心這筆費用。即使執行了查詢的那部分,由於限制,它仍然會很快。一個節點的報告成本估算是在它執行到完成的假設下進行的。按比例降低成本是其上方 LIMIT 節點的工作。
表 checkin_table 似乎是空的。這很奇怪,不是嗎?
導致過多 BufFileWrite 的原因不是此查詢,或者至少不是您顯示的查詢實例——也許在其他情況下,它會使用非空的 checkin_table 執行。是什麼讓你認為這個查詢是罪魁禍首?您提到了幾種不同的工具,但除了您誤解的 LIMITed 索引掃描的總成本估算之外,您沒有說明這些工具特別向您展示了什麼。
您可以使用pg_stat_statements來查看平均或總體上哪些查詢很慢,並且可以使用“log_min_duration_statement”或更好的auto_explain來查看哪些特定的執行單獨很慢。
您最慢的查詢不一定會導致所有這些 IO 流失,但導致如此多的 IO 流失的任何事情都需要至少有點慢。所以你最好的選擇是從最慢的開始。
我根據您的情況製作了表格和查詢。即使它們與您的不同,也將有助於解決問題。
create table students (student_id integer, student_name varchar(100)); create table student_point (student_id integer, exam_seq integer, points integer ); create index idx_student_point_02 on student_point(exam_seq,student_id); create table check_in_table ( check_dt timestamp, house_no integer, student_id integer ); create index idx_check_in_table_01 on check_in_table(house_no,check_dt); create table intervention_table (student_id integer, intervention_date timestamp, intervention_type varchar(1)); create index idx_test on intervention_table(student_id,intervention_date, intervention_type); -- insert sample data insert into students with ch as ( select chr(i+96) as c from generate_series(1,25) i ) select row_number() over() as student_id, c1||c2||c3 as student_name from (select c c1 from ch) a, (select c c2 from ch) b, (select c c3 from ch) c, (select i from generate_series(1,50) i) d; commit; insert into student_point select student_id, row_number() over(partition by student_id) as exam_seq, trunc(random() * 100+1) as points from students, ( select i from generate_series(1,5) i) j; commit; --For invention_table data, I lost sql scripts. sorry :( --10 second delay select pg_sleep(10); --the numbers below are just random : I just want to show numbers in no order. I executed these insert queries repeatedly. insert into check_in_table select now() as check_dt, trunc(random() * 10+1) as room_no, student_id from students where student_id between ( select trunc(random()*5733) as student_id ) and ( select trunc(random()*23467) as student_id ) limit 32231; commit; select pg_sleep(10); insert into check_in_table select now() as check_dt, trunc(random() * 10+1) as room_no, student_id from students where student_id between ( select trunc(random()*15801) as student_id ) and ( select trunc(random()*35154) as student_id ) limit 14332; commit; select pg_sleep(10); insert into check_in_table select now() as check_dt, trunc(random() * 10+1) as room_no, student_id from students where student_id between ( select trunc(random()*98752) as student_id ) and ( select trunc(random()*112506) as student_id ) limit 24233; commit; select pg_sleep(10); insert into check_in_table select now() as check_dt, trunc(random() * 10+1) as room_no, student_id from students where student_id between ( select trunc(random()*551500) as student_id ) and ( select trunc(random()*570000) as student_id ) limit 14391; commit; --query before tuning : really slow and causing a heavy cpu load & buffwrite for a long time SELECT SUM(points) points ,SUM(CASE WHEN intervention_type::INT = 2 OR intervention_type::INT = 3 THEN 1 ELSE 0 END) attention FROM ( SELECT checkins.student_id, intercepts.intervention_type ,max(exam_seq), sum(sp.points) as points FROM ( SELECT student_id FROM check_in_table WHERE house_no = 1 AND check_dt> '2020-07-08 12:44:47' AND check_dt<= '2020-07-08 12:44:57' GROUP BY student_id ) checkins LEFT JOIN students ON checkins.student_id = students.student_id LEFT JOIN student_point sp ON checkins.student_id = sp.student_id LEFT JOIN ( select record_id, student_id, intervention_type, intervention_date FROM intervention_table i1 WHERE (student_id, intervention_date) in ( SELECT student_id, MAX(intervention_date) FROM intervention_table GROUP BY student_id ) ) AS intercepts ON checkins.student_id = intercepts.student_id GROUP BY checkins.student_id, intercepts.intervention_type ) AS result WHERE max IN ( SELECT exam_seq FROM student_point ORDER BY exam_seq DESC LIMIT 1 ) OR max ISNULL; --query after tuning with checkins as ( SELECT student_id FROM check_in_table WHERE house_no = 1 AND check_dt> '2020-07-08 12:44:47' AND check_dt<= '2020-07-08 12:44:57' GROUP BY student_id ) SELECT SUM(points) points ,SUM(CASE WHEN intervention_type::INT = 2 OR intervention_type::INT = 3 THEN 1 ELSE 0 END) attention FROM ( SELECT /*+ Leading(checkins students) */ checkins.student_id, intercepts.intervention_type ,max(exam_seq), sum(sp.points) as points FROM checkins LEFT JOIN students ON checkins.student_id = students.student_id LEFT JOIN student_point sp ON checkins.student_id = sp.student_id LEFT JOIN ( select record_id, student_id, intervention_type, intervention_date FROM intervention_table i1 WHERE (student_id,intervention_date) in ( SELECT i2.student_id, MAX(intervention_date) FROM intervention_table i2 ,checkins c WHERE i2.student_id = c.student_id GROUP BY i2.student_id ) ) AS intercepts ON checkins.student_id = intercepts.student_id GROUP BY checkins.student_id, intercepts.intervention_type ) AS result WHERE max IN ( SELECT exam_seq FROM student_point ORDER BY exam_seq DESC LIMIT 1 ) OR max ISNULL;