報告されたインデックスサイズと実行プランのバッファ数の大きな不一致


10

問題

次のようなクエリがあります

SELECT COUNT(1) 
  FROM article
  JOIN reservation ON a_id = r_article_id 
 WHERE r_last_modified < now() - '8 weeks'::interval 
   AND r_group_id = 1 
   AND r_status = 'OPEN';

(10分後に)タイムアウトになることが多いため、この問題を調査することにしました。

EXPLAIN (ANALYZE, BUFFERS)出力は次のようになります。

 Aggregate  (cost=264775.48..264775.49 rows=1 width=0) (actual time=238960.290..238960.291 rows=1 loops=1)
   Buffers: shared hit=200483 read=64361 dirtied=666 written=8, temp read=3631 written=3617
   I/O Timings: read=169806.955 write=0.154
   ->  Hash Join  (cost=52413.67..264647.65 rows=51130 width=0) (actual time=1845.483..238957.588 rows=21644 loops=1)
         Hash Cond: (reservation.r_article_id = article.a_id)
         Buffers: shared hit=200483 read=64361 dirtied=666 written=8, temp read=3631 written=3617
         I/O Timings: read=169806.955 write=0.154
         ->  Index Scan using reservation_r_article_id_idx1 on reservation  (cost=0.42..205458.72 rows=51130 width=4) (actual time=34.035..237000.197 rows=21644 loops=1)
               Filter: ((r_group_id = 1) AND (r_status = 'OPEN') AND (r_last_modified < (now() - '56 days'::interval)))
               Rows Removed by Filter: 151549
               Buffers: shared hit=200193 read=48853 dirtied=450 written=8
               I/O Timings: read=168614.105 write=0.154
         ->  Hash  (cost=29662.22..29662.22 rows=1386722 width=4) (actual time=1749.392..1749.392 rows=1386814 loops=1)
               Buckets: 32768  Batches: 8  Memory Usage: 6109kB
               Buffers: shared hit=287 read=15508 dirtied=216, temp written=3551
               I/O Timings: read=1192.850
               ->  Seq Scan on article  (cost=0.00..29662.22 rows=1386722 width=4) (actual time=23.822..1439.310 rows=1386814 loops=1)
                     Buffers: shared hit=287 read=15508 dirtied=216
                     I/O Timings: read=1192.850
 Total runtime: 238961.812 ms

ボトルネックノードは明らかにインデックススキャンです。それでは、インデックスの定義を見てみましょう。

CREATE INDEX reservation_r_article_id_idx1 
    ON reservation USING btree (r_article_id)
 WHERE (r_status <> ALL (ARRAY['FULFILLED', 'CLOSED', 'CANCELED']));

サイズと行番号

そのサイズ(\di+物理ファイルからのアクセスまたは物理ファイルへのアクセスによるレポート)は36 MBです。予約は通常、上記にリストされていないすべてのステータスで比較的短い時間しか費やしていないため、大量の更新が発生しているため、インデックスはかなり肥大化しています(ここでは約24 MBが無駄になっています)。それでも、サイズは比較的小さいです。

reservationテーブルには、約40万行を含む、サイズは約3.8 GBです。まだクローズされていない予約の数は約170,000です(正確な数は上記のインデックススキャンノードで報告されます)。

ここで驚きです:インデックススキャンは大量のバッファー(つまり、8 kbページ)のフェッチを報告します。

Buffers: shared hit=200193 read=48853 dirtied=450 written=8

キャッシュとディスク(またはOSキャッシュ)から読み取った数値の合計は1.9 GBです!

最悪のシナリオ

一方、最悪のシナリオでは、すべてのタプルがテーブルの異なるページにある場合、訪問(21644 + 151549)+ 4608ページ(テーブルからフェッチされた行の合計と物理ページからのインデックスページ番号)が考慮されます。サイズ)。これはまだ180,000未満です-観測されているほぼ250,000をはるかに下回っています。

興味深い(そしておそらく重要な)のは、ディスクの読み取り速度が約2.2 MB / sであることです。これはごく普通のことです。

だから何?

この矛盾がどこから来るのかについて誰かが考えを持っていますか?

注:明確にするために、ここでは何を改善または変更するかについてのアイデアがありますが、実際に得た数値を理解したいと思います。これが問題です。

更新:キャッシュまたはマイクロバキュームの効果の確認

