Postgres 9.4で大きな(1.2TB)静的テーブルの部分インデックスを作成しようとしています。
私のデータは完全に静的なので、すべてのデータを挿入してから、すべてのインデックスを作成できます。
この1.2 TBのテーブルには、データをきれいに分割するrun_id
という名前の列があります。 run_id
sの範囲をカバーするインデックスを作成することにより、優れたパフォーマンスを得ました。次に例を示します。
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%を示しています)。
部分インデックスの作成を高速化するために何かできることはありますか?
システム仕様:
テーブル仕様:
テーブル定義:
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))
(列名をあまり読みすぎないようにしてください。多少わかりにくくしました。)
背景情報:
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
(整数)
VACUUM
、CREATE INDEX
、ALTER 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
の効果は、コミットされているかどうかに関係なく、現在のトランザクションが終了するまでのみ持続します。
オブジェクトのサイズを測定するには:
サーバーは通常、それ以外の場合は合理的に構成する必要があります。
たぶん、これは過度に設計されたものです。実際に単一の完全なインデックスを使用してみましたか?テーブル全体をカバーする部分的なインデックスは、インデックスルックアップに大きな利益をもたらしません(あるとしても)。テキストからは、すべてのrun_idにインデックスがあると推測しますか?部分インデックスを使用したインデックススキャンにはいくつかの利点があるかもしれませんが、それでも私は単純な1インデックスソリューションを最初にベンチマークします。
インデックスを作成するたびに、テーブル全体の完全なIOバインドスキャンが必要です。したがって、複数の部分インデックスを作成するには、単一のインデックスよりもはるかに多くのIOテーブルを読み取る必要があります、ただし、単一の大きなインデックスの場合、並べ替えはディスクに流出します。部分的なインデックスを要求する場合は、すべての(またはいくつかの)インデックスを同時に(メモリが許可して)同時に構築してみてください。
メモリ内の8バイトのbigintであるすべてのrun_idをソートするために必要なmaintenance_work_memの概算については、10.5 * 8 GB +オーバーヘッドが必要です。
デフォルト以外のテーブルスペースにインデックスを作成することもできます。これらのテーブルスペースは、冗長ではない(障害が発生した場合はインデックスを再作成する)か、より高速なアレイ上にあるディスクを指す可能性があります。
部分インデックスと同じ基準を使用してテーブルをパーティション化することも検討してください。これにより、実際にインデックスを作成しなくても、クエリの実行時にインデックスと同じ速度が得られます。