ドキュメントの検索
karat

+

K

APIリファレンス ↗

注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。

Postgresにおけるクエリの最適化

このページでは、Postgresでのクエリの最適なパフォーマンスを目指してクエリをチューニングすることに焦点を当てています。一般的には、オブジェクトセットActionsのような機能を使用して、データの読み取りと書き戻しを行うオントロジーの機能を基にSlateアプリケーションを構築することをお勧めします。

レスポンシブなアプリケーションを設計するには、慎重な計画と考慮が必要です。データモデル、クエリ構造、依存関係グラフは、アプリケーションのパフォーマンスと使いやすさに重要な役割を果たします。このガイドでは、Slateで記述できるPostgreSQLクエリのパフォーマンスチューニングの側面に焦点を当てており、パフォーマンスのヒューリスティック、チューニングの機会を特定する方法、パフォーマンスを改善するための提案を含んでいます。

クエリのパフォーマンスチューニングは反復的なプロセスです。"正しい"答えを生成するために同じ要求を書くための多くの異なる方法があります。

パフォーマンスヒューリスティック

PostgreSQLクエリの目標実行時間は<= 500msです。対象となるデータセットが1000万レコード以下である場合、ほとんどのユースケースでこれが可能であるはずですが、データモデルを設計する際には慎重な計画と考慮が必要です。

クエリのパフォーマンスを評価する際、特に新しいデータセットを使用する場合は、統計を収集し分析する前にクエリを複数回実行することを確認してください。これにより、データセットが適切にキャッシュされ、パフォーマンスが期待される使用法により適合するようになります。PostgreSQLのキャッシュについて詳しく知りたい場合は、以下の外部リンクを参照してください:

EXPLAIN... 説明

EXPLAINは、クエリ実行プランを返すPostgresが提供する特に有用なコマンドです。クエリプランは、Postgresが受け取るすべてのリクエストに対して作成され、クエリの構造とデータのプロパティを使用して、リクエストを処理する最速の方法を決定します。ガイド全体で参照されるEXPLAINコマンドの簡単なレビューから始めます。

EXPLAIN

ノード

ノードをクエリの評価のステップ、または作業の論理単位と考えることができます。ノードは反転したグラフとして返され、これは応答の最初の行が最後に行われる作業の単位であることを意味します。各ノードは -> で先行されます。

-> event上のevent_type_idxを使用したIndex Scan (...)

なぜこれが重要なのか:ノードは、クエリ実行プランの非効率的な操作を特定し、パフォーマンスチューニングの努力を優先するために使用されます。

コスト

最初の数字はスタートアップコスト(最初のレコードを取得するまでの時間)です。二番目の数字は、スタートアップから完了までの全ノードの処理コストです。

(**コスト=86.83..4577.07** rows=2368 width=10)

コストは、Postgresクエリプランナーがオブジェクト(一般的にはテーブル)の統計に基づいて生成する推定値です。この数値は実際のランタイムを表していないものの、実際の実行と直接相関しているはずです。

コストは、以下の複数の作業要素の組み合わせです:順次フェッチ、非順次(ランダム)フェッチ、行の処理、オペレータ(関数)の処理、インデックスエントリの処理。コストはI/OとCPU活動を表しており、数字が大きいほど、Postgresがタスクを完了するために必要と思われる作業量が多いことを意味します。Postgresクエリオプティマイザーは、コストに基づいて実行プランを決定するという点を注意しておくことが重要です。

ROWS

このプランノードによって出力されると見積もられる行の数。

(cost=86.83..4577.07 **rows=2368** width=10)

なぜこれが重要なのか:ROWSは、大量のデータを返すノードを特定するために使用でき、または予想される通りに動作していないことを確認するために使用できます。

WIDTH

ノードによって出力される行の推定平均サイズ(バイト単位)。

(cost=86.83..4577.07 rows=2368 **width=10**)

なぜこれが重要なのか:WIDTHは、非常に大きなプロパティを持つ行を出力するノードを特定するため、または大量の列を出力するノードを特定するために使用できます。

EXPLAIN ANALYZE

ノード

上記参照

ACTUAL TIME

_Cost_と同様に、最初の数字はスタートアップに必要な実際の時間(ミリ秒)です。二番目の数字はスタートアップから完了までの全ノードの処理に必要な実際の時間です。

(**actual time=10.313..12.530** rows=4857 loops=1)

名前が示す通り、actual timeはステートメントの実行によって取得されます。キーワードANALYZEはPostgresに実行計画を表示するとともにクエリを実行するように指示します。クエリがタイムアウトする問題がある場合は、ANALYZEを削除するとクエリプランのみが返され、クエリの実行よりもはるかに高速になります。

なぜこれが重要なのか:これはパフォーマンス問題を引き起こしているノードまたは操作を最も明確に示す指標です。

ROWS

ノードによって出力される行の推定数。

(actual time=10.313..12.530 **rows=4857** loops=1)