jjanesの回答に基づいて、まったく同じクエリをすぐに再実行するとどうなるかを確認しました。影響を受けるバッファの数は実際には変化しません。(これを行うために、クエリを単純化して問題を最小限に抑えています。)これは、最初の実行から見たものです。

 Aggregate  (cost=240541.52..240541.53 rows=1 width=0) (actual time=97703.589..97703.590 rows=1 loops=1)
   Buffers: shared hit=413981 read=46977 dirtied=56
   I/O Timings: read=96807.444
   ->  Index Scan using reservation_r_article_id_idx1 on reservation  (cost=0.42..240380.54 rows=64392 width=0) (actual time=13.757..97698.461 rows=19236 loops=1)
         Filter: ((r_group_id = 1) AND (r_status = 'OPEN') AND (r_last_modified < (now() - '56 days'::interval)))
         Rows Removed by Filter: 232481
         Buffers: shared hit=413981 read=46977 dirtied=56
         I/O Timings: read=96807.444
 Total runtime: 97703.694 ms

そして2番目の後に:

 Aggregate  (cost=240543.26..240543.27 rows=1 width=0) (actual time=388.123..388.124 rows=1 loops=1)
   Buffers: shared hit=460990
   ->  Index Scan using reservation_r_article_id_idx1 on reservation  (cost=0.42..240382.28 rows=64392 width=0) (actual time=0.032..385.900 rows=19236 loops=1)
         Filter: ((r_group_id = 1) AND (r_status = 'OPEN') AND (r_last_modified < (now() - '56 days'::interval)))
         Rows Removed by Filter: 232584
         Buffers: shared hit=460990
 Total runtime: 388.187 ms

1
おそらく無関係ですが、参加する必要がありますarticleか?関係するすべての列がreservationテーブルからのものであり、FKがあると仮定すると、結果は同じになるはずです。
ypercubeᵀᴹ

それは非常に良い質問です。正解です。これは必要ありません。これは、他のチームによる監視で使用されるクエリです。それでも、少なくともクエリプランを見ると、他のすべてはその厄介なインデックススキャンの装飾にすぎません:)
dezso

1
結合を削除しても大きな違いはないことを付け加えておきます。誇張されたインデックススキャンはそのまま残ります。
dezso、2015年

トーストテーブルへのアクセス?あなたが示すコラムはトーストされるとは思いませんが。テスト目的でデータベースのアイドルクローンがある場合は、そのデータベースで実行pg_stat_reset()してからクエリを実行しpg_statio_user_tables、ブロックの属性を確認することができます。
jjanes

回答:


4

ここで重要なのは、多くの更新とインデックスの膨らみです。

インデックスには、「ライブ」ではなくなったテーブル内の行へのポインタが含まれています。これらは、更新された行の古いバージョンです。古い行のバージョンは、古いスナップショットでクエリを満たすためにしばらく保持され、その後、必要以上に頻繁にそれらを削除する作業を行う必要がないため、しばらく保持されます。

インデックスをスキャンするときは、これらの行にアクセスする必要があり、それらが表示されなくなったことに気づくため、無視します。explain (analyze,buffers)声明は、バッファのカウントを読み/ヒットをこれらの行を検査する過程で除き、明示的にこの活動を報告しません。

btreeにはいくつかの「マイクロバキューム」コードがあり、スキャンが再びインデックスに戻ると、追跡したポインタがもう存在していないことを記憶し、インデックスでデッドとしてマークします。そうすれば、次に実行される同様のクエリで、再度追跡する必要がなくなります。そのため、まったく同じクエリをもう一度実行すると、バッファアクセスが予測したものに近づくことがわかります。

VACUUMテーブルをより頻繁に使用することもできます。これにより、部分インデックスだけでなく、テーブル自体から不要なタプルが削除されます。一般に、回転率の高い部分インデックスを持つテーブルは、デフォルトレベルよりも積極的なバキュームの恩恵を受ける可能性があります。


私の編集を参照してください-私にとって、それはマイクロバキュームではなくキャッシュのように見えます。
dezso

新しい数値は古い数値とは大きく異なります(ほぼ2倍)。そのため、実際の行とインデックススキャン用にフィルター処理された行の新しい数値を確認せずに、それらの意味を解釈することは困難です。
jjanes

彼らが今日見るように完全な計画を追加しました。影響を受けたバッファ数は、行数と同様に、金曜日以降大幅に増加しました。
dezso 2015年

存続期間の長いトランザクションがありますか?そうである場合、インデックススキャンがまだ表示されていない行を追跡している可能性があります(これにより、余分なバッファーヒットが発生します)。スナップショット。
jjanes 2015年

私は何も持っていません-通常のトランザクションは1秒もかかりません。たまに数秒ですが、長くはありません。
dezso 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.