SlideShare a Scribd company logo
4
Most read
5
Most read
6
Most read
© 2023 NTT DATA Corporation
Memoizeの仕組み
41回 PostgreSQLアンカンファレンス
2023/4/24
NTTデータ 技術開発本部 笠原辰仁
© 2023 NTT DATA Corporation 2
はじめに
• 本日は、PostgreSQLのMemoizeという機能について説明します。最近の
PostgreSQL(v14~)をお使いの方でしたら、既に知っていて活用している、あるいは知らない
うちにお世話になっているかもしれません。
• 今日はそのMemoizeの仕組みや効果について一緒に見ていこうと思います。
• 本資料は公開します。
© 2023 NTT DATA Corporation 3
Memoizeについて
Memoizeは当初 Result Cache という名称で開発が進められていた機能。2021年4月にCommitされました。
• Add Result Cache executor node (take 2)
• https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=9eacee2e62d89cab7b004f97
c206c4fba4f1d745
その後にもう少し良い名前はないものか、ということで Memoize に改称。
• Change the name of the Result Cache node to Memoize
• https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=83f4fcc65503c5d4e5d5eefc8
e7a70d3c9a6496f
ちなみにMemoize(Memoization:メモ化)は
「プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブ
ルーチン(関数)の呼び出し毎の再計算を防ぐ手法である。」
(https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E5%8C%96 より引用)
のとおり、PostgreSQLの方言やDBMSに特化した機能・演算ではないです。
© 2023 NTT DATA Corporation 4
Memoizeの狙い
Nested Loop Joinにおいて、一方のテーブルの結合キーのカーディナリティが低い場合、他方のテーブルの同じ行に
対して何度も読み取りが繰り返されることになります。これ自体は正常な動作ですが、同じデータへ通常のパス
(インデックスのルート -> 中間ノード -> リーフ -> ヒープ…)を何度も辿るのは冗長です。
col1 ・・・
1
1
2
2
col1 ・・・
1
2
3
4
結合キーのカーディナリティ低 結合キーのカーディナリティ高
SELECT … FROM t1 JOIN t2 ON t1.col1 = t2.col1 WHERE …
t1 (Outer) t2 (Inner)
特定の行のみ何度もアクセスされる。
インデックススキャンかつ共有バッファに
乗り切るサイズだとしても、チリも積も
ればそれなりのオーバーヘッドになる。
© 2023 NTT DATA Corporation 5
Memoizeの狙い
そこで、Memoize(メモ化)を利用し結合対象のレコードをバックエンドプロセスのローカルメモリにキャッシュすることで、
Nested Loop Joinの性能向上が可能となりました。この機能は特にユーザが意識せずとも自動で利用されます。
(enable_memoize = on/off で使用有無を制御可能)。プランナがコスト推定を行い、利用に適切だと判断さ
れると使われます。Nested Loopの時に必ず利用されるわけではありません。
col1 ・・・
1
1
2
2
col1 ・・・
1
2
3
4
結合キーのカーディナリティ低 結合キーのカーディナリティ高
SELECT … FROM t1 JOIN t2 ON t1.col1 = t2.col1 WHERE …
t1 (Outer) t2 (Inner)
col1 ・・・
1
2
Memoizeされた
キャッシュ
Memoize
Memoizeされたデータを使うことでt2へのアクセスを高速化できる
© 2023 NTT DATA Corporation 6
Memoizeの仕組み
Memoizeはメモ化する対象のテーブルから行を読み取りつつ逐次実施されます。メモ化されたデータの実体はハッ
シュテーブルとハッシュテーブルのエントリキー値のリスト(dlist)です。エントリのキー値はLRUで管理されます。ハッシュ
テーブルの上限に達した場合は古いものを除去します。つまりディスクに書き込み(Spill Out)はしません。
col1 ・・・
・・・
40 ZZZ
・・・
t2
Key:90 Key:40 Key:10 Key:40
Key complete tuple
0x11 t {10, AAA}
0x12 t {90, ZZZ}
0x13 f {40, HHH}
ハッシュテーブルは「(double) work_mem * hash_mem_multiplier * 1024.0」を上限としている。
ハッシュテーブルへの格納時に上限を超える場合はエントリーキーのLRUリストを元に古いエントリ内
の全データを削除
{10, BBB}
{40, BBB} {40, ZZZ}
エントリーキーのリスト(LRU)
ハッシュテーブル
結合キー(例はcol1)の値をエントリー
キーから検索。あればリストの末尾に
移動。なければリストの末尾に追加
リストに追加したキー値含むタプルデータを
ハッシュテーブルへ格納。もし対象テーブルに
これ以上該当のキー値がない場合は
completeフラグを真にする。
© 2023 NTT DATA Corporation 7
Memoizeの仕組み
MEMO_CACHE_LOOKUP
(開始ステート)
MEMO_FILLING_CACHE
(ハッシュテーブルへデータ蓄積するステート)
MEMO_CACHE_FETCH_NEXT_TUPLE
(ハッシュテーブル内のキャッシュを返すステート)
MEMO_END_OF_SCAN
(終了ステート)
キーリストにエントリがある &&
completeは真?
ハッシュテーブルのデータはもうない?
該当のキー値を持つレコードは
もうない?
新規のデータをハッシュテーブルへ
格納可能?(必要に応じてハッシュテーブルの
古いエントリ削除しEvictions++)
MEMO_CACHE_BYPASS_MODE
(ハッシュテーブルを使用しないステート)
Memoize内部の処理はOuterのテーブルから参照した1レコードを対象に、Innerのテーブルから結合対象となるレコードを全て検
索する過程で以下のフローをたどります。ステートマシンとなっており、「MEMO_CACHE_LOOKUP」から始まり
「MEMO_END_OF_SCAN」で1巡です。これをOuterの結合対象レコードの数だけ行います。なお、ハッシュテーブルがいっぱいにな
りハッシュテーブルのサイズ削減時に格納しようとしているキーが削減対象になる場合はハッシュテーブルを使用しないモードになります。
該当のキー値を持つレコードは
もうない?
Yes
(cache hit++)
Yes
Yes
Yes
(ハッシュテーブルの
complete flagを
trueに)
Yes
No
No
No (次のデータ返却の準備)
No
(cache miss++)
No
(格納しようとしたキーがエントリ削除対象の場合 overflow++)
© 2023 NTT DATA Corporation 8
実行計画でMemoizeの効果を見る
c1 c2
1
2
・・・
100
1
2
・・・
100
結合キーのカーディナリティ低
t1 (Outer)
Outer(駆動表)はc1列に1~100が5000回繰り返される値を持つ50万件のテーブル。
Innerはc1列に1~500000を持つ50万件のテーブル。
「SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1;」
を実行し、c1列を結合キーとしてNested Loopした場合の性能差を見てみます。
c1 c2
1
2
・・・
500000
t2 (Inner)
結合キーのカーディナリティ高
Outer(駆動表)のt1は全行が検索対象。t2は一部のみ対象。
従来(-v13)であればt2へのIndexScanが50万回走るが、
Memoizeを使うことでどの程度高速化するか?
© 2023 NTT DATA Corporation 9
【参考】 サンプル
-- 開発中のPostgreSQLのHEAD (2023/4月中旬)で実験
-- Unloggedは単にWALを書きたくないだけ
CREATE UNLOGGED TABLE t1 (c1 int, c2 text);
CREATE UNLOGGED TABLE t2 (c1 int, c2 text);
-- t1はカーディナリティ低、t2はカーディナリティ高
INSERT INTO t1 SELECT generate_series(1,100), md5(i::text) FROM generate_series(1,5000)i;
INSERT INTO t2 SELECT i, md5(i::text) FROM generate_series(1,500000)i;
CREATE INDEX t1_c1_idx ON t1 (c1);
CREATE INDEX t2_c1_idx ON t2 (c1);
ANALYZE t1, t2;
-- 以下はなるべく実行計画をMemoizeのon/offで揃えるため
SET enable_hashjoin TO off;
SET enable_mergejoin TO off;
SET enable_bitmapscan TO off;
SET max_parallel_workers_per_gather = 0;
© 2023 NTT DATA Corporation 10
実行計画でMemoizeの効果を見る
SET enable_memoize TO off;
=# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2 ;
QUERY PLAN
---------------------------------------------------------------------------------------------
Nested Loop (actual time=0.022..898.447 rows=500000 loops=1)
-> Seq Scan on t1 (actual time=0.008..45.665 rows=500000 loops=1)
-> Index Scan using t2_c1_idx on t2 (actual time=0.001..0.001 rows=1 loops=500000)
Index Cond: (c1 = t1.c1)
Planning Time: 0.072 ms
Execution Time: 928.049 ms
(6 rows)
今回のサンプルを使った環境では、Memoizeを使わない場合、928ミリ秒でした。
想定通り、Outer(駆動表)をSeqScanし、50万件を対象にInnerのt2に50万回の
IndexScanが実施されている(loops=500000)
© 2023 NTT DATA Corporation 11
実行計画でMemoizeの効果を見る
SET enable_memoize TO on;
=# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1;
QUERY PLAN
------------------------------------------------------------------------------------------
Nested Loop (actual time=0.028..294.401 rows=500000 loops=1)
-> Seq Scan on t1 (actual time=0.009..39.044 rows=500000 loops=1)
-> Memoize (actual time=0.000..0.000 rows=1 loops=500000)
Cache Key: t1.c1
Cache Mode: logical
Hits: 499900 Misses: 100 Evictions: 0 Overflows: 0 Memory Usage: 14kB
-> Index Scan using t2_c1_idx on t2 (actual time=0.001..0.001 rows=1 loops=100)
Index Cond: (c1 = t1.c1)
Planning Time: 0.093 ms
Execution Time: 311.941 ms
(10 rows)
Memoizeを使う場合、311ミリ秒となり、おおよそ3倍近く高速化しました。
Outer(駆動表)の最初の100行(c1が1~100)はハッシュテーブルにないので、
Missesは100。IndexScanも100回実施(loops=100)。それ以降はハッシュ
テーブルでヒットするので、Hitsが500000 – 100 = 499900となっている。
© 2023 NTT DATA Corporation 12
Memoizeが不利になる場合を作ってみる
c1 c2
1
2
・・・
100
1
2
・・・
100
結合キーのカーディナリティ低
t1 (Outer)
先ほどのサンプルテーブルのInner側にc1=100のレコードを1000件追加します。ついでにc2列を長大にし、
あえてハッシュテーブルに収まらない状態を作ってみます。
ついでにwork_mem=‘64kB’としてハッシュテーブルを小さくしてみましょう。
c1 c2
1
2
・・・
500000
t2 (Inner)
結合キーのカーディナリティ高
col1 ・・・
1
2
ハッシュテーブル
「c1:100, c2:1.9kBくらいのテキスト」
を1000件追加してみる。
ここに収まりきらないケースでMemoizeが有効になった場合はどうなるか?
© 2023 NTT DATA Corporation 13
【参考】 サンプル2
-- Innerのt2へ1000件追加
INSERT INTO t2 SELECT 100, repeat(md5(i::text), 60) FROM generate_series(1,1000)i;
-- work_memを64kB (最低値)へ
SET work_mem TO ‘64kB’;
© 2023 NTT DATA Corporation 14
再び実行計画でMemoizeの効果を見る
SET enable_memoize TO off;
=# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1;
QUERY PLAN
----------------------------------------------------------------------------------------
Nested Loop (actual time=0.024..1855.766 rows=5500000 loops=1)
-> Seq Scan on t1 (actual time=0.008..34.877 rows=500000 loops=1)
-> Index Scan using t2_c1_idx on t2 (actual time=0.001..0.003 rows=11 loops=500000)
Index Cond: (c1 = t1.c1)
Planning Time: 0.135 ms
Execution Time: 2009.872 ms
(6 rows) (6 rows)
Memoizeを使わない場合、2009ミリ秒でした。t2に結合対象が1000件増え、結果の行数が50万から5000 *
1000 (c1=100がOuterに5000件ある) 増えて550万件になったので実行時間が増えてます。
© 2023 NTT DATA Corporation 15
再び実行計画でMemoizeの効果を見る
SET enable_memoize TO on;
=# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1;
QUERY PLAN
------------------------------------------------------------------------------------------
Nested Loop (actual time=0.018..3822.641 rows=5500000 loops=1)
-> Seq Scan on t1 (actual time=0.005..52.471 rows=500000 loops=1)
-> Memoize (actual time=0.002..0.006 rows=11 loops=500000)
Cache Key: t1.c1
Cache Mode: logical
Hits: 0 Misses: 500000 Evictions: 499960 Overflows: 5000 Memory Usage: 130kB
-> Index Scan using t2_c1_idx on t2 (actual time=0.001..0.004 rows=11 loops=500000)
Index Cond: (c1 = t1.c1)
Planning Time: 0.193 ms
Execution Time: 4040.609 ms
(10 rows)
Memoizeを使う場合、4040ミリ秒となり、おおよそ2倍近く低速化しました。
c1=100の結合時にハッシュテーブルへ格納できないためOverflow(ハッシュテーブルに収まりきら
なかった回数)がc1=100の件数、つまり5000回発生。
今回はc1が1~100の巡回において1~99でハッシュテーブルへ格納、100で全てのエントリがクリ
ア、また1からその繰り返し・・となっており、Memoizeが全く機能できなかった。そのためMissesが
50万回となっている。Evictionsはハッシュテーブルから除去されたエントリ数の総計。
© 2023 NTT DATA Corporation 16
再び実行計画でMemoizeの効果を見る
SET work_mem TO '4MB’;
=# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1;
QUERY PLAN
------------------------------------------------------------------------------------------
Nested Loop (actual time=0.027..1107.136 rows=5500000 loops=1)
-> Seq Scan on t1 (actual time=0.009..45.169 rows=500000 loops=1)
-> Memoize (actual time=0.000..0.001 rows=11 loops=500000)
Cache Key: t1.c1
Cache Mode: logical
Hits: 499900 Misses: 100 Evictions: 0 Overflows: 0 Memory Usage: 1928kB
-> Index Scan using t2_c1_idx on t2 (actual time=0.002..0.005 rows=11 loops=100)
Index Cond: (c1 = t1.c1)
Planning Time: 0.099 ms
Execution Time: 1313.725 ms
(10 rows)(10 rows)
work_memを十分な値に設定した場合は1313ミリ秒となり、Memoizeを使わないケースよりも高速化しました。
© 2023 NTT DATA Corporation 17
【おまけ】ハッシュテーブルからあふれるケースでもマシな場合
=# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1;
QUERY PLAN
---------------------------------------------------------------------------------------------
Nested Loop (actual time=0.031..1487.835 rows=5500000 loops=1)
-> Index Scan using t1_c1_idx on t1 (actual time=0.017..109.438 rows=500000 loops=1)
-> Memoize (actual time=0.000..0.002 rows=11 loops=500000)
Cache Key: t1.c1
Cache Mode: logical
Hits: 494901 Misses: 5099 Evictions: 5099 Overflows: 5000 Memory Usage: 130kB
-> Index Scan using t2_c1_idx on t2 (actual time=0.002..0.103 rows=982 loops=5099)
Index Cond: (c1 = t1.c1)
Planning Time: 0.096 ms
Execution Time: 1602.543 ms
(10 rows)
Outerのテーブルスキャン順序を1,2,3 … 99,100,1,2,3…ではなく、1,1,1,2,2,…99,100,100のようにして
キャッシュのEvictionが起こりにくいようにすると、性能低下が限定的となります。
Overflowは変わらないものの、c1=1~99をOuterから読みだしているフェーズでは99回の
Missesとなるが残りはキャッシュに救われるのでHitsが多くなる。c1=100のフェーズでは毎回
OverflowとEvictionsが発生し、Hitしない。
© 2023 NTT DATA Corporation 18
【参考】 PostgreSQL16からのMemoizeの適用領域拡大
=# EXPLAIN (ANALYZE on, COSTS off)
SELECT * FROM t1, (SELECT * FROM t2 UNION ALL SELECT * FROM t2) t3 WHERE t1.c1 = t3.c1;
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Nested Loop (actual time=0.019..346.989 rows=1000000 loops=1)
-> Seq Scan on t1 (actual time=0.005..37.872 rows=500000 loops=1)
-> Memoize (actual time=0.000..0.000 rows=2 loops=500000)
Cache Key: t1.c1
Cache Mode: logical
Hits: 499900 Misses: 100 Evictions: 0 Overflows: 0 Memory Usage: 21kB
-> Append (actual time=0.001..0.003 rows=2 loops=100)
-> Index Scan using t2_c1_idx on t2 (actual time=0.001..0.001 rows=1 loops=100)
Index Cond: (c1 = t1.c1)
-> Index Scan using t2_c1_idx on t2 t2_1 (actual time=0.001..0.001 rows=1 loops=100)
Index Cond: (c1 = t1.c1)
Planning Time: 0.242 ms
Execution Time: 383.323 ms
(13 rows)
InnerがUNION ALL経由のAppendのノードだった場合でも、v16からはMemoizeを利用できるようになりました。
(パーティションテーブル経由のAppendノードに対してはv15でもMemoizeを利用できます)
© 2023 NTT DATA Corporation 19
【参考】 Cache Mode
通常、Memoizeしたデータの引き当てはCache-Keyによるハッシュ等号演算子で行われ、この場合はLogicalモードでの比較
となります。一方、比較演算やfloatの+0.0/-0.0のようなハッシュ等号演算では同じと評価されるケースではバイナリモードでの
比較が行われます。
postgres=# EXPLAIN SELECT count(*) FROM t1, t2 WHERE t1.c1 = t2.c2;
QUERY PLAN
-----------------------------------------------------------------------------------------
Aggregate (cost=420.61..420.62 rows=1 width=8)
-> Nested Loop (cost=0.30..395.61 rows=10000 width=0)
-> Seq Scan on t2 (cost=0.00..145.00 rows=10000 width=4)
-> Memoize (cost=0.30..0.32 rows=1 width=4)
Cache Key: t2.c2
Cache Mode: logical
-> Index Only Scan using t1_pkey on t1 (cost=0.29..0.31 rows=1 width=4)
Index Cond: (c1 = t2.c2)
postgres=# EXPLAIN SELECT count(*) FROM t1, t2 WHERE t1.c1 < t2.c2;
QUERY PLAN
---------------------------------------------------------------------------------------------
Aggregate (cost=500337.24..500337.25 rows=1 width=8)
-> Nested Loop (cost=0.30..417003.91 rows=33333333 width=0)
-> Seq Scan on t2 (cost=0.00..145.00 rows=10000 width=4)
-> Memoize (cost=0.30..58.63 rows=3333 width=4)
Cache Key: t2.c2
Cache Mode: binary
-> Index Only Scan using t1_pkey on t1 (cost=0.29..58.62 rows=3333 width=4)
Index Cond: (c1 < t2.c2)
© 2023 NTT DATA Corporation 20
まとめ
Memoizeの目的、コンセプト、仕組み、実行計画を通した実際の作用を見てました。
ハマれば、いつものクエリが自然に高速化されると思います。PostgreSQLをバージョンアップしたら、
高速化されたんだけど?という方は実行計画を見るとMemoizeが機能しているかもしれません。
ただし場合によっては性能低下の要因となる可能性がありますので、仕組みを踏まえて使いこな
してみてください。
© 2023 NTT DATA Corporation
その他、記載されている会社名、商品名、又はサービス名は、
各社の登録商標又は商標です。

