Prodとdevの間で同じクエリを実行し、少し異なるクエリプランを受け取るという奇妙な問題があります。その理由はわかりません。できる限りのことはカバーするつもりですが、重要なことを忘れてしまった場合はお知らせください。
Postgresql:11.6
GCE製品仕様:
GCE Dev仕様:
製品構成:
shared_buffers = 16GB
seq_page_cost = 1.0
random_page_cost = 3.0
effective_cache_size = 48GB
work_mem = 512MB
maintenance_work_mem = 2GB
effective_io_concurrency = 200
開発設定:
shared_buffers = 3840MB
seq_page_cost = 1.0
random_page_cost = 4.0
effective_cache_size = 11520MB
work_mem = 512MB
maintenance_work_mem = 960MB
effective_io_concurrency = 4
テーブル定義
Table "api.ms1_peak"
Column | Type | Collation | Nullable | Default
-----------------------+------------------+-----------+----------+---------------------------------------------------
ms1_peak_id | integer | | not null | nextval('api.ms1_peak_ms1_peak_id_seq'::regclass)
mzml_id | integer | | |
charge | integer | | |
mz | double precision | | |
adduct_m | double precision | | |
rt_corrected | double precision | | |
Indexes:
"ms1_peak_pkey" PRIMARY KEY, btree (ms1_peak_id)
"ms1_peak_ms1_peak_id_idx" btree (ms1_peak_id) INCLUDE (mz, rt_corrected)
"ms1_peak_mz_rt_ms1_peak_id_idx" btree (mz, rt_corrected) INCLUDE (ms1_peak_id, charge)
"ms1_peak_mzml_idx" btree (mzml_id)
Foreign-key constraints:
"ms1_peak_mzml_id_fkey" FOREIGN KEY (mzml_id) REFERENCES api.mzml(mzml_id) ON DELETE CASCADE
Table "api.molecular_structure"
Column | Type | Collation | Nullable | Default
------------------------+------------------+-----------+----------+-------------------------------------------------------------------------
molecular_structure_id | integer | | not null | nextval('api.molecular_structure_molecular_structure_id_seq'::regclass)
monoisotopic_mass | double precision | | |
mol | mol | | not null |
Indexes:
"molecular_structure_pkey" PRIMARY KEY, btree (molecular_structure_id)
"molecular_structure_unique_idx" UNIQUE, btree (md5(mol::text))
"molecular_structure_monoisotopic_mass_idx" btree (monoisotopic_mass, molecular_structure_id)
Referenced by:
TABLE "api.smallmolecule_fragment" CONSTRAINT "smallmolecule_molecular_structure_id_fkey" FOREIGN KEY (molecular_structure_id) REFERENCES api.molecular_structure(molecular_structure_id) ON DELETE CASCADE
TABLE "api.smallmolecule" CONSTRAINT "smallmolecule_molecular_structure_id_fkey" FOREIGN KEY (molecular_structure_id) REFERENCES api.molecular_structure(molecular_structure_id) ON DELETE SET NULL
Table "api.smallmolecule"
Column | Type | Collation | Nullable | Default
------------------------+--------------------------+-----------+----------+-------------------------------------------------------------
smallmolecule_id | integer | | not null | nextval('api.smallmolecule_smallmolecule_id_seq'::regclass)
created_date | timestamp with time zone | | | now()
original_smiles | text | | |
reference | json | | |
names | jsonb | | |
cas | text | | |
names_tsvector | tsvector | | |
molecular_structure_id | integer | | |
Indexes:
"smallmolecule_pkey" PRIMARY KEY, btree (smallmolecule_id)
"smallmolecule_molecular_structure_id_idx" btree (molecular_structure_id, smallmolecule_id)
"smallmolecule_names_idx" Gist (names_tsvector)
Foreign-key constraints:
"smallmolecule_molecular_structure_id_fkey" FOREIGN KEY (molecular_structure_id) REFERENCES api.molecular_structure(molecular_structure_id) ON DELETE SET NULL
"smallmolecule_organization_id_fkey" FOREIGN KEY (organization_id) REFERENCES api.organization(organization_id) ON DELETE SET NULL
問題のクエリ
select p.ms1_peak_id,
(
select coalesce(json_agg(sm.smallmolecule_id), '[]') from api.molecular_structure as ms
join api.smallmolecule sm using(molecular_structure_id)
where ms.monoisotopic_mass BETWEEN p.adduct_m - public.mz_tol(p.adduct_m, 5) AND
p.adduct_m + public.mz_tol(p.adduct_m, 5)
) as smallmolecules
from api.ms1_peak as p
where p.mzml_id = 20634
and adduct_m is not null
order by ms1_peak_id asc;
開発サーバーのクエリプランは次のとおりです
Sort (cost=94236607.88..94236610.32 rows=973 width=36) (actual time=173.075..173.144 rows=1082 loops=1)
Output: p.ms1_peak_id, ((SubPlan 1))
Sort Key: p.ms1_peak_id
Sort Method: quicksort Memory: 106kB
Buffers: shared hit=58597
-> Bitmap Heap Scan on api.ms1_peak p (cost=39.56..94236559.59 rows=973 width=36) (actual time=17.735..172.456 rows=1082 loops=1)
Output: p.ms1_peak_id, (SubPlan 1)
Recheck Cond: (p.mzml_id = 20634)
Filter: (p.adduct_m IS NOT NULL)
Rows Removed by Filter: 1318
Heap Blocks: exact=399
Buffers: shared hit=58597
-> Bitmap Index Scan on ms1_peak_mzml_idx (cost=0.00..39.32 rows=1967 width=0) (actual time=0.180..0.180 rows=2400 loops=1)
Index Cond: (p.mzml_id = 20634)
Buffers: shared hit=12
SubPlan 1
-> Aggregate (cost=96843.52..96843.53 rows=1 width=32) (actual time=0.157..0.157 rows=1 loops=1082)
Output: COALESCE(json_agg(sm.smallmolecule_id), '[]'::json)
Buffers: shared hit=58186
-> Hash Join (cost=6062.96..96842.90 rows=246 width=4) (actual time=0.136..0.156 rows=1 loops=1082)
Output: sm.smallmolecule_id
Hash Cond: (ms.molecular_structure_id = sm.molecular_structure_id)
Buffers: shared hit=58186
-> Bitmap Heap Scan on api.molecular_structure ms (cost=816.07..91096.68 rows=26500 width=4) (actual time=0.019..0.111 rows=89 loops=1082)
Output: ms.molecular_structure_id, ms.molecular_weight, ms.monoisotopic_mass, ms.molecular_formula, ms.flat_smiles, ms.mol, ms.ecfp6, ms.fcfp6
Recheck Cond: ((ms.monoisotopic_mass >= (p.adduct_m - (('5'::double precision * p.adduct_m) / '1000000'::double precision))) AND (ms.monoisotopic_mass <= (p.adduct_m + (('5'::double precision * p.adduct_m) / '1000000'::double precision))))
Heap Blocks: exact=54237
Buffers: shared hit=57961
-> Bitmap Index Scan on molecular_structure_monoisotopic_mass_idx (cost=0.00..809.45 rows=26500 width=0) (actual time=0.012..0.012 rows=89 loops=1082)
Index Cond: ((ms.monoisotopic_mass >= (p.adduct_m - (('5'::double precision * p.adduct_m) / '1000000'::double precision))) AND (ms.monoisotopic_mass <= (p.adduct_m + (('5'::double precision * p.adduct_m) / '1000000'::double precision))))
Buffers: shared hit=3724
-> Hash (cost=4633.03..4633.03 rows=49108 width=8) (actual time=16.781..16.782 rows=49102 loops=1)
Output: sm.smallmolecule_id, sm.molecular_structure_id
Buckets: 65536 Batches: 1 Memory Usage: 2431kB
Buffers: shared hit=225
-> Index Only Scan using smallmolecule_molecular_structure_id_idx on api.smallmolecule sm (cost=0.41..4633.03 rows=49108 width=8) (actual time=0.030..7.495 rows=49108 loops=1)
Output: sm.smallmolecule_id, sm.molecular_structure_id
Heap Fetches: 0
Buffers: shared hit=225
Planning Time: 0.440 ms
Execution Time: 173.291 ms
生産について
Sort (cost=3158406.45..3158408.86 rows=965 width=36) (actual time=10773.364..10773.433 rows=1082 loops=1)
Output: p.ms1_peak_id, ((SubPlan 1))
Sort Key: p.ms1_peak_id
Sort Method: quicksort Memory: 106kB
Buffers: shared hit=358573
-> Index Scan using ms1_peak_mzml_idx on api.ms1_peak p (cost=0.56..3158358.61 rows=965 width=36) (actual time=14.920..10770.308 rows=1082 loops=1)
Output: p.ms1_peak_id, (SubPlan 1)
Index Cond: (p.mzml_id = 20634)
Filter: (p.adduct_m IS NOT NULL)
Rows Removed by Filter: 1318
Buffers: shared hit=358573
SubPlan 1
-> Aggregate (cost=3266.75..3266.76 rows=1 width=32) (actual time=9.924..9.924 rows=1 loops=1082)
Output: COALESCE(json_agg(sm.smallmolecule_id), '[]'::json)
Buffers: shared hit=357961
-> Hash Join (cost=1164.60..3266.13 rows=246 width=4) (actual time=8.142..9.913 rows=1 loops=1082)
Output: sm.smallmolecule_id
Inner Unique: true
Hash Cond: (sm.molecular_structure_id = ms.molecular_structure_id)
Buffers: shared hit=357961
-> Index Only Scan using smallmolecule_molecular_structure_id_idx on api.smallmolecule sm (cost=0.41..1973.03 rows=49108 width=8) (actual time=0.009..5.957 rows=49108 loops=1017)
Output: sm.molecular_structure_id, sm.smallmolecule_id
Heap Fetches: 0
Buffers: shared hit=322390
-> Hash (cost=834.29..834.29 rows=26392 width=4) (actual time=0.087..0.087 rows=89 loops=1082)
Output: ms.molecular_structure_id
Buckets: 32768 Batches: 1 Memory Usage: 262kB
Buffers: shared hit=35571
-> Index Only Scan using molecular_structure_monoisotopic_mass_idx on api.molecular_structure ms (cost=0.45..834.29 rows=26392 width=4) (actual time=0.032..0.071 rows=89 loops=1082)
Output: ms.molecular_structure_id
Index Cond: ((ms.monoisotopic_mass >= (p.adduct_m - (('5'::double precision * p.adduct_m) / '1000000'::double precision))) AND (ms.monoisotopic_mass <= (p.adduct_m + (('5'::double precision * p.adduct_m) / '1000000'::double precision))))
Heap Fetches: 0
Buffers: shared hit=35571
Planning Time: 2.329 ms
Execution Time: 10773.584 ms
興味深いのは、私が別のクエリプランを取得している開発者です。通常はビットマップスキャンを使用していますが、本番環境ではインデックス/インデックスのみのスキャンを使用しています。
これで、ビットマップスキャンを無効にしてインデックスのみのルートを強制する場合、開発サーバーに近いクエリプランを出力してprodに送ることができます
これはset enable_bitmapscan = off;
を設定した後のdev出力です
Sort (cost=107687464.30..107687466.74 rows=973 width=36) (actual time=166.059..166.129 rows=1082 loops=1)
Output: p.ms1_peak_id, ((SubPlan 1))
Sort Key: p.ms1_peak_id
Sort Method: quicksort Memory: 106kB
Buffers: shared hit=58988
-> Index Scan using ms1_peak_mzml_idx on api.ms1_peak p (cost=0.56..107687416.01 rows=973 width=36) (actual time=17.591..165.425 rows=1082 loops=1)
Output: p.ms1_peak_id, (SubPlan 1)
Index Cond: (p.mzml_id = 20634)
Filter: (p.adduct_m IS NOT NULL)
Rows Removed by Filter: 1318
Buffers: shared hit=58988
SubPlan 1
-> Aggregate (cost=110667.53..110667.54 rows=1 width=32) (actual time=0.150..0.150 rows=1 loops=1082)
Output: COALESCE(json_agg(sm.smallmolecule_id), '[]'::json)
Buffers: shared hit=58376
-> Hash Join (cost=5247.33..110666.91 rows=246 width=4) (actual time=0.123..0.149 rows=1 loops=1082)
Output: sm.smallmolecule_id
Hash Cond: (ms.molecular_structure_id = sm.molecular_structure_id)
Buffers: shared hit=58376
-> Index Only Scan using molecular_structure_monoisotopic_mass_idx on api.molecular_structure ms (cost=0.45..104920.69 rows=26500 width=4) (actual time=0.010..0.106 rows=89 loops=1082)
Output: ms.monoisotopic_mass, ms.molecular_structure_id
Index Cond: ((ms.monoisotopic_mass >= (p.adduct_m - (('5'::double precision * p.adduct_m) / '1000000'::double precision))) AND (ms.monoisotopic_mass <= (p.adduct_m + (('5'::double precision * p.adduct_m) / '1000000'::double precision))))
Heap Fetches: 96251
Buffers: shared hit=58151
-> Hash (cost=4633.03..4633.03 rows=49108 width=8) (actual time=16.941..16.941 rows=49102 loops=1)
Output: sm.smallmolecule_id, sm.molecular_structure_id
Buckets: 65536 Batches: 1 Memory Usage: 2431kB
Buffers: shared hit=225
-> Index Only Scan using smallmolecule_molecular_structure_id_idx on api.smallmolecule sm (cost=0.41..4633.03 rows=49108 width=8) (actual time=0.027..7.519 rows=49108 loops=1)
Output: sm.smallmolecule_id, sm.molecular_structure_id
Heap Fetches: 0
Buffers: shared hit=225
このクエリは本番環境に少し近づいており、さらに高速です(devのどちらの場合もprodより高速です)。ただし、2つのクエリプランはまだ少し異なります。 prodクエリプランが内部固有を使用していることに注意してください:trueは、インデックスを「分子構造体のモノイソトピックス_マス」の下部に配置し、開発中に1082回ループします。内部固有を使用しない場合、このインデックスの使用法は説明の上位にあります:真
Explain.depeszを通じてこれらのクエリプランを配置すると、それが分解され、本番用クエリプランナーがapi.smallmoleculeのインデックスのみのスキャンで6秒、ハッシュ結合で4秒かかることがわかります。
開発中は、api.smallmoleculeでのインデックスのみのスキャンは8ミリ秒で、ハッシュ結合は28ミリ秒です。
Devとproductionの分子構造と小分子のデータは同じです。両方とも吸引され、分析されます。したがって、これらのテーブルのデータセットは同じです。
分子構造で約520万行、小分子で49000行。
構成の微調整をいじって、random_page_cost = 2.0
を設定すると、開発サーバーにまったく同じ計画を実行および出力させることができます。これにより、まったく同じ計画が作成されます。ですから、これは間違いなく2つの間のチューニングの問題だと考え始めています。 prodでrandom_page_costを調整しても、この特定のクエリには影響がないようです。
これらのサーバーはGoogle Cloud Engine上にあり、製品は永続的なSSDを使用しているため、開発者がHDDを使用している間、特定の値のチューニングは専用SSDの設定では正確ではない可能性があります。
より良い結果を得るために本番環境でこれを調整またはデバッグする方法についてのアイデアは素晴らしいでしょう。私が行方不明のものは教えてください。
編集:
Vacuum Analyzeは本番環境で実行されました。これは新しいデータなので、新しい挿入、更新、削除は行われないため、インデックスの断片化が問題であるとは思いません。
本番環境でインデックススキャンを無効にするとset enable_indexscans = 'off'
ビットマップヒープスキャンプランと、開発マシンのパフォーマンスに一致する180ミリ秒が得られます。
Sort (cost=70758692.10..70758694.51 rows=966 width=36) (actual time=179.621..179.692 rows=1082 loops=1)
Output: p.ms1_peak_id, ((SubPlan 1))
Sort Key: p.ms1_peak_id
Sort Method: quicksort Memory: 106kB
Buffers: shared hit=60536
-> Bitmap Heap Scan on api.ms1_peak p (cost=42.52..70758644.20 rows=966 width=36) (actual time=29.350..179.233 rows=1082 loops=1)
Output: p.ms1_peak_id, (SubPlan 1)
Recheck Cond: (p.mzml_id = 20634)
Filter: (p.adduct_m IS NOT NULL)
Rows Removed by Filter: 1318
Heap Blocks: exact=399
Buffers: shared hit=60536
-> Bitmap Index Scan on ms1_peak_mzml_idx (cost=0.00..42.28 rows=1962 width=0) (actual time=0.169..0.169 rows=2400 loops=1)
Index Cond: (p.mzml_id = 20634)
Buffers: shared hit=12
SubPlan 1
-> Aggregate (cost=73243.01..73243.02 rows=1 width=32) (actual time=0.163..0.163 rows=1 loops=1082)
Output: COALESCE(json_agg(sm.smallmolecule_id), '[]'::json)
Buffers: shared hit=60125
-> Hash Join (cost=3970.76..73242.40 rows=246 width=4) (actual time=0.145..0.162 rows=1 loops=1082)
Output: sm.smallmolecule_id
Hash Cond: (ms.molecular_structure_id = sm.molecular_structure_id)
Buffers: shared hit=60125
-> Bitmap Heap Scan on api.molecular_structure ms (cost=576.97..69450.26 rows=26392 width=4) (actual time=0.019..0.106 rows=89 loops=1082)
Output: ms.molecular_structure_id, ms.molecular_weight, ms.monoisotopic_mass, ms.molecular_formula, ms.flat_smiles, ms.mol, ms.ecfp6, ms.fcfp6
Recheck Cond: ((ms.monoisotopic_mass >= (p.adduct_m - (('5'::double precision * p.adduct_m) / '1000000'::double precision))) AND (ms.monoisotopic_mass <= (p.adduct_m + (('5'::double precision * p.adduct_m) / '1000000'::double precision))))
Heap Blocks: exact=54237
Buffers: shared hit=57836
-> Bitmap Index Scan on molecular_structure_monoisotopic_mass_idx (cost=0.00..570.37 rows=26392 width=0) (actual time=0.012..0.012 rows=89 loops=1082)
Index Cond: ((ms.monoisotopic_mass >= (p.adduct_m - (('5'::double precision * p.adduct_m) / '1000000'::double precision))) AND (ms.monoisotopic_mass <= (p.adduct_m + (('5'::double precision * p.adduct_m) / '1000000'::double precision))))
Buffers: shared hit=3599
-> Hash (cost=2780.02..2780.02 rows=49102 width=8) (actual time=28.821..28.822 rows=49102 loops=1)
Output: sm.smallmolecule_id, sm.molecular_structure_id
Buckets: 65536 Batches: 1 Memory Usage: 2431kB
Buffers: shared hit=2289
-> Seq Scan on api.smallmolecule sm (cost=0.00..2780.02 rows=49102 width=8) (actual time=0.007..19.592 rows=49108 loops=1)
Output: sm.smallmolecule_id, sm.molecular_structure_id
Buffers: shared hit=2289
Planning Time: 0.461 ms
Execution Time: 179.824 ms
(40 rows)
もう1つ気づいたのは、サブクエリ内のsmallmolecule table
への結合を削除すると、このクエリが非常に高速になることです。
編集2:
私はなんとかpg_basebackupを実行し、開発と本番の間でデータを完全に同期しました。数百万以上の挿入があった唯一のテーブルはms1_peakテーブルであり、これらの結果の間、mocular_structureテーブルとsmallmoleculeテーブルは2つの間で変更されませんでした。
Ms1_peakからの結果セットは、古いdevとprodの間で同じ結果を返しました(1082ピーク)。
しかし、いずれにしても、クエリが非常に高速であるため、大きなピークテーブルが原因であるとはまだ確信していません。しかし、今ではdevとprodの両方がまったく同じクエリプランと10秒のパフォーマンスを生成します。小分子テーブルへの参加が非常に遅いのはなぜですか。
この問題は、api.molecular_structure
(実際の89ではなく26392)でのビットマップインデックススキャンの行数の見積もりが悪いために発生し、計画を傾けるデータにわずかな違いがあるはずです。
推定値を改善することはおそらく困難です。
クエリを書き換えるのはどうですか?
SELECT p.ms1_peak_id,
coalesce(json_agg(sm.smallmolecule_id), '[]')
FROM api.ms1_peak AS p
LEFT JOIN api.molecular_structure AS ms
ON ms.monoisotopic_mass BETWEEN p.adduct_m - public.mz_tol(p.adduct_m, 5)
AND p.adduct_m + public.mz_tol(p.adduct_m, 5)
LEFT JOIN api.smallmolecule sm
USING (molecular_structure_id)
WHERE p.mzml_id = 20634
AND p.adduct_m IS NOT NULL
GROUP BY p.ms1_peak_id
ORDER BY p.ms1_peak_id ASC;
速度が向上するかどうかはわかりませんが、試してみる価値はあります。
製品にSSDがあるので、seq_page_cost = random_page_cost = 1.0を設定してみましたか?そう示唆する記事がたくさんあります。
本番環境では開発環境のデータが小さいため、同様の問題が発生しました。インデックスを汚染している多数の終了したクライアントのデータ(そのままの行)があった。そのため、終了したクライアントのデータを除外するようにインデックスを調整したところ、パフォーマンスはprod/devでほぼ同じでした。