JSONBに格納されたメタデータのパフォーマンスを、PostgreSQL 9.4サーバーを使用する従来のEAVカタログ(生物医学のシナリオで一般的に使用される)と比較してテストしています。
適切なインデックス付けでEAVパフォーマンスを改善しようとすると問題が発生します。これは他のテーブルでの他のクエリでも発生した問題なので、根本的な間違いを犯しているのではないかと心配しています。
EAVをモデル化するために、3つのテーブルがあります。
1)すべてのエンティティを含むdata
(表示されていません)
2)すべての属性を含むeav_attribute
CREATE TABLE eav_attribute (
id serial PRIMARY KEY,
data_type integer NOT NULL,
loop integer,
name text NOT NULL,
field_type text NOT NULL,
has_unit boolean,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
2)すべての値を含むeav_value_text_data
CREATE TABLE eav_value_text_data (
id serial PRIMARY KEY,
entity integer NOT NULL,
attribute integer NOT NULL,
value text NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
最後の2つのテーブルを関連付ける次の外部キー制約を使用します(他の外部キーは、試したクエリで使用されていないため、表示されていません)。
ALTER TABLE ONLY eav_value_text_data
ADD CONSTRAINT eav_value_text_data_attribute_fkey
FOREIGN KEY (attribute) REFERENCES eav_attribute(id);
2つのテーブルに、ゲノムの変動の巨大なデータセットを入力しました。 dataテーブルの2,500万行に対してeav_value_text_data
テーブルに2億5,000万行以上と49の異なる属性(eav_attribute
の行)があります。
次のクエリのコストとパフォーマンスを確認しています。
SELECT count(*) FROM eav_value_text_data v
INNER JOIN eav_attribute a ON a.id = v.attribute
WHERE a.data_type = 11
AND a.name = 'id'
AND v.value = 'rs145368920';
値[rs145368920]はeav_value_text_data
に15回だけ表示されます
最初に、2つのテーブルにインデックスを追加せずにEXPLAIN ANALYZEを実行しました。
クエリプラン
Aggregate (cost=5109969.74..5109969.75 rows=1 width=0) (actual time=351086.638..351086.639 rows=1 loops=1)
-> Nested Loop (cost=0.00..5109969.32 rows=168 width=0) (actual time=34814.603..351086.541 rows=15 loops=1)
Join Filter: (a.attribute = b.id)
-> Seq Scan on eav_attribute b (cost=0.00..1.73 rows=1 width=4) (actual time=0.027..0.034 rows=1 loops=1)
Filter: ((data_type = 11) AND (name = 'id'::text))
Rows Removed by Filter: 48
-> Seq Scan on eav_value_text_data a (cost=0.00..5109864.40 rows=8255 width=4) (actual time=34814.555..351086.380 rows=15 loops=1)
Filter: (value = 'rs145368920'::text)
Rows Removed by Filter: 252054702
Planning time: 145.614 ms
Execution time: 351096.291 ms
両方のテーブルで順次スキャンを実行し、350秒という極めてひどい時間を必要とします。次に、eav_value_text_data
の列属性にインデックスを追加して、クエリのパフォーマンスを向上させます。
CREATE INDEX eav_value_text_data_attribute_index ON eav_value_text_data (attribute);
そして、もう一度EXPLAIN ANALYZEを実行します。
クエリプラン
Aggregate (cost=2726067.17..2726067.18 rows=1 width=0) (actual time=1262829.858..1262829.859 rows=1 loops=1)
-> Nested Loop (cost=423172.71..2726066.76 rows=166 width=0) (actual time=113152.884..1262829.758 rows=15 loops=1)
-> Seq Scan on eav_attribute b (cost=0.00..1.73 rows=1 width=4) (actual time=0.027..0.062 rows=1 loops=1)
Filter: ((data_type = 11) AND (name = 'id'::text))
Rows Removed by Filter: 48
-> Bitmap Heap Scan on eav_value_text_data a (cost=423172.71..2726057.61 rows=741 width=4) (actual time=113152.821..1262829.557 rows=15 loops=1)
Recheck Cond: (attribute = b.id)
Rows Removed by Index Recheck: 223460596
Filter: (value = 'rs145368920'::text)
Rows Removed by Filter: 24188192
Heap Blocks: exact=37881 lossy=1921273
-> Bitmap Index Scan on eav_value_text_data_attribute_index (cost=0.00..423172.52 rows=22914127 width=0) (actual time=14177.368..14177.368 rows=24188207 loops=1)
Index Cond: (attribute = b.id)
Planning time: 126.846 ms
Execution time: 1262840.302 ms
現在、インデックススキャンを実行していますが、1260秒以上かかります。これは、インデックスがない場合よりも4倍近く劣っています。ここの問題は何ですか?これは、損失の多いヒープブロックの数が多いことに関係していますか? work_memを調整するだけで解決できますか?キャッシュ効果を回避するために、すべてのクエリはコールドキャッシュ(Postgresサーバーを停止してキャッシュをフラッシュする)で実行されました。
現在、クエリを実行しているシステムに4GBのRAM=があります。
work_mem = 4 MB
shared_buffers = 950 MB(合計RAMの約25%)
effective_cache_size = 2600 MB(合計RAMの約70%)
大きなテーブル_eav_value_text_data
_の選択的な述語は_v.value = 'rs145368920'
_であるため、何よりもvalue
のインデックスが必要です。 attribute
のインデックスはほとんど関係ありません-可能であれば index-only スキャンを許可する最初のインデックスとの組み合わせのみ:
_CREATE INDEX eav_value_text_data_val_att_idx ON eav_value_text_data (value, attribute);
_
これはhugeの違いを生むはずです。value
はインデックスの最初の列である必要があります。もっと:
uuid
としてインデックス化value
列が大きすぎてインデックスを作成できないことが判明したため、代わりにmd5ハッシュを使用することにしました(これは優れたソリューションです)。
Md5ハッシュを uuid
として保存することを検討してください。これは最も効率的(より小さく、より高速なストレージ)です。結果をuuid
にキャストできます:
_md5(value)::uuid
_
この回答の最後の章の詳細:
関連:
インデックスは次のようになります。
_CREATE INDEX eav_value_special_idx
ON eav_value_text_data (cast(md5(value) AS uuid), attribute);
_
インデックス定義の明示的なcast()
構文に注意してください:
クエリでは引き続き簡略構文を使用できます。
_SELECT count(*)
FROM eav_value_text_data v
JOIN eav_attribute a ON a.id = v.attribute
WHERE a.data_type = 11
AND a.name = 'id'
AND md5(v.value)::uuid = md5('rs145368920')::uuid;
_