Postgres部分インデックスの作成を高速化


8

Postgres 9.4で大きな(1.2 TB)静的テーブルの部分インデックスを作成しようとしています。

私のデータは完全に静的なので、すべてのデータを挿入してから、すべてのインデックスを作成できます。

この1.2 TBのテーブルrun_idには、データをきれいに分割するという名前の列があります。さまざまなをカバーするインデックスを作成することにより、優れたパフォーマンスを得ていますrun_id。次に例を示します。

CREATE INDEX perception_run_frame_idx_run_266_thru_270
ON run.perception
(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

これらの部分インデックスにより、望ましいクエリ速度が得られます。残念ながら、各部分インデックスの作成には約70分かかります。

CPUが制限されているようです(topプロセスの100%を示しています)。
部分インデックスの作成を高速化するために何かできることはありますか?

システム仕様:

  • 18コアXeon
  • 192GB RAM
  • RAIDに12個のSSD
  • 自動バキュームがオフになっています
  • maintenance_work_mem:64GB(高すぎる?)

テーブル仕様:

  • サイズ:1.26 TB
  • 行数:10537億
  • 一般的なインデックスサイズ:3.2GB(〜.5GBの差異があります)

テーブル定義:

CREATE TABLE run.perception(
id bigint NOT NULL,
run_id bigint NOT NULL,
frame bigint NOT NULL,
by character varying(45) NOT NULL,
by_anyone bigint NOT NULL,
by_me bigint NOT NULL,
by_s_id integer,
owning_p_id bigint NOT NULL,
obj_type_set bigint,
seq integer,
subj_id bigint NOT NULL,
subj_state_frame bigint NOT NULL,
CONSTRAINT perception_pkey PRIMARY KEY (id))

(列名を読みすぎないようにしてください-多少難読化しました。)

背景情報:

  • このデータを使用する別のチームがオンサイトにいますが、実際には1人または2人のユーザーしかいません。(このデータはすべてシミュレーションによって生成されます。)ユーザーは、挿入が完了し、インデックスが完全に構築されて初めて、データの分析を開始します。私たちの主な関心事は、使用可能なデータを生成するために必要な時間を削減することです。そして現在、ボトルネックはインデックス作成時間です。
  • パーシャルを使用する場合、クエリ速度は完全に適切です。実際、各インデックスがカバーする実行数を増やしても、十分なクエリパフォーマンスを維持できると思います。
  • テーブルをパーティション分割する必要があると思います。私たちはそのルートをとる前に他のすべてのオプションを使い果たしようとしています。

この追加情報は有用です。関連する列のデータタイプ、一般的なクエリ、カーディナリティ(行数)、いくつ異なるのrun_idでしょうか。均等に分散?ディスク上の結果のインデックスのサイズ?データは静的です。しかし、あなたは唯一のユーザーですか?
Erwin Brandstetter、2015年

詳細を更新しました。
バージー

1
自動バキュームがオフになっています」-なぜですか?それは本当に悪い考えです。これにより、統計の収集が防止され、不正なクエリプランが生成されます
a_horse_with_no_name

@a_horse_with_no_nameすべてのデータが挿入された後、手動で分析を開始します
Burnsy

あなたの状況はまだはっきりしていません。クエリはどのように見えますか?あなたのテーブルがそうであるならば、あなたはcompletely staticどういう意味We have a separate team onsite that consumes this dataですか?範囲run_id >= 266 AND run_id <= 270またはテーブル全体にインデックスを付けますか?各インデックスの平均余命はどれくらいですか?それを使用するクエリの数は?にはrun_idいくつの異なる値がありますか?〜15ミオのように聞こえます。あたりの行数はrun_id、約800の異なる値になりrun_idますか?なぜされているobj_type_setby_s_idseqNOT NULL定義されていませんか?それぞれのおおよそのNULL値の割合はどれくらいですか?
Erwin Brandstetter 2015

回答:


8

BRINインデックス

Postgres 9.5以降で利用可能で、おそらくあなたが探しているものだけです。はるかに高速なインデックス作成、はるかに小さなインデックス。ただし、クエリは通常それほど速くありません。マニュアル:

BRINはBlock Range Indexの略です。BRINは、特定の列がテーブル内の物理的な場所と自然に相関している非常に大きなテーブルを処理するために設計されています。ブロックの範囲は、テーブル内の物理的に隣接しているページのグループです。各ブロック範囲について、いくつかの要約情報がインデックスによって保存されます。

続きを読む、もっとあります。
デペスは予備試験を行った。

最適なケース:でクラスター化された行を書き込むことができる場合run_id、インデックスは非常に小さくなり、作成がはるかに安価になります。

CREATE INDEX foo ON run.perception USING brin (run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

テーブル全体にインデックスを付けることもできます

テーブルレイアウト

他に何をするにせよ、次のように列をordingすることにより、行ごとのアライメント要件のためにパディングで失われた8バイトを節約できます。

CREATE TABLE run.perception(
  id               bigint NOT NULL PRIMARY KEY
, run_id           bigint NOT NULL
, frame            bigint NOT NULL
, by_anyone        bigint NOT NULL
, by_me            bigint NOT NULL
, owning_p_id      bigint NOT NULL
, subj_id          bigint NOT NULL
, subj_state_frame bigint NOT NULL
, obj_type_set     bigint
, by_s_id          integer
, seq              integer
, by               varchar(45) NOT NULL -- or just use type text
);

列にNULL値がない場合、テーブルを79 GB小さくします。詳細:

また、NULLにできる列は3つだけです。NULLビットマップは、9〜72列で8バイトを占めます。場合つのみ 整数列がNULLである、ストレージ・パラドックスのためのコーナーケースがある。無駄な4つのバイトが、列のNULLビットマップを必要としないことにより、保存された8バイト:代わりにダミー値を使用するより安価であろう。詳細はこちら:

部分インデックス

実際のクエリによっては、上記のものではなく、次の5つの部分インデックスを使用する方が効率的です。

CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 266;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 267;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 268;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 269;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 270;

それぞれに対して1つのトランザクションを実行します。

run_idこの方法でインデックス列を削除すると、インデックスエントリごとに8バイトが節約されます-行あたり40バイトではなく32バイト。各インデックスの作成も安価ですが、テーブルが大きすぎてキャッシュに保持できない場合、1つではなく5つを作成するとかなり時間がかかります(@Jürgenや@Chrisのコメント)。ですから、それはあなたにとって有用かもしれないし、そうでないかもしれません。

パーティショニング

継承に基づく -Postgres 9.5までの唯一のオプション。
(Postgres 11または、できれば12の新しい宣言型パーティション分割は、より賢くなっています。)

マニュアル:

制約の除外時に、親テーブルのすべての子に対するすべての制約が検査されるため、パーティションの数が多いと、クエリの計画時間が大幅に増える可能性があります。そのため、従来の継承ベースのパーティション分割は、おそらく最大100パーティションまで適切に機能します。何千ものパーティションを使用しないでください。

大胆な強調鉱山。したがって、の1000個の異なる値を推定するとrun_id、それぞれ約10個の値にまたがるパーティションが作成されます。


maintenance_work_mem

私はあなたがmaintenance_work_mem私の最初の読みですでに調整していることを逃しました。参考のために、回答に引用とアドバイスを残しておきます。ドキュメントごと:

maintenance_work_mem (整数)

メンテナンス操作が使用するメモリの最大量を指定する、などVACUUMCREATE INDEXおよびALTER TABLE ADD FOREIGN KEY。デフォルトは64メガバイト(64MB)です。データベースセッションで一度に実行できるのはこれらの操作の1つだけであり、インストールでは通常、それらの多くが同時に実行されないため、この値をより大きい値に設定しても安全ですwork_mem。設定を大きくすると、バキューム処理とデータベースダンプの復元のパフォーマンスが向上する可能性があります。

注ときautovacuumに動作し、最大autovacuum_max_workers倍このメモリはそれほど高すぎるデフォルト値を設定しないように注意し、割り当てることができます。これを個別に制御すると便利な場合があります setting autovacuum_work_mem

必要なだけ高く設定します。これは、(私たちにとって)不明なインデックスサイズに依存します。そして実行中のセッションのためにローカルでのみ。引用が説明しているように、高すぎる一般的な設定は、自動バキュームがより多くのRAMを要求する可能性があるため、そうでなければサーバーを枯渇させる可能性があります。また、実行中のセッションであっても、必要以上に大きく設定しないでください。空きRAMは、データのキャッシュに有効に利用される可能性があります。

次のようになります。

BEGIN;

SET LOCAL maintenance_work_mem = 10GB;  -- depends on resulting index size

CREATE INDEX perception_run_frame_idx_run_266_thru_270 ON run.perception(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

COMMIT;

についてSET LOCAL

SET LOCALコミットされているかどうかに関係なく、現在のトランザクションの最後までの最後の効果。

オブジェクトのサイズを測定するには:

サーバーは通常、それ以外の場合は合理的に構成する必要があります。


テーブルはRAMよりもはるかに大きいため、彼の作業はIOバウンドであるに違いない。テーブルをさらに頻繁に読み取ると、作成された各インデックスをメモリ内でソートするのに十分なメモリがあるかどうかに関係なく、問題が悪化します。
ユルゲン・ストローベル

私はこれについてユルゲンと一緒にいます。テーブルのサイズが原因で、作成されたインデックスごとにテーブルで基本的にフルシーケンシャルスキャンを実行する必要があると思います。さらに、個別の部分インデックスを作成することでパフォーマンスが大幅に向上するかどうかはわかりません(90%は増加が見られないと確信していますが、これではオフになる可能性があります)。インデックス作成のソリューションでは、全体の構築時間を短縮するために、「単一の部分インデックス」としてクエリする全範囲にわたって1つのインデックスを作成する必要があります。
クリス

@Chris:同意します。5つのインデックスの作成には、1つよりも時間がかかります(すべてのインデックスが小さい場合でも、各インデックスを作成する方が安く、クエリが高速になる場合があります)。もう少し考えてみると、これはPostgres 9.5のBRINインデックスの完璧なユースケースになるはずです。
Erwin Brandstetter 2015

3

たぶん、これは過度に設計されたものです。実際に単一の完全なインデックスを使用してみましたか?テーブル全体をカバーする部分的なインデックスは、インデックスルックアップに大きな利益をもたらしません(あるとしても)。また、テキストから、すべてのrun_idにインデックスがあると推測しますか?部分インデックスを使用したインデックススキャンにはいくつかの利点があるかもしれませんが、それでも私は単純な1インデックスソリューションを最初にベンチマークします。

インデックスを作成するたびに、テーブル全体の完全なIOバインドスキャンが必要です。したがって、複数の部分インデックスを作成するには、単一の大きなインデックスの場合、並べ替えがディスクに溢れますが、単一のインデックスよりもはるかに多くのIOでテーブルを読み取る必要があります。部分的なインデックスを要求する場合は、すべての(またはいくつかの)インデックスを同時に(メモリが許せば)同時に構築してみてください。

メモリ内のすべてのrun_id(8バイトbigints)をソートするために必要なmaintenance_work_memの概算については、10.5 * 8 GB +オーバーヘッドが必要です。


0

デフォルト以外のテーブルスペースにインデックスを作成することもできます。これらの表領域は、冗長ではない(障害が発生した場合はインデックスを再作成する)か、より高速なアレイ上にあるディスクを指す可能性があります。

部分インデックスと同じ基準を使用してテーブルをパーティション化することも検討してください。これにより、実際にインデックスを作成しなくても、クエリを実行するときにインデックスと同じ速度が得られます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.