注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。
このページでは、Postgres で最適なパフォーマンスを得るためのクエリのチューニングに焦点を当てます。通常は、オントロジーの機能を基盤として Slate アプリケーションを構築し、オブジェクトセットや Actions などの機能を使用してデータを読み書きすることを推奨します。
応答性の高いアプリケーションを設計するには、慎重な計画と検討が必要です。データモデル、クエリ構造、依存関係グラフは、それぞれアプリケーションのパフォーマンスと使いやすさにおいて重要な役割を果たします。このガイドでは、Slate で記述できる PostgreSQL クエリのパフォーマンスチューニングに焦点を当て、パフォーマンスに関する経験則、チューニングの余地を特定する方法、パフォーマンスを改善するための推奨事項を紹介します。
クエリのパフォーマンスチューニングは、反復的なプロセスです。「正しい」結果を得るために、同じ要求をさまざまな方法で記述できます。
PostgreSQL クエリの目標実行時間は500 ms以下です。対象のデータセットが1,000万件未満のレコードを含む場合、ほとんどのユースケースでこの目標を達成できるはずですが、データモデルの設計時には慎重な計画と検討が必要です。
クエリのパフォーマンスを評価するとき、特に新しいデータセットを使用する場合は、統計を収集して分析する前に、必ずクエリを複数回実行してください。これにより、データセットが適切にキャッシュされ、パフォーマンスが想定される使用状況により近いものになります。PostgreSQL のキャッシュについて詳しく知りたい場合は、次のリンクを参照してください。
EXPLAIN は、クエリ実行計画を返すために Postgres が提供する、特に便利なコマンドです。Postgres が受け取るすべてのリクエストに対してクエリ計画が作成されます。この計画では、クエリ構造とデータの特性を使用して、リクエストを処理する最も速い方法を決定します。このセクションでは、まず、このガイド全体で参照する EXPLAIN コマンドを説明します。
ノードは、処理の論理的な単位、またはクエリ評価のステップと考えることができます。ノードは逆順のグラフとして返されるため、レスポンスの最初の行は、最後に実行される処理単位を表します。各ノードの前には -> が付きます。
-> Index Scan using event_type_idx on event (...)
重要な理由:ノードは、クエリ実行計画内の非効率な処理を特定し、パフォーマンスチューニングの優先順位を付けるために使用します。
最初の数値は起動コスト(最初のレコードを取得するまでの時間)です。2番目の数値は、起動から完了まで、ノード全体を処理するコストです。
(**cost=86.83..4577.07** rows=2368 width=10)
コストは、オブジェクト(通常はテーブル)の統計に基づいて Postgres のクエリプランナーが生成する推定値です。この数値は実際の実行時間を表すものではありませんが、実際の実行とは直接的な相関があるはずです。
コストは、順次フェッチ、非順次(ランダム)フェッチ、行の処理、演算子(関数)の処理、インデックスエントリの処理という、いくつかの処理要素の組み合わせです。コストは I/O と CPU の活動量を表します。数値が大きいほど、タスクを完了するために必要だと Postgres が見積もる処理量が多くなります。Postgres のクエリオプティマイザーは、このコストに基づいて使用する実行計画を決定します。
この計画ノードが出力する行数の推定値です。
(cost=86.83..4577.07 **rows=2368** width=10)
重要な理由:ROWS を使用すると、大量のデータを返すノードや、期待どおりに動作していないノードを特定できます。
ノードが出力する行の平均サイズの推定値(バイト単位)です。
(cost=86.83..4577.07 rows=2368 **width=10**)
重要な理由:WIDTH を使用すると、非常に大きなプロパティを持つ行や、多数の列を含む行を出力するノードを特定できます。
_コスト_と同様に、最初の数値は起動に必要な実際の時間で、単位はミリ秒 (ms) です。2番目の数値は、起動から完了まで、ノード全体を処理する実際の時間です。
(**actual time=10.313..12.530** rows=4857 loops=1)
名前のとおり、実際の時間はステートメントを実行することで取得されます。キーワード ANALYZE は、実行計画を表示するとともにクエリを実行するよう Postgres に指示します。クエリのタイムアウトに問題がある場合は、ANALYZE を削除するとクエリ計画のみが返されるため、クエリを実行するよりも大幅に速くなるはずです。
重要な理由:どのノードや処理がパフォーマンスの問題を引き起こしているかを示す、最も明確な指標です。
ノードが出力する行数の推定値です。
(actual time=10.313..12.530 **rows=4857** loops=1)
重要な理由:ROWS は、特定の処理に期待される時間よりも長くかかっている_理由_を理解するための手がかりになります。
ノードの合計実行回数を示します。表示される実際の時間と行数の値は、1回の実行あたりの平均値です。LOOPS の値に実際の時間を掛けると、ノードで費やされた合計時間を算出できます。
(actual time=10.313..12.530 rows=150 **loops=10**)
sequential scans(シーケンシャルスキャン):Seq Scan 操作は、ディスクに保存されたリレーション(テーブル)全体をスキャンします(TABLE ACCESS FULL と同様です)。リレーションのスキーマ、サイズ、制約、インデックスの有無にかかわらず、リレーションに対してシーケンシャルスキャンを実行することは常に可能です。
index scans:Index Scan は B ツリーを走査し、リーフノードをたどって一致するすべてのエントリーを見つけ、対応するテーブルデータを取得します。INDEX RANGE SCAN に続いて TABLE ACCESS BY INDEX ROWID 操作を実行するのと同様です。
bitmap index/heap scans:通常の Index Scan は、インデックスからタプルポインターを1つずつ取得し、その都度テーブル内の該当タプルにアクセスします。ビットマップスキャンは、インデックスからすべてのタプルポインターを一度に取得し、メモリー内の「ビットマップ」データ構造を使って並べ替えてから、タプルの物理的な場所の順序でテーブルのタプルにアクセスします。
index only:Index Only Scan は B ツリーを走査し、リーフノードをたどって一致するすべてのエントリーを見つけます。インデックスにはクエリを満たすためのすべての列が含まれているため、テーブルへのアクセスは不要です(ただし、MVCC の可視性情報は例外です)。結合操作は通常、一度に2個のテーブルのみを処理します。クエリで3個以上のテーブルを結合する場合、結合は順次実行されます。まず2個のテーブルを結合し、次にその中間結果と次のテーブルを結合する、という流れです。そのため、結合のコンテキストでは、「テーブル」という用語が「中間結果」を意味することもあります。
nested loop:一方のテーブルから結果を取得し、そのテーブルの各行についてもう一方のテーブルにクエリを実行することで、2個のテーブルを結合します。
merge join:(ソート)マージ結合は、並べ替え済みの2つのリストをファスナーのように組み合わせます。結合の両側を事前に並べ替えておく必要があります。
hash joins:ハッシュ結合は、結合の片側の候補レコードをハッシュテーブル(プラン内で Hash と指定されます)に読み込み、もう一方の側の各レコードについてそのハッシュテーブルを検索します。
GroupAggregate:事前に並べ替えられたセットを GROUP BY 句に従って集計します。この操作では大量のデータをバッファーに保持しません。HashAggregate:一時的なハッシュテーブルを使ってレコードをグループ化します。HashAggregate 操作では、事前に並べ替えられたデータセットは不要です。その代わり、大量のメモリーを使用して中間結果を実体化します(パイプライン処理は行いません)。出力には意味のある順序付けは行われません。UniqueWindowAgg:ウィンドウ関数が使用されていることを示します。Sort
work_mem 内に収まる場合、クイックソートを使用できます。Limit
DISTINCT は PostgreSQL のコードベースの古い部分に由来し、一意のレコードを識別するために効率の低い方法を使用しています。DISTINCT の使用を避けます。a.id = b.person_id など)を使用するようにします。これにより、より効率的な結合方式(Nested Loop Join ではなく Hash Join など)を使用できます。PostgreSQL の GROUPING SETS ドキュメント ↗を参照してください。
UNION は重複レコードを除去します。このため、対象のテーブルを並べ替える必要があります。
リクエストに伴うコストを考慮し、UNION の使用を避けることを強く推奨します。UNION が必要になるケースはごく少ないはずですが、必要な場合は、Slate からクエリを実行する前に Transforms レイヤーで処理するべきです。
UNION ALL は重複を除去せず、一方のテーブルの行をもう一方のテーブルに効率的に追加します。
よくある間違い:UNION と UNION ALL の比較 ↗を参照してください。
次の条件では、インデックスの追加を検討します。
Copied!1 2 3 4 5 6 7 8 9 10SELECT relname, seq_scan - idx_scan AS too_much_seq, CASE WHEN seq_scan - coalesce(idx_scan, 0) > 0 THEN 'missing index?' ELSE 'OK' END, pg_relation_size(relname::regclass) AS rel_size, seq_scan, idx_scan FROM pg_stat_all_tablesWHERE schemaname = 'public'AND pg_relation_size(relname::regclass) > 80000 ORDER BY too_much_seq DESC;
Copied!1 2 3 4 5 6 7SELECT indexrelid::regclass as index, relid::regclass as table, 'DROP INDEX ' || indexrelid::regclass || ';' as drop_statement FROM pg_stat_user_indexes JOIN pg_index USING (indexrelid) WHERE idx_scan = 0AND indisunique is false;
インデックスオンリースキャン ↗の適切な使用方法と利点について、詳しくご覧ください。