web-dev-qa-db-ja.com

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

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

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

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

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

BRINインデックス

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

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

続きを読む、もっとあります。
Depeszが予備テストを実行しました。

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

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

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

テーブルレイアウト

他に何をしても、次のように列をオーディングすることにより、行ごとのアラインメント要件のためにパディングで失われた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バイトを占めます。 1つだけintegerカラムが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バイト。各インデックスの作成も安価ですが、テーブルが大きすぎてキャッシュに保持できない場合(@Jürgenや@Chrisがコメントしたように)、1つではなく5つを作成すると、かなり時間がかかります。ですから、それはあなたにとって有用かもしれないし、そうでないかもしれません。

パーティショニング

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

マニュアル:

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

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


maintenance_work_mem

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

maintenance_work_mem(整数)

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

autovacuumを実行すると、このメモリが最大 autovacuum_max_workers 倍に割り当てられる可能性があるため、デフォルト値を高く設定しすぎないように注意してください。個別に setting autovacuum_work_mem でこれを制御すると便利な場合があります。

必要なだけ高く設定します。これは、(私たちにとって)不明なインデックスサイズに依存します。そしてローカルでのみ実行中のセッション。引用が説明しているように、高すぎるgeneral設定は、自動バキュームがより多くのRAMを要求する可能性があるため、そうでなければサーバーを枯渇させる可能性があります。また、実行中のセッションであっても、必要以上に大きく設定しないでください。データのキャッシュには、free 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の効果は、コミットされているかどうかに関係なく、現在のトランザクションが終了するまでのみ持続します。

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

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

8

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

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

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

3
Jürgen Strobel

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

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

0
Kirk Roybal