〜2,000,000,000のエントリを含むパーティションテーブルを持つPostgresデータベースがあります。
「識別子」の最初の文字に基づいてパーティション化されたデータベースを定義しました-これは37のサブテーブルに分割されます[0-9、a-z、デフォルト(その他すべてのキャッチオール)]。
データベースは非常にシンプルでわかりやすく、以下で定義されています。
_create table entries (
id bigserial,
identifier text null,
password text null,
additional_fields jsonb
)
partition by list (lower(left(identifier, 1)));
ALTER DATABASE credentials SET constraint_exclusion=on;
CREATE TABLE entries_0 PARTITION OF entries for values in ('0');
CREATE TABLE entries_1 PARTITION OF entries for values in ('1');
CREATE TABLE entries_2 PARTITION OF entries for values in ('2');
CREATE TABLE entries_3 PARTITION OF entries for values in ('3');
...
CREATE TABLE entries_z PARTITION OF entries for values in ('z');
ALTER TABLE entries_0 ADD CONSTRAINT first_letter CHECK (lower(left(identifier, 1)) = '0');
ALTER TABLE entries_1 ADD CONSTRAINT first_letter CHECK (lower(left(identifier, 1)) = '1');
ALTER TABLE entries_2 ADD CONSTRAINT first_letter CHECK (lower(left(identifier, 1)) = '2');
ALTER TABLE entries_3 ADD CONSTRAINT first_letter CHECK (lower(left(identifier, 1)) = '3');
...
ALTER TABLE entries_z ADD CONSTRAINT first_letter CHECK (lower(left(identifier, 1)) = 'z');
CREATE INDEX ident_idx on entries(identifier);
_
ただし、EXPLAIN
を実行すると、まだシーケンシャルスキャンを実行しているということです。
_EXPLAIN SELECT * FROM entries where identifier = 'some_identifier_from_subtable_s' LIMIT 1;
_
出力:
_Limit (cost=0.00..140.92 rows=1 width=104)
-> Append (cost=0.00..43418531.72 rows=308113 width=104)
-> Seq Scan on entries_0 (cost=0.00..23239.38 rows=2 width=68)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
-> Seq Scan on entries_1 (cost=0.00..150187.81 rows=6 width=68)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
-> Seq Scan on entries_2 (cost=0.00..94694.38 rows=4 width=67)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
-> Seq Scan on entries_3 (cost=0.00..81656.71 rows=3 width=67)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
... etc.
-> Seq Scan on entries_z (cost=0.00..579207.95 rows=13 width=69)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
-> Seq Scan on entries_default (cost=0.00..15582.36 rows=4 width=69)
Filter: (identifier = 'some_identifier_from_subtable_s'::text)
_
何を間違っているのですか?インテリジェントなパーティション分割では、クエリを_entries_s
_パーティションのみにリダイレクトできるはずですが、そうでない場合はどうでしょうか?そして、CREATE INDEX ident_idx on entries(identifier);
はクエリにインデックスを通過させる必要がありますか?
明示的なクエリを追加すると、新しいプランは次のようになります。
_FROM entries
WHERE identifier = 'some_identifier_from_subtable_s'
AND lower(left(identifier, 1)) = 's' -- 1st letter of above identifier
LIMIT 1;
_
出力:
_Limit (cost=1000.00..2583053.76 rows=1 width=71)
-> Gather (cost=1000.00..2583053.76 rows=1 width=71)
Workers Planned: 2
-> Parallel Append (cost=0.00..2582053.66 rows=1 width=71)
-> Parallel Seq Scan on entries_s (cost=0.00..2582053.66 rows=1 width=71)
Filter: ((identifier = 'some_identifier_from_subtable_s'::text) AND (lower("left"(identifier, 1)) = 's'::text))
_
_entries_s
_で引き続き順次スキャンを実行しています。 CREATE INDEX ident_idx on entries(identifier);
でインデックスを宣言すると、すべてのパーティションに伝達されませんか?
パーティションキーを明示的に追加してみてください(冗長です!)。お気に入り:
SELECT *
FROM entries
WHERE identifier = 'some_identifier_from_subtable_s'
AND lower(left(identifier, 1)) = 's' -- 1st letter of above identifier
LIMIT 1;
これにより、Postgresはクエリから他のすべてのパーティションをプルーニングできることを理解できるはずです。
もちろん$1
から派生できます:
AND lower(left(identifier, 1)) = lower(left($1, 1))
念頭に置いたようにインデックスを作成しない限り、1つのパーティションで順次スキャンが表示されます。
CREATE INDEX ident_idx on entries(identifier);
マニュアルの引用: のため、これはPostgres 11以降で機能します。
パーティションテーブルで
CREATE INDEX
を呼び出すと、デフォルトの動作では、すべてのパーティションに再帰して、すべてのパーティションが一致するインデックスを持つようにします。
Postgres 10以前では、パーティションごとにインデックスを作成する必要があります。
Autovacuumが起動する前にクエリをすぐにフォローアップする場合は、インデックスを作成した後、テーブルでANALYZE
を実行する必要がある場合があります。