シンプルな構造のテーブル(id、metadata_json、stamp)があります。stampはタイムスタンプで、Btreeインデックスが付いています。 MetadataJsonは、GINインデックスを持つjsonbです。
テーブルには2,500万行あります。 PostgreSQL 10を使用しています。
Table "public.metadata"
Column | Type | Modifiers
---------------+-----------------------------+-----------
id | uuid | not null
metadata_json | jsonb |
stamp | timestamp without time zone |
Indexes:
"metadata_pkey" PRIMARY KEY, btree (id)
"metadata_idx" gin (metadata_json)
"stamp_idx" btree (stamp)
私が実行しているクエリは非常に単純です:
select * from metadata where metadata_json @> '{"someBool": true}'
AND stamp >= '01-01-2016' ORDER BY stamp DESC LIMIT 100;
それがどのように機能するかという私の考え:私はスタンプにbtreeを持っているので、インデックスで逆順に移動し、json制限で行をテストする必要があります(40%の選択性があります)。数ミリ秒で戻ると思います。
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=80598.46..80598.71 rows=100 width=381) (actual time=445064.728..445064.791 rows=100 loops=1)
-> Sort (cost=80598.46..80607.46 rows=3600 width=381) (actual time=445064.724..445064.754 rows=100 loops=1)
Sort Key: stamp DESC
Sort Method: top-N heapsort Memory: 109kB
-> Bitmap Heap Scan on metadata (cost=66591.00..80460.87 rows=3600 width=381) (actual time=2881.164..444283.520 rows=1437024 loops=1)
Recheck Cond: ((metadata_json @> '{"someBool": true}'::jsonb) AND (stamp >= '2016-01-01 00:00:00'::timestamp without time zone))
Heap Blocks: exact=882439
-> BitmapAnd (cost=66591.00..66591.00 rows=3600 width=0) (actual time=2599.415..2599.415 rows=0 loops=1)
-> Bitmap Index Scan on metadata_idx (cost=0.00..260.25 rows=25100 width=0) (actual time=1762.166..1762.166 rows=10041746 loops=1)
Index Cond: (metadata_json @> '{"someBool": true}'::jsonb)
-> Bitmap Index Scan on stamp_idx (cost=0.00..66328.69 rows=3600034 width=0) (actual time=760.136..760.136 rows=3591329 loops=1)
Index Cond: (stamp >= '2016-01-01 00:00:00'::timestamp without time zone)
Planning time: 5.008 ms
Execution time: 445072.043 ms
(14 rows)
計画から、プランナーは統計が本当にオフになっているようですが、テーブルが分析され、サンプリングは1000に設定されました。
編集:いくつかの検索の結果、postgresにはjsonbデータ型の統計がないことがわかりました...このタイプのクエリを最適化する方法を教えてください。
編集2:ビットマップスキャンを無効にすると、クエリは非常に高速(2ミリ秒)になります。しかし、私はそれが良い解決策だとは思いません...
編集3:フェンシング(WITH CTEステートメント)
Limit (cost=1489968.48..1489968.73 rows=100 width=56) (actual time=447543.199..447543.262 rows=100 loops=1)
CTE t
-> Bitmap Heap Scan on metadata (cost=67228.70..1408830.13 rows=3600034 width=381) (actual time=1045.566..441897.315 rows=3591329 loops=1)
Recheck Cond: (stamp >= '2016-01-01 00:00:00'::timestamp without time zone)
Heap Blocks: exact=1229457
-> Bitmap Index Scan on stamp_idx (cost=0.00..66328.69 rows=3600034 width=0) (actual time=663.960..663.960 rows=3591329 loops=1)
Index Cond: (stamp >= '2016-01-01 00:00:00'::timestamp without time zone)
-> Sort (cost=81138.35..81147.35 rows=3600 width=56) (actual time=447543.197..447543.227 rows=100 loops=1)
Sort Key: t.stamp DESC
Sort Method: top-N heapsort Memory: 109kB
-> CTE Scan on t (cost=0.00..81000.76 rows=3600 width=56) (actual time=1045.577..446935.261 rows=1437024 loops=1)
Filter: (metadata_json @> '{"someBool": true}'::jsonb)
Rows Removed by Filter: 2154305
Planning time: 0.169 ms
Execution time: 447692.843 ms
編集4:フェンシング(FROMでの副選択)
Limit (cost=1798933.42..1811851.02 rows=100 width=381) (actual time=198282.400..198282.400 rows=0 loops=1)
-> Subquery Scan on foo (cost=1798933.42..2186461.48 rows=3000 width=381) (actual time=198282.397..198282.397 rows=0 loops=1)
Filter: (foo.metadata_json @> '{"someBool": true}'::jsonb)
Rows Removed by Filter: 3591329
-> Gather Merge (cost=1798933.42..2148961.13 rows=3000028 width=381) (actual time=184803.964..195869.763 rows=3591329 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Sort (cost=1797933.40..1801683.43 rows=1500014 width=381) (actual time=184599.426..188532.836 rows=1197110 loops=3)
Sort Key: metadata.stamp DESC
Sort Method: external merge Disk: 461368kB
-> Parallel Bitmap Heap Scan on metadata (cost=67228.70..1382579.88 rows=1500014 width=381) (actual time=1171.006..178501.269 rows=1197110 loops=3)
Recheck Cond: (stamp >= '2016-01-01 00:00:00'::timestamp without time zone)
Heap Blocks: exact=408005
-> Bitmap Index Scan on stamp_idx (cost=0.00..66328.69 rows=3600034 width=0) (actual time=728.401..728.401 rows=3591329 loops=1)
Index Cond: (stamp >= '2016-01-01 00:00:00'::timestamp without time zone)
Planning time: 6.704 ms
Execution time: 198509.456 ms
強制的に無効にしたビットマップスキャン
set enable_bitmapscan = off;
explain analyze select * from metadata where metadata_json @> '{"someBool": true}' AND stamp >= '01-01-2015' ORDER BY stamp DESC LIMIT 100;
Limit (cost=0.44..256064.27 rows=100 width=381) (actual time=0.065..1.814 rows=100 loops=1)
-> Index Scan Backward using stamp_idx on metadata (cost=0.44..18423793.42 rows=7195 width=381) (actual time=0.064..1.777 rows=100 loops=1)
Index Cond: (stamp >= '2015-01-01 00:00:00'::timestamp without time zone)
Filter: (metadata_json @> '{"someBool": true}'::jsonb)
Rows Removed by Filter: 126
Planning time: 0.180 ms
Execution time: 1.856 ms
編集5存在する複合gin(stamp、metadata_json)インデックスのみ:
explain analyze select * from metadata where metadata_json @> '{"someBool": true}
AND stamp >= '01-01-2016' ORDER BY stamp DESC LIMIT 100;
Limit (cost=14132.36..14132.61 rows=100 width=381) (actual time=308836.991..308837.052 rows=100 loops=1)
-> Sort (cost=14132.36..14141.36 rows=3600 width=381) (actual time=308836.988..308837.018 rows=100 loops=1)
Sort Key: stamp DESC
Sort Method: top-N heapsort Memory: 109kB
-> Bitmap Heap Scan on metadata (cost=124.90..13994.77 rows=3600 width=381) (actual time=3160.418..308183.328 rows=1437024 loops=1)
Recheck Cond: ((stamp >= '2016-01-01 00:00:00'::timestamp without time zone) AND (metadata_json @> '{"someBool": true}'::jsonb))
Heap Blocks: exact=882439
-> Bitmap Index Scan on metadata_stamp_metadata_json_idx (cost=0.00..124.00 rows=3600 width=0) (actual time=2883.484..2883.484 rows=1437024 loops=1)
Index Cond: ((stamp >= '2016-01-01 00:00:00'::timestamp without time zone) AND (metadata_json @> '{"someBool": true}'::jsonb))
Planning time: 0.233 ms
Execution time: 308857.051 ms
最終的な解決策:
Jsonをkey-valueに分解し、それをテーブル「recordId、key、value、stamp」として保存しました。そして私はこれらにbtreeインデックスを作成しました-そして結果は数ミリ秒で普遍的に返されます。統計のないjsonには、普遍的な解決策はないと思います。
これはおそらくjsonb構造で実行できる最良の方法であるため、正しい答えはEvanに行きます。
ここで苦しんでいる本当の問題は、jsonbの統計情報が悪いことです。これは既知の問題です。これも修正されません。
-> Bitmap Index Scan on metadata_idx (cost=0.00..260.25 rows=25100 width=0) (actual time=1762.166..1762.166 rows=10041746 loops=1)
Index Cond: (metadata_json @> '{"someBool": true}'::jsonb)
ここで、PostgreSQLは25100を想定していますが、選択性が低いため10041746が返されます。タイムスタンプの見積もりは3.6 Mを返すとかなり正確です。それは多くの掘り起こしです。そのため、代わりにjsonbでインデックススキャンを実行します。
いくつかのオプションがあります。
Ypercubeの提案に従って、複合GINインデックスを作成します。
CREATE EXTENSION btree_gin;
CREATE INDEX ON metadata USING gin(stamp, metadata_json);
Metadata_json-> someBoolにインデックスを追加します
最適化フェンスを使用します。
SELECT *
FROM (
SELECT *
FROM metadata
WHERE stamp >= '01-01-2016'
ORDER BY stamp DESC
OFFSET 0
)
WHERE metadata_json @> '{"someBool": true}'
ORDER BY stamp DESC
LIMIT 100;
jsonb_path_ops
。複合GINインデックスの作成にも使用できます。
CREATE INDEX ON metadata USING gin(stamp, metadata_json jsonb_path_ops);