More Related Content

PPTX
PostgreSQLのロール管理とその注意点(Open Source Conference 2022 Online/Osaka 発表資料)
PPTX
監査要件を有するシステムに対する PostgreSQL 導入の課題と可能性
PDF
Vacuum徹底解説
PDF
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
PDF
「のどが渇いた」というユーザーに何を出す? ユーザーの「欲しい」に惑わされない、本当のインサイトを見つけるUXデザイン・UXリサーチ
PPTX
【DL輪読会】A Time Series is Worth 64 Words: Long-term Forecasting with Transformers
PDF
【DL輪読会】マルチエージェント強化学習における近年の 協調的方策学習アルゴリズムの発展
PDF
見やすいプレゼン資料の作り方 - リニューアル増量版
PostgreSQLのロール管理とその注意点(Open Source Conference 2022 Online/Osaka 発表資料)
監査要件を有するシステムに対する PostgreSQL 導入の課題と可能性
Vacuum徹底解説
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
「のどが渇いた」というユーザーに何を出す? ユーザーの「欲しい」に惑わされない、本当のインサイトを見つけるUXデザイン・UXリサーチ
【DL輪読会】A Time Series is Worth 64 Words: Long-term Forecasting with Transformers
【DL輪読会】マルチエージェント強化学習における近年の 協調的方策学習アルゴリズムの発展
見やすいプレゼン資料の作り方 - リニューアル増量版

