web-dev-qa-db-ja.com

Postgresが順次スキャンを行わないようにするためのパーティションテーブルのインデックス作成

〜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);でインデックスを宣言すると、すべてのパーティションに伝達されませんか?

2
JonLuca

パーティションキーを明示的に追加してみてください(冗長です!)。お気に入り:

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を実行する必要がある場合があります。

2