なぜこれが重要なのか:ROWSは、特定の操作がなぜ予想よりも長い時間をかけているのかについての文脈を提供するのに役立ちます。

LOOPS

ノードの実行総数を報告します。表示される実際の時間と行の値は、実行あたりの平均です。ノードで過ごした合計時間を得るには、LOOPSの値を実際の時間で掛けます。

(actual time=10.313..12.530 rows=150 **loops=10**)

操作(プランノード)の理解

スキャン

  • sequential scans(seq scan):Seq Scan操作は、ディスク上に保存されている関係(テーブル)全体をスキャンします(TABLE ACCESS FULLのようなもの)。関係のスキーマ、サイズ、制約、インデックスの存在に関係なく、関係に対してseq scansを実行することは常に可能です。
    • seq scanの特徴は次のとおりです:
      • スタートアップが速い(順次I/Oはランダムアクセスよりもはるかに速い)。
      • 各ブロックは一度だけ読み取られます。
      • 生成される出力は順序付けられていません。
  • index scans:Index ScanはB-treeをトラバースし、すべての一致するエントリを見つけるために葉ノードを通過し、対応するテーブルデータをフェッチします。これはINDEX RANGE SCANに続いてTABLE ACCESS BY INDEX ROWID操作を行うものと同じです。
    • Index Scanの特徴は次のとおりです:
      • ランダムアクセスは順次I/Oよりもはるかに遅い。
      • インデックスにアクセスするために追加のI/Oが必要です。
      • 同じブロックを複数回読む可能性があります。
      • 順序付けされた出力を生成します。
  • bitmap index/heap scans:単純なIndex Scanは、インデックスから一度に1つのタプルポインタを取得し、すぐにそのタプルをテーブルで訪問します。bitmap scanは、インデックスからすべてのタプルポインタを一度に取得し、それらをメモリ内の"bitmap"データ構造を使用してソートし、次に物理的なタプルロケーションの順序でテーブルタプルを訪問します。
    • bitmap index/heap scanの特徴は次のとおりです:
      • インデックス選択性での順次I/O。
      • すべてのインデックスタプルが読み取られてソートされるため、スタートアップが遅い。
      • INおよび=ANY(配列)演算子、および低選択性インデックススキャンによく選ばれます。
      • 複数のインデックスを組み合わせることができます。
      • 生成される出力は順序付けされていません。
  • index only:Index Only ScanはB-treeをトラバースし、すべての一致するエントリを見つけるために葉ノードを通過します。インデックスがクエリを満足させるすべての列を持っているため、テーブルへのアクセスは必要ありません(MVCC可視性情報は例外です)。

結合

結合操作は通常、一度に2つのテーブルのみを処理します。クエリが2つ以上のテーブルを結合する場合、結合は順次に実行されます:最初に2つのテーブル、次に中間結果と次のテーブル、というように。結合の文脈では、「テーブル」という用語はしたがって「中間結果」を意味することもあります。

  • nested loop:一方のテーブルから結果を取得し、そのテーブルの各行に対して他方のテーブルをクエリすることで2つのテーブルを結合します。
    • 一般的に最もパフォーマンスが低い形式の結合です。
    • 最初のレコードを生成するのが速い。
    • 第二の子が遅い場合、パフォーマンスがマイナスになる可能性があります。
    • CROSS JOINを実行できる唯一の結合です。
    • 不等結合条件を実行できる唯一の結合です。
  • merge join:(sort)merge joinは、ジッパーのように2つのソートされたリストを結合します。結合の両側が事前にソートされている必要があります。
    • 等価結合条件のみに使用できます。
    • 一般的に大きなデータセットに対して最もパフォーマンスが高いです。
    • 順序付けられた入力が必要で、これには遅いソートやインデックススキャンが必要です。
    • すべてのインデックスタプルが読み取られてソートされるため、スタートアップが遅いです。
  • hash joins:hash joinは、結合の一方の候補レコードをハッシュテーブル(計画内でHashとマークされている)にロードし、結合の他方の各レコードに対してそれを調査します。
    • 等価結合条件のみに使用できます。
    • 一般的に大きなテーブルを小さなテーブルに結合する際に最もパフォーマンスが高いです。
    • ハッシュ可能なデータ型専用です。
    • 小さなテーブルをハッシュ化するためのスタートアップが遅い。
    • テーブルの統計が古くて不正確な場合、パフォーマンスがマイナスに影響を受けます。

集約

  • GroupAggregate:group by句に従って事前にソートされたセットを集約します。この操作は大量のデータをバッファリングしません。
  • HashAggregate:一時的なハッシュテーブルを使用してレコードをグループ化します。HashAggregate操作は、事前にソートされたデータセットを必要とせず、代わりに大量のメモリを使用して中間結果を具体化します(パイプライン化されません)。出力は何らかの意味で順序付けされません。
  • Unique
  • WindowAgg:ウィンドウ関数の使用を示します。

