web-dev-qa-db-ja.com

btreeインデックスを含むjsonb列の統計に一貫性がない

Jsonb列を含むクエリのパフォーマンスは、テスト中にVACUUM ANALYZEを実行するたびに大きく異なることに気付きました。テーブルを分析した後、完全に異なる実行プランがランダムに表示されます。

ここではPostgres 9.6を使用しています。私のテストのセットアップは次のとおりです。jsonb列の「params」に1つのキー「x」を挿入しています。値は1〜6で、1が最もまれで、6が最も一般的です。比較のために同じ値の分布を含む通常のint列 "single_param"もあります。

CREATE TABLE test_data (
    id      serial,
    single_param    int,
    params      jsonb
);

INSERT INTO test_data
SELECT 
    generate_series(1, 1000000) AS id, 
    floor(log(random() * 9999999 + 1)) AS single_param,
    json_build_object(
        'x', floor(log(random() * 9999999 + 1))
    ) AS params;

CREATE INDEX idx_test_btree ON test_data (cast(test_data.params->>'x' AS int));
CREATE INDEX idx_test_gin ON test_data USING GIN (params);
CREATE INDEX ON test_data(id)
CREATE INDEX ON test_data(single_param)

私がテストしているクエリは、結果をページ分割するための典型的なクエリです。IDで並べ替え、出力を最初の50行に制限しています。

SELECT * FROM test_data where (params->>'x')::int = 1 ORDER BY id DESC LIMIT 50;

VACUUM ANALYZEを実行した後、2つのExplain分析出力の1つをランダムに取得します。

Limit  (cost=0.42..836.59 rows=50 width=33) (actual time=39.679..410.292 rows=10 loops=1)
  ->  Index Scan Backward using test_data_id_idx on test_data  (cost=0.42..44317.43 rows=2650 width=33) (actual time=39.678..410.283 rows=10 loops=1)
        Filter: (((params ->> 'x'::text))::integer = 1)
        Rows Removed by Filter: 999990"
Planning time: 0.106 ms
Execution time: 410.314 ms

または

Limit  (cost=8.45..8.46 rows=1 width=33) (actual time=0.032..0.034 rows=10 loops=1)
  ->  Sort  (cost=8.45..8.46 rows=1 width=33) (actual time=0.032..0.032 rows=10 loops=1)
        Sort Key: id DESC
        Sort Method: quicksort  Memory: 25kB
        ->  Index Scan using idx_test_btree on test_data  (cost=0.42..8.44 rows=1 width=33) (actual time=0.007..0.016 rows=10 loops=1)
              Index Cond: (((params ->> 'x'::text))::integer = 1)
Planning time: 0.320 ms
Execution time: 0.052 ms

違いは、where句に一致する列数の見積もりが2つのプラン間で異なることです。最初のものは2650行、2番目のものは1行で、実数は10行です。

GINインデックスを使用する可能性のある次のバージョンのクエリは、json列のデフォルトの推定値1%を使用しているように見えますが、これも上記のような不適切なクエリプランになります。

SELECT * FROM test_data where params @> '{"x": 1}' ORDER BY id DESC LIMIT 50;

私の当初の想定では、Postgresはjsonb列の統計情報を持たず、常に@>演算子を使用したクエリの場合と同じように推定値を使用します。しかし、私が作成したbtreeインデックスを使用できるように記述されたクエリでは、異なる推定値を使用しています。十分な場合もあれば、悪い場合もあります。

これらの見積もりはどこから来たのですか? Postgresがインデックスを使って作成したある種の統計だと思います。列統計の場合、より正確な統計を収集するオプションがありますが、これらの統計にはそのようなものはありますか?または、私のケースでPostgresにより良いプランを選択させる他の方法はありますか?

6
Mad Scientist

現在(バージョン9.6)、Postgresにはjsonのようなドキュメントタイプのinternalsに関するany統計がありません。 jsonbxmlまたはhstore。 (それを変更するかどうか、およびどのように変更するかについての議論がありました。)代わりに、Postgresクエリプランナーは一定のデフォルト頻度推定値を使用します(観察したように)。

ただしidx_test_btreeのように機能インデックスの個別の統計があります。 マニュアルにはこのヒントがあります あなたのために:

ヒント:ANALYZEの頻度を列ごとに調整することはあまり生産的ではないかもしれませんが、ANALYZEによって収集される統計の詳細レベルを列ごとに調整することは価値があるかもしれません。 WHERE句で頻繁に使用され、非常に不規則なデータ分布がある列は、他の列よりも細かいデータヒストグラムが必要になる場合があります。 ALTER TABLE SET STATISTICSを参照するか、default_statistics_target構成パラメーターを使用してデータベース全体のデフォルトを変更してください。

また、デフォルトでは、関数の選択性について利用できる情報が限られています。ただし、関数呼び出しを使用する式インデックスを作成すると、関数に関する有用な統計が収集され、式インデックスを使用するクエリプランを大幅に改善できます。

収集される統計量は、一般的な設定 default_statistics_target に依存します。これは、列ごとの設定で上書きできます。列の設定は、依存するインデックスを自動的にカバーします。

100のデフォルト設定は控えめです。 100万行のテストの場合、データの分布が不均一な場合、大幅に増やすと役立つことがあります。これをもう一度確認すると、実際にはALTER INDEXインデックス列ごとの統計ターゲットを微調整できることがわかりました。 pgsql-docsの関連する説明を参照してください。

ALTER TABLE idx_test_btree ALTER int4 SET STATISTICS 2000;  -- max 10000, default 100

インデックス列のデフォルト名は正確に直感的ではありませんが、次のように検索できます。

SELECT attname FROM pg_attribute WHERE attrelid = 'idx_test_btree'::regclass

ケースのインデックス列名として、型名int4になるはずです。

STATISTICSの最適な設定は、いくつかの要因に依存します:データ分布、データタイプ、更新頻度、一般的なクエリの特性、...

内部的には、これはpg_attribute.attstattargetの値を設定し、これの正確な意味は( ドキュメントごと )です。

スカラーデータ型の場合、attstattargetは、収集する「最も一般的な値」のターゲット数であり、作成するヒストグラムビンのターゲット数でもあります。

次に、autovacuumが起動するのを待たない場合は、ANALYZEを実行します。

ANALYZE test_data;

インデックスを直接ANALYZEできないため、テーブルをANALYZEする必要があります。 (効果を確認したい場合は、その前後に)確認してください:

SELECT * FROM pg_statistic WHERE starelid = 'idx_test_btree'::regclass;

クエリを再試行してください...

関連:

5