What's hot (20)

PDF
PostgreSQLレプリケーション10周年!徹底紹介!(PostgreSQL Conference Japan 2019講演資料)
PDF
PostgreSQL 15の新機能を徹底解説
PDF
統計情報のリセットによるautovacuumへの影響について(第39回PostgreSQLアンカンファレンス@オンライン 発表資料)
PDF
PostgreSQLのリカバリ超入門(もしくはWAL、CHECKPOINT、オンラインバックアップの仕組み)
PPTX
pg_bigmで全文検索するときに気を付けたい5つのポイント(第23回PostgreSQLアンカンファレンス@オンライン 発表資料)
PDF
PostgreSQL: XID周回問題に潜む別の問題
PPTX
PostgreSQLモニタリングの基本とNTTデータが追加したモニタリング新機能(Open Source Conference 2021 Online F...
PPTX
PostgreSQLのfull_page_writesについて(第24回PostgreSQLアンカンファレンス@オンライン 発表資料)
PDF
PostgreSQLのバグとの付き合い方 ~バグの調査からコミュニティへの報告、修正パッチ投稿まで~(PostgreSQL Conference Japa...
PDF
Where狙いのキー、order by狙いのキー
PDF
PostgreSQL 15 開発最新情報
PDF
NTT DATA と PostgreSQL が挑んだ総力戦
PDF
オンライン物理バックアップの排他モードと非排他モードについて(第15回PostgreSQLアンカンファレンス@オンライン 発表資料)
PPTX
オンライン物理バックアップの排他モードと非排他モードについて ~PostgreSQLバージョン15対応版~(第34回PostgreSQLアンカンファレンス...
PDF
pg_hint_planを知る(第37回PostgreSQLアンカンファレンス@オンライン 発表資料)
PPTX
PostgreSQL 12は ここがスゴイ! ~性能改善やpluggable storage engineなどの新機能を徹底解説~ (NTTデータ テクノ...
PDF
PostgreSQL16でのロールに関する変更点(第41回PostgreSQLアンカンファレンス@オンライン 発表資料)
PPTX
PostgreSQL開発コミュニティに参加しよう!(PostgreSQL Conference Japan 2021 発表資料)
PPTX
PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~
PDF
YugabyteDBを使ってみよう - part2 -(NewSQL/分散SQLデータベースよろず勉強会 #2 発表資料)
PostgreSQLレプリケーション10周年!徹底紹介!(PostgreSQL Conference Japan 2019講演資料)
PostgreSQL 15の新機能を徹底解説
統計情報のリセットによるautovacuumへの影響について(第39回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQLのリカバリ超入門(もしくはWAL、CHECKPOINT、オンラインバックアップの仕組み)
pg_bigmで全文検索するときに気を付けたい5つのポイント(第23回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQL: XID周回問題に潜む別の問題
PostgreSQLモニタリングの基本とNTTデータが追加したモニタリング新機能(Open Source Conference 2021 Online F...
PostgreSQLのfull_page_writesについて(第24回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQLのバグとの付き合い方 ~バグの調査からコミュニティへの報告、修正パッチ投稿まで~(PostgreSQL Conference Japa...
Where狙いのキー、order by狙いのキー
PostgreSQL 15 開発最新情報
NTT DATA と PostgreSQL が挑んだ総力戦
オンライン物理バックアップの排他モードと非排他モードについて(第15回PostgreSQLアンカンファレンス@オンライン 発表資料)
オンライン物理バックアップの排他モードと非排他モードについて ~PostgreSQLバージョン15対応版~(第34回PostgreSQLアンカンファレンス...
pg_hint_planを知る(第37回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQL 12は ここがスゴイ! ~性能改善やpluggable storage engineなどの新機能を徹底解説~ (NTTデータ テクノ...
PostgreSQL16でのロールに関する変更点(第41回PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQL開発コミュニティに参加しよう!(PostgreSQL Conference Japan 2021 発表資料)
PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~
YugabyteDBを使ってみよう - part2 -(NewSQL/分散SQLデータベースよろず勉強会 #2 発表資料)
Ad

Similar to Memoizeの仕組み(第41回PostgreSQLアンカンファレンス@オンライン 発表資料) (20)

PDF
YugabyteDBの実行計画を眺める(NewSQL/分散SQLデータベースよろず勉強会 #3 発表資料)
PPT
Maatkit で MySQL チューニング
PPTX
Spmv9forpublic
PDF
PostgreSQL13 新機能紹介
PPT
HandlerSocket plugin for MySQL
PDF
TPC-DSから学ぶPostgreSQLの弱点と今後の展望
PPT
20090107 Postgre Sqlチューニング(Sql編)
PDF
第11回 配信講義 計算科学技術特論B(2022)
KEY
軽量EvernoteクライアントSmartEverにおけるアプリ高速化の工夫と課題
PDF
Pgunconf 20121212-postgeres fdw
PDF
【学習メモ#8th】12ステップで作る組込みOS自作入門
PDF
2018年度 若手技術者向け講座 大量データの扱い・ストアド・メモリ管理
PPTX
Oracle In-database-archiving ~Oracleでの論理削除~
PPTX
Linuxの2038年問題を調べてみた
PDF
Let's scale-out PostgreSQL using Citus (Japanese)
PDF
tcpdump & xtrabackup @ MySQL Casual Talks #1
PPTX
SQLチューニング入門 入門編
PDF
PostgreSQL18新機能紹介(db tech showcase 2025 発表資料)
PDF
RとStanでクラウドセットアップ時間を分析してみたら #TokyoR
PPTX
Postgres Playground で pgbench を走らせよう!(第35回PostgreSQLアンカンファレンス@オンライン 発表資料)
YugabyteDBの実行計画を眺める(NewSQL/分散SQLデータベースよろず勉強会 #3 発表資料)
Maatkit で MySQL チューニング
Spmv9forpublic
PostgreSQL13 新機能紹介
HandlerSocket plugin for MySQL
TPC-DSから学ぶPostgreSQLの弱点と今後の展望
20090107 Postgre Sqlチューニング(Sql編)
第11回 配信講義 計算科学技術特論B(2022)
軽量EvernoteクライアントSmartEverにおけるアプリ高速化の工夫と課題
Pgunconf 20121212-postgeres fdw
【学習メモ#8th】12ステップで作る組込みOS自作入門
2018年度 若手技術者向け講座 大量データの扱い・ストアド・メモリ管理
Oracle In-database-archiving ~Oracleでの論理削除~
Linuxの2038年問題を調べてみた
Let's scale-out PostgreSQL using Citus (Japanese)
tcpdump & xtrabackup @ MySQL Casual Talks #1
SQLチューニング入門 入門編
PostgreSQL18新機能紹介(db tech showcase 2025 発表資料)
RとStanでクラウドセットアップ時間を分析してみたら #TokyoR
Postgres Playground で pgbench を走らせよう!(第35回PostgreSQLアンカンファレンス@オンライン 発表資料)
Ad

More from NTT DATA Technology & Innovation (20)

PDF
開発中の新機能 Spark Declarative Pipeline に飛びついてみたが難しかった(JEDAI DAIS Recap#2 講演資料)
PDF
PGConf.dev 2025 参加レポート (JPUG総会併設セミナー2025 発表資料)
PDF
Can We Use Rust to Develop Extensions for PostgreSQL? (POSETTE: An Event for ...
PDF
つくって壊して直して学ぶ Database on Kubernetes (CloudNative Days Summer 2025 発表資料)
PDF
2025年現在のNewSQL (最強DB講義 #36 発表資料)
PDF
Java in Japan: A Journey of Community, Culture, and Global Integration (JavaO...
PDF
Unveiling the Hidden Layers of Java Class Files: Beyond Bytecode (Devnexus 2025)
PDF
論理レプリケーションのアーキテクチャ (第52回 PostgreSQLアンカンファレンス@オンライン 発表資料)
PDF
実はアナタの身近にある!? Linux のチェックポイント/レストア機能 (NTT Tech Conference 2025 発表資料)
PDF
Apache Sparkに対するKubernetesのNUMAノードを意識したリソース割り当ての性能効果 (Open Source Conference ...
PDF
PostgreSQL最新動向 ~カラムナストアから生成AI連携まで~ (Open Source Conference 2025 Tokyo/Spring ...
PDF
pgbenchのスレッドとクライアント (第51回 PostgreSQLアンカンファレンス@オンライン 発表資料)
PDF
PostgreSQLのgitレポジトリから見える2024年の開発状況 (第51回 PostgreSQLアンカンファレンス@オンライン 発表資料)
PDF
ストリーム処理はデータを失うから怖い?それ、何とかできますよ! 〜Apahe Kafkaを用いたストリーム処理における送達保証〜 (Open Source...
PDF
生成AI時代のPostgreSQLハイブリッド検索 (第50回PostgreSQLアンカンファレンス@オンライン 発表資料)
PDF
DAIS2024参加報告 ~Spark中心にしらべてみた~ (JEDAI DAIS Recap 講演資料)
PDF
PostgreSQLのHTAP適応について考える (PostgreSQL Conference Japan 2024 講演資料)
PDF
静かに変わってきたクラスファイルを詳細に調べて楽しむ(JJUG CCC 2024 Fall講演資料)
PDF
Gartnerも注目するグリーンソフトウェアの実現に向けて (Green Software Foundation Global Summit 2024 T...
PDF
パーティションのATTACH時の注意ポイント (第49回PostgreSQLアンカンファレンス@東京 発表資料)
開発中の新機能 Spark Declarative Pipeline に飛びついてみたが難しかった(JEDAI DAIS Recap#2 講演資料)
PGConf.dev 2025 参加レポート (JPUG総会併設セミナー2025 発表資料)
Can We Use Rust to Develop Extensions for PostgreSQL? (POSETTE: An Event for ...
つくって壊して直して学ぶ Database on Kubernetes (CloudNative Days Summer 2025 発表資料)
2025年現在のNewSQL (最強DB講義 #36 発表資料)
Java in Japan: A Journey of Community, Culture, and Global Integration (JavaO...
Unveiling the Hidden Layers of Java Class Files: Beyond Bytecode (Devnexus 2025)
論理レプリケーションのアーキテクチャ (第52回 PostgreSQLアンカンファレンス@オンライン 発表資料)
実はアナタの身近にある!? Linux のチェックポイント/レストア機能 (NTT Tech Conference 2025 発表資料)
Apache Sparkに対するKubernetesのNUMAノードを意識したリソース割り当ての性能効果 (Open Source Conference ...
PostgreSQL最新動向 ~カラムナストアから生成AI連携まで~ (Open Source Conference 2025 Tokyo/Spring ...
pgbenchのスレッドとクライアント (第51回 PostgreSQLアンカンファレンス@オンライン 発表資料)
PostgreSQLのgitレポジトリから見える2024年の開発状況 (第51回 PostgreSQLアンカンファレンス@オンライン 発表資料)
ストリーム処理はデータを失うから怖い?それ、何とかできますよ! 〜Apahe Kafkaを用いたストリーム処理における送達保証〜 (Open Source...
生成AI時代のPostgreSQLハイブリッド検索 (第50回PostgreSQLアンカンファレンス@オンライン 発表資料)
DAIS2024参加報告 ~Spark中心にしらべてみた~ (JEDAI DAIS Recap 講演資料)
PostgreSQLのHTAP適応について考える (PostgreSQL Conference Japan 2024 講演資料)
静かに変わってきたクラスファイルを詳細に調べて楽しむ(JJUG CCC 2024 Fall講演資料)
Gartnerも注目するグリーンソフトウェアの実現に向けて (Green Software Foundation Global Summit 2024 T...
パーティションのATTACH時の注意ポイント (第49回PostgreSQLアンカンファレンス@東京 発表資料)

Memoizeの仕組み(第41回PostgreSQLアンカンファレンス@オンライン 発表資料)

  • 1. © 2023 NTT DATA Corporation Memoizeの仕組み 41回 PostgreSQLアンカンファレンス 2023/4/24 NTTデータ 技術開発本部 笠原辰仁
  • 2. © 2023 NTT DATA Corporation 2 はじめに • 本日は、PostgreSQLのMemoizeという機能について説明します。最近の PostgreSQL(v14~)をお使いの方でしたら、既に知っていて活用している、あるいは知らない うちにお世話になっているかもしれません。 • 今日はそのMemoizeの仕組みや効果について一緒に見ていこうと思います。 • 本資料は公開します。
  • 3. © 2023 NTT DATA Corporation 3 Memoizeについて Memoizeは当初 Result Cache という名称で開発が進められていた機能。2021年4月にCommitされました。 • Add Result Cache executor node (take 2) • https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=9eacee2e62d89cab7b004f97 c206c4fba4f1d745 その後にもう少し良い名前はないものか、ということで Memoize に改称。 • Change the name of the Result Cache node to Memoize • https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=83f4fcc65503c5d4e5d5eefc8 e7a70d3c9a6496f ちなみにMemoize(Memoization:メモ化)は 「プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブ ルーチン(関数)の呼び出し毎の再計算を防ぐ手法である。」 (https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E5%8C%96 より引用) のとおり、PostgreSQLの方言やDBMSに特化した機能・演算ではないです。
  • 4. © 2023 NTT DATA Corporation 4 Memoizeの狙い Nested Loop Joinにおいて、一方のテーブルの結合キーのカーディナリティが低い場合、他方のテーブルの同じ行に 対して何度も読み取りが繰り返されることになります。これ自体は正常な動作ですが、同じデータへ通常のパス (インデックスのルート -> 中間ノード -> リーフ -> ヒープ…)を何度も辿るのは冗長です。 col1 ・・・ 1 1 2 2 col1 ・・・ 1 2 3 4 結合キーのカーディナリティ低 結合キーのカーディナリティ高 SELECT … FROM t1 JOIN t2 ON t1.col1 = t2.col1 WHERE … t1 (Outer) t2 (Inner) 特定の行のみ何度もアクセスされる。 インデックススキャンかつ共有バッファに 乗り切るサイズだとしても、チリも積も ればそれなりのオーバーヘッドになる。
  • 5. © 2023 NTT DATA Corporation 5 Memoizeの狙い そこで、Memoize(メモ化)を利用し結合対象のレコードをバックエンドプロセスのローカルメモリにキャッシュすることで、 Nested Loop Joinの性能向上が可能となりました。この機能は特にユーザが意識せずとも自動で利用されます。 (enable_memoize = on/off で使用有無を制御可能)。プランナがコスト推定を行い、利用に適切だと判断さ れると使われます。Nested Loopの時に必ず利用されるわけではありません。 col1 ・・・ 1 1 2 2 col1 ・・・ 1 2 3 4 結合キーのカーディナリティ低 結合キーのカーディナリティ高 SELECT … FROM t1 JOIN t2 ON t1.col1 = t2.col1 WHERE … t1 (Outer) t2 (Inner) col1 ・・・ 1 2 Memoizeされた キャッシュ Memoize Memoizeされたデータを使うことでt2へのアクセスを高速化できる
  • 6. © 2023 NTT DATA Corporation 6 Memoizeの仕組み Memoizeはメモ化する対象のテーブルから行を読み取りつつ逐次実施されます。メモ化されたデータの実体はハッ シュテーブルとハッシュテーブルのエントリキー値のリスト(dlist)です。エントリのキー値はLRUで管理されます。ハッシュ テーブルの上限に達した場合は古いものを除去します。つまりディスクに書き込み(Spill Out)はしません。 col1 ・・・ ・・・ 40 ZZZ ・・・ t2 Key:90 Key:40 Key:10 Key:40 Key complete tuple 0x11 t {10, AAA} 0x12 t {90, ZZZ} 0x13 f {40, HHH} ハッシュテーブルは「(double) work_mem * hash_mem_multiplier * 1024.0」を上限としている。 ハッシュテーブルへの格納時に上限を超える場合はエントリーキーのLRUリストを元に古いエントリ内 の全データを削除 {10, BBB} {40, BBB} {40, ZZZ} エントリーキーのリスト(LRU) ハッシュテーブル 結合キー(例はcol1)の値をエントリー キーから検索。あればリストの末尾に 移動。なければリストの末尾に追加 リストに追加したキー値含むタプルデータを ハッシュテーブルへ格納。もし対象テーブルに これ以上該当のキー値がない場合は completeフラグを真にする。
  • 7. © 2023 NTT DATA Corporation 7 Memoizeの仕組み MEMO_CACHE_LOOKUP (開始ステート) MEMO_FILLING_CACHE (ハッシュテーブルへデータ蓄積するステート) MEMO_CACHE_FETCH_NEXT_TUPLE (ハッシュテーブル内のキャッシュを返すステート) MEMO_END_OF_SCAN (終了ステート) キーリストにエントリがある && completeは真? ハッシュテーブルのデータはもうない? 該当のキー値を持つレコードは もうない? 新規のデータをハッシュテーブルへ 格納可能?(必要に応じてハッシュテーブルの 古いエントリ削除しEvictions++) MEMO_CACHE_BYPASS_MODE (ハッシュテーブルを使用しないステート) Memoize内部の処理はOuterのテーブルから参照した1レコードを対象に、Innerのテーブルから結合対象となるレコードを全て検 索する過程で以下のフローをたどります。ステートマシンとなっており、「MEMO_CACHE_LOOKUP」から始まり 「MEMO_END_OF_SCAN」で1巡です。これをOuterの結合対象レコードの数だけ行います。なお、ハッシュテーブルがいっぱいにな りハッシュテーブルのサイズ削減時に格納しようとしているキーが削減対象になる場合はハッシュテーブルを使用しないモードになります。 該当のキー値を持つレコードは もうない? Yes (cache hit++) Yes Yes Yes (ハッシュテーブルの complete flagを trueに) Yes No No No (次のデータ返却の準備) No (cache miss++) No (格納しようとしたキーがエントリ削除対象の場合 overflow++)
  • 8. © 2023 NTT DATA Corporation 8 実行計画でMemoizeの効果を見る c1 c2 1 2 ・・・ 100 1 2 ・・・ 100 結合キーのカーディナリティ低 t1 (Outer) Outer(駆動表)はc1列に1~100が5000回繰り返される値を持つ50万件のテーブル。 Innerはc1列に1~500000を持つ50万件のテーブル。 「SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1;」 を実行し、c1列を結合キーとしてNested Loopした場合の性能差を見てみます。 c1 c2 1 2 ・・・ 500000 t2 (Inner) 結合キーのカーディナリティ高 Outer(駆動表)のt1は全行が検索対象。t2は一部のみ対象。 従来(-v13)であればt2へのIndexScanが50万回走るが、 Memoizeを使うことでどの程度高速化するか?
  • 9. © 2023 NTT DATA Corporation 9 【参考】 サンプル -- 開発中のPostgreSQLのHEAD (2023/4月中旬)で実験 -- Unloggedは単にWALを書きたくないだけ CREATE UNLOGGED TABLE t1 (c1 int, c2 text); CREATE UNLOGGED TABLE t2 (c1 int, c2 text); -- t1はカーディナリティ低、t2はカーディナリティ高 INSERT INTO t1 SELECT generate_series(1,100), md5(i::text) FROM generate_series(1,5000)i; INSERT INTO t2 SELECT i, md5(i::text) FROM generate_series(1,500000)i; CREATE INDEX t1_c1_idx ON t1 (c1); CREATE INDEX t2_c1_idx ON t2 (c1); ANALYZE t1, t2; -- 以下はなるべく実行計画をMemoizeのon/offで揃えるため SET enable_hashjoin TO off; SET enable_mergejoin TO off; SET enable_bitmapscan TO off; SET max_parallel_workers_per_gather = 0;
  • 10. © 2023 NTT DATA Corporation 10 実行計画でMemoizeの効果を見る SET enable_memoize TO off; =# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2 ; QUERY PLAN --------------------------------------------------------------------------------------------- Nested Loop (actual time=0.022..898.447 rows=500000 loops=1) -> Seq Scan on t1 (actual time=0.008..45.665 rows=500000 loops=1) -> Index Scan using t2_c1_idx on t2 (actual time=0.001..0.001 rows=1 loops=500000) Index Cond: (c1 = t1.c1) Planning Time: 0.072 ms Execution Time: 928.049 ms (6 rows) 今回のサンプルを使った環境では、Memoizeを使わない場合、928ミリ秒でした。 想定通り、Outer(駆動表)をSeqScanし、50万件を対象にInnerのt2に50万回の IndexScanが実施されている(loops=500000)
  • 11. © 2023 NTT DATA Corporation 11 実行計画でMemoizeの効果を見る SET enable_memoize TO on; =# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1; QUERY PLAN ------------------------------------------------------------------------------------------ Nested Loop (actual time=0.028..294.401 rows=500000 loops=1) -> Seq Scan on t1 (actual time=0.009..39.044 rows=500000 loops=1) -> Memoize (actual time=0.000..0.000 rows=1 loops=500000) Cache Key: t1.c1 Cache Mode: logical Hits: 499900 Misses: 100 Evictions: 0 Overflows: 0 Memory Usage: 14kB -> Index Scan using t2_c1_idx on t2 (actual time=0.001..0.001 rows=1 loops=100) Index Cond: (c1 = t1.c1) Planning Time: 0.093 ms Execution Time: 311.941 ms (10 rows) Memoizeを使う場合、311ミリ秒となり、おおよそ3倍近く高速化しました。 Outer(駆動表)の最初の100行(c1が1~100)はハッシュテーブルにないので、 Missesは100。IndexScanも100回実施(loops=100)。それ以降はハッシュ テーブルでヒットするので、Hitsが500000 – 100 = 499900となっている。
  • 12. © 2023 NTT DATA Corporation 12 Memoizeが不利になる場合を作ってみる c1 c2 1 2 ・・・ 100 1 2 ・・・ 100 結合キーのカーディナリティ低 t1 (Outer) 先ほどのサンプルテーブルのInner側にc1=100のレコードを1000件追加します。ついでにc2列を長大にし、 あえてハッシュテーブルに収まらない状態を作ってみます。 ついでにwork_mem=‘64kB’としてハッシュテーブルを小さくしてみましょう。 c1 c2 1 2 ・・・ 500000 t2 (Inner) 結合キーのカーディナリティ高 col1 ・・・ 1 2 ハッシュテーブル 「c1:100, c2:1.9kBくらいのテキスト」 を1000件追加してみる。 ここに収まりきらないケースでMemoizeが有効になった場合はどうなるか?
  • 13. © 2023 NTT DATA Corporation 13 【参考】 サンプル2 -- Innerのt2へ1000件追加 INSERT INTO t2 SELECT 100, repeat(md5(i::text), 60) FROM generate_series(1,1000)i; -- work_memを64kB (最低値)へ SET work_mem TO ‘64kB’;
  • 14. © 2023 NTT DATA Corporation 14 再び実行計画でMemoizeの効果を見る SET enable_memoize TO off; =# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1; QUERY PLAN ---------------------------------------------------------------------------------------- Nested Loop (actual time=0.024..1855.766 rows=5500000 loops=1) -> Seq Scan on t1 (actual time=0.008..34.877 rows=500000 loops=1) -> Index Scan using t2_c1_idx on t2 (actual time=0.001..0.003 rows=11 loops=500000) Index Cond: (c1 = t1.c1) Planning Time: 0.135 ms Execution Time: 2009.872 ms (6 rows) (6 rows) Memoizeを使わない場合、2009ミリ秒でした。t2に結合対象が1000件増え、結果の行数が50万から5000 * 1000 (c1=100がOuterに5000件ある) 増えて550万件になったので実行時間が増えてます。
  • 15. © 2023 NTT DATA Corporation 15 再び実行計画でMemoizeの効果を見る SET enable_memoize TO on; =# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1; QUERY PLAN ------------------------------------------------------------------------------------------ Nested Loop (actual time=0.018..3822.641 rows=5500000 loops=1) -> Seq Scan on t1 (actual time=0.005..52.471 rows=500000 loops=1) -> Memoize (actual time=0.002..0.006 rows=11 loops=500000) Cache Key: t1.c1 Cache Mode: logical Hits: 0 Misses: 500000 Evictions: 499960 Overflows: 5000 Memory Usage: 130kB -> Index Scan using t2_c1_idx on t2 (actual time=0.001..0.004 rows=11 loops=500000) Index Cond: (c1 = t1.c1) Planning Time: 0.193 ms Execution Time: 4040.609 ms (10 rows) Memoizeを使う場合、4040ミリ秒となり、おおよそ2倍近く低速化しました。 c1=100の結合時にハッシュテーブルへ格納できないためOverflow(ハッシュテーブルに収まりきら なかった回数)がc1=100の件数、つまり5000回発生。 今回はc1が1~100の巡回において1~99でハッシュテーブルへ格納、100で全てのエントリがクリ ア、また1からその繰り返し・・となっており、Memoizeが全く機能できなかった。そのためMissesが 50万回となっている。Evictionsはハッシュテーブルから除去されたエントリ数の総計。
  • 16. © 2023 NTT DATA Corporation 16 再び実行計画でMemoizeの効果を見る SET work_mem TO '4MB’; =# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1; QUERY PLAN ------------------------------------------------------------------------------------------ Nested Loop (actual time=0.027..1107.136 rows=5500000 loops=1) -> Seq Scan on t1 (actual time=0.009..45.169 rows=500000 loops=1) -> Memoize (actual time=0.000..0.001 rows=11 loops=500000) Cache Key: t1.c1 Cache Mode: logical Hits: 499900 Misses: 100 Evictions: 0 Overflows: 0 Memory Usage: 1928kB -> Index Scan using t2_c1_idx on t2 (actual time=0.002..0.005 rows=11 loops=100) Index Cond: (c1 = t1.c1) Planning Time: 0.099 ms Execution Time: 1313.725 ms (10 rows)(10 rows) work_memを十分な値に設定した場合は1313ミリ秒となり、Memoizeを使わないケースよりも高速化しました。
  • 17. © 2023 NTT DATA Corporation 17 【おまけ】ハッシュテーブルからあふれるケースでもマシな場合 =# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1; QUERY PLAN --------------------------------------------------------------------------------------------- Nested Loop (actual time=0.031..1487.835 rows=5500000 loops=1) -> Index Scan using t1_c1_idx on t1 (actual time=0.017..109.438 rows=500000 loops=1) -> Memoize (actual time=0.000..0.002 rows=11 loops=500000) Cache Key: t1.c1 Cache Mode: logical Hits: 494901 Misses: 5099 Evictions: 5099 Overflows: 5000 Memory Usage: 130kB -> Index Scan using t2_c1_idx on t2 (actual time=0.002..0.103 rows=982 loops=5099) Index Cond: (c1 = t1.c1) Planning Time: 0.096 ms Execution Time: 1602.543 ms (10 rows) Outerのテーブルスキャン順序を1,2,3 … 99,100,1,2,3…ではなく、1,1,1,2,2,…99,100,100のようにして キャッシュのEvictionが起こりにくいようにすると、性能低下が限定的となります。 Overflowは変わらないものの、c1=1~99をOuterから読みだしているフェーズでは99回の Missesとなるが残りはキャッシュに救われるのでHitsが多くなる。c1=100のフェーズでは毎回 OverflowとEvictionsが発生し、Hitしない。
  • 18. © 2023 NTT DATA Corporation 18 【参考】 PostgreSQL16からのMemoizeの適用領域拡大 =# EXPLAIN (ANALYZE on, COSTS off) SELECT * FROM t1, (SELECT * FROM t2 UNION ALL SELECT * FROM t2) t3 WHERE t1.c1 = t3.c1; QUERY PLAN ----------------------------------------------------------------------------------------------------- Nested Loop (actual time=0.019..346.989 rows=1000000 loops=1) -> Seq Scan on t1 (actual time=0.005..37.872 rows=500000 loops=1) -> Memoize (actual time=0.000..0.000 rows=2 loops=500000) Cache Key: t1.c1 Cache Mode: logical Hits: 499900 Misses: 100 Evictions: 0 Overflows: 0 Memory Usage: 21kB -> Append (actual time=0.001..0.003 rows=2 loops=100) -> Index Scan using t2_c1_idx on t2 (actual time=0.001..0.001 rows=1 loops=100) Index Cond: (c1 = t1.c1) -> Index Scan using t2_c1_idx on t2 t2_1 (actual time=0.001..0.001 rows=1 loops=100) Index Cond: (c1 = t1.c1) Planning Time: 0.242 ms Execution Time: 383.323 ms (13 rows) InnerがUNION ALL経由のAppendのノードだった場合でも、v16からはMemoizeを利用できるようになりました。 (パーティションテーブル経由のAppendノードに対してはv15でもMemoizeを利用できます)
  • 19. © 2023 NTT DATA Corporation 19 【参考】 Cache Mode 通常、Memoizeしたデータの引き当てはCache-Keyによるハッシュ等号演算子で行われ、この場合はLogicalモードでの比較 となります。一方、比較演算やfloatの+0.0/-0.0のようなハッシュ等号演算では同じと評価されるケースではバイナリモードでの 比較が行われます。 postgres=# EXPLAIN SELECT count(*) FROM t1, t2 WHERE t1.c1 = t2.c2; QUERY PLAN ----------------------------------------------------------------------------------------- Aggregate (cost=420.61..420.62 rows=1 width=8) -> Nested Loop (cost=0.30..395.61 rows=10000 width=0) -> Seq Scan on t2 (cost=0.00..145.00 rows=10000 width=4) -> Memoize (cost=0.30..0.32 rows=1 width=4) Cache Key: t2.c2 Cache Mode: logical -> Index Only Scan using t1_pkey on t1 (cost=0.29..0.31 rows=1 width=4) Index Cond: (c1 = t2.c2) postgres=# EXPLAIN SELECT count(*) FROM t1, t2 WHERE t1.c1 < t2.c2; QUERY PLAN --------------------------------------------------------------------------------------------- Aggregate (cost=500337.24..500337.25 rows=1 width=8) -> Nested Loop (cost=0.30..417003.91 rows=33333333 width=0) -> Seq Scan on t2 (cost=0.00..145.00 rows=10000 width=4) -> Memoize (cost=0.30..58.63 rows=3333 width=4) Cache Key: t2.c2 Cache Mode: binary -> Index Only Scan using t1_pkey on t1 (cost=0.29..58.62 rows=3333 width=4) Index Cond: (c1 < t2.c2)
  • 20. © 2023 NTT DATA Corporation 20 まとめ Memoizeの目的、コンセプト、仕組み、実行計画を通した実際の作用を見てました。 ハマれば、いつものクエリが自然に高速化されると思います。PostgreSQLをバージョンアップしたら、 高速化されたんだけど?という方は実行計画を見るとMemoizeが機能しているかもしれません。 ただし場合によっては性能低下の要因となる可能性がありますので、仕組みを踏まえて使いこな してみてください。
  • 21. © 2023 NTT DATA Corporation その他、記載されている会社名、商品名、又はサービス名は、 各社の登録商標又は商標です。