その他

  • Sort
    • ORDER BY、DISTINCT、GROUP BY、UNION、およびmerge joinsで発生します。
    • かなりのスタートアップ時間。
    • ソートがwork_memに収まる場合、クイックソートを使用できます。
    • ソートがメモリに収まらない場合、ディスクにスピルして一時ファイルを使用します。これは非常に高価なことがあります。
  • Limit
    • LIMITとOFFSETの両方を処理します。
    • WHERE句がない場合のmin()とmax()に使用できます。
    • OFFSETのためにスキップされたレコードはまだ生成/具体化されますが、結果が返される前に破棄されます。
    • 子スキャンのコストはまだ全コストです。
    • ソートとlimitを組み合わせると、ソートの最適化形式を使用できます。

クエリチューニングのベストプラクティス

SELECT

  • リクエストをサポートするためにフェッチするページを制限するのに役立つ、表示に必要な属性のみを含めます。

DISTINCT

  • DISTINCTはPostgreSQLのコードベースの古い部分から来ており、一意のレコードを識別するための効率的でない方法を使用します。
  • 可能であれば、GROUP BYまたはサブクエリを使用してDISTINCTの使用を避けてください。

JOIN

  • テーブルを結合するときは、ON句で単純な等価文(a.id = b.person_idなど)を使用してみてください。これにより、より効率的な結合手法が使用できるようになります(Nested Loop JoinではなくHash Joinなど)。
  • 可能であれば、サブクエリをJOIN文に変換します。これにより、オプティマイザが意図を理解し、より良いプランを選択する可能性が高くなります。
  • JOINを適切に使用してください:結果が重複するためにGROUP BYやDISTINCTを使用していますか?これは通常、不適切なJOINの使用を示しており、コストが高くなる可能性があります。
  • 実行プランがHash Joinを使用している場合、テーブルサイズの推定が間違っていると非常に遅くなる可能性があります。したがって、あなたの掃除戦略を見直してテーブルの統計が正確であることを確認してください。
  • 可能であれば相関サブクエリを避けてください。これによりクエリのコストが大幅に増加する可能性があります。
  • 行の存在を基準にチェックするときはEXISTSを使用してください。これは「ショートサーキット」(少なくとも1つの一致を見つけたときに処理を停止)します。

WHERE句

  • 可能ならLIKEを避けてください。
  • 大きなリストをIN()文に渡すのを避けてください。代わりにJOIN条件を使用するか、除外ベースのロジックにロジックを調整することを検討してください。
  • WHERE句での関数呼び出しを避けてください。

GROUP BY & GROUPING SET

Deeper into Postgres 9.5 - New Group By Options for Aggregationを参照してください。

UNION vs UNION ALL

UNIONは重複レコードを除去します。これには問題のテーブルをソートする必要があります。

リクエストに関連するコストを考慮に入れ、UNIONの使用を強くお勧めします。これが必要なインスタンスは非常に少なく、そうであれば、Slateからクエリする前にTransformsレイヤーで行うべきです。

UNION ALLは重複を除去せず、効率的に一方のテーブルの行を他方に追加します。

Common Mistakes: UNION VS. UNION ALLを参照してください。

インデックス

以下の条件でインデックスの追加を検討してください:

  • 逐次スキャン(seq scan)を排除します。ただし、それが小さなテーブルである場合や、クエリが行の5%以上をフェッチする場合は除きます。
  • 複数列のインデックスを使用する場合は、含める列を定義する順序に注意してください。
  • 頻繁に使用される列に、高度に選択性のあるインデックスを使用します。

不足しているインデックスの特定

Copied!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 SELECT 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_tables -- すべてのテーブルの統計情報を取得 WHERE schemaname = 'public' -- スキーマ名が'public'のテーブルのみを対象 AND pg_relation_size(relname::regclass) > 80000 -- テーブルのサイズが80000バイトより大きいもののみを対象 ORDER BY too_much_seq DESC; -- シーケンシャルスキャンとインデックススキャンの差(too_much_seq)が大きい順に並べ替える

未使用のインデックスを特定する

Copied!
1 2 3 4 5 6 7 SELECT indexrelid::regclass as index, -- indexrelidをregclassとしてindexという名前で取得 relid::regclass as table, -- relidをregclassとしてtableという名前で取得 'DROP INDEX ' || indexrelid::regclass || ';' as drop_statement -- 'DROP INDEX '文字列とindexrelid::regclassを結合してdrop_statementという名前で取得 FROM pg_stat_user_indexes -- pg_stat_user_indexesテーブルからデータを取得 JOIN pg_index USING (indexrelid) -- pg_indexテーブルとindexrelidを用いて結合 WHERE idx_scan = 0AND indisunique is false; -- idx_scanが0でかつindisuniqueがfalseのデータを選択

インデックスのみのスキャン(カバリングインデックス)

index-only scansの適切な使用方法と利点について詳しく読む。