Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
PostgreSQLのVisibilityの仕組み
Search
Shinya Kato
June 17, 2025
2
420
PostgreSQLのVisibilityの仕組み
PostgreSQLのVisibilityの仕組み
(第53回 PostgreSQLアンカンファレンス@オンライン 発表資料)
2025年6月24日(火)
NTT OSSセンタ
加藤 慎也
Shinya Kato
June 17, 2025
Tweet
Share
More Decks by Shinya Kato
See All by Shinya Kato
pg_bigmをRustで実装する(第50回PostgreSQLアンカンファレンス@オンライン 発表資料)
shinyakato_
0
230
多次元ストリーミング時系列データの効率的なモチーフモニタリングアルゴリズム / Monitoring Motif on Multi-dimensional Streaming Time-series, presented at DPSWS 2019
shinyakato_
0
28
Discord Monitoring for Streaming Time-series, presented at DEXA 2019
shinyakato_
0
27
ストリーミング時系列データの効率的なディスコードモニタリングアルゴリズム / Discord Monitoringfor Streaming Time-series, presented at DEIM 2019
shinyakato_
0
25
Monitoring Range Motif on Streaming Time-Series, presented at DEXA 2018
shinyakato_
0
17
ストリーミング時系列データの効率的なモチーフモニタリングアルゴリズム / Monitoring Range Motif on Streaming Time-Series, presented at DICOMO 2018
shinyakato_
0
140
Featured
See All Featured
Building an army of robots
kneath
306
45k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
667
120k
Making Projects Easy
brettharned
116
6.3k
StorybookのUI Testing Handbookを読んだ
zakiyama
30
5.8k
Optimizing for Happiness
mojombo
379
70k
Rebuilding a faster, lazier Slack
samanthasiow
81
9k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
331
22k
The Art of Programming - Codeland 2020
erikaheidi
54
13k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
15
1.5k
Building a Scalable Design System with Sketch
lauravandoore
462
33k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
53
2.8k
How STYLIGHT went responsive
nonsquared
100
5.6k
Transcript
PostgreSQLのVisibilityの仕組み 2025/6/24 第53回 PostgreSQLアンカンファレンス@オンライン NTT OSSセンタ 加藤 慎也
1 © NTT CORPORATION 2025 自己紹介 • 加藤 慎也(かとう しんや)
• NTT OSSセンタ • @ShinyaKato_
2 © NTT CORPORATION 2025 発表について • 発表資料:https://speakerdeck.com/shinyakato_ • バージョン:PostgreSQL
18 Beta 1
3 © NTT CORPORATION 2025 Visibility • 日本語で「可視性」 • 各トランザクションからテーブル内の行(タプル)が見えるか
どうかを制御する仕組み • PostgreSQLはMVCC(Multi-version Concurrency Control) を使用しており、行(タプル)には複数のバージョンがある
4 © NTT CORPORATION 2025 ページ単位の可視性判定
5 © NTT CORPORATION 2025 • PostgreSQLのテーブルの 実態はファイル • ファイルは8kBのページから
構成 PostgreSQLのテーブル ︙ 8kB
6 © NTT CORPORATION 2025 • あるページがどのTxからも 可視であることがわかれば、 タプルごとに可視判定しなく て済む
• その情報がページヘッダに 書かれている PostgreSQLのテーブル 8kB
7 © NTT CORPORATION 2025 ページヘッダ • ページヘッダはPageHeaderData構造体に格納 • src/include/storage/bufpage.h
• レイアウト フィールド 型 長さ 説明 pd_lsn PageXLogRecPtr 8バイト LSN: このページへの最終変更に対応するWALレコードの最後のバイトの次のバ イト pd_checksum uint16 2バイト ページチェックサム pd_flags uint16 2バイト フラグビット pd_lower LocationIndex 2バイト 空き領域の始まりに対するオフセット pd_upper LocationIndex 2バイト 空き領域の終わりに対するオフセット pd_special LocationIndex 2バイト 特別な空間の始まりに対するオフセット pd_pagesize_version uint16 2バイト ページサイズおよびレイアウトのバージョン番号の情報 pd_prune_xid TransactionId 4バイト ページ上でもっとも古い切り詰められていないXMAX。存在しなければゼロ。
8 © NTT CORPORATION 2025 pd_flags • 以下のフラグがある • PD_HAS_FREE_LINES:未使用の行ポインタがあるか
• PD_PAGE_FULL:新しいタプルに十分な空き領域がないか • PD_ALL_VISIBLE:すべてのタプルが全Txから可視か • PD_VALID_FLAG_BITS:有効なpd_flagsビットの論理和
9 © NTT CORPORATION 2025 PD_ALL_VISIBLE • static inline void
PageSetAllVisible(Page page)により設定 • src/include/storage/bufpage.h • VACUUM、COPY FREEZE実行時に呼び出される
10 © NTT CORPORATION 2025 実行例 =# CREATE TABLE t
(id INT PRIMARY KEY, data TEXT); CREATE TABLE =# INSERT INTO t SELECT generate_series(1,300), md5(clock_timestamp()::TEXT); INSERT 0 300 =# SELECT * FROM t; id | data -----+---------------------------------- 1 | 302d85dfa64368cf33b2f0ec7df82097 2 | ac1c2d98a3d4548c367b2d981bbb7637 ... 299 | 0e03406007e1e5863d4526e15e3d809b 300 | 43d738e2ce6e3ccd3a7c74a9e6a3a260 (300 rows) 適当にテーブルを作成して、 データを挿入
11 © NTT CORPORATION 2025 実行例 =# CREATE EXTENSION pageinspect;
CREATE EXTENSION =# SELECT * FROM page_header(get_raw_page('t', 0)); lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid ------------+----------+-------+-------+-------+---------+----------+---------+----------- 0/D28B7410 | 0 | 0 | 504 | 512 | 8192 | 8192 | 4 | 0 (1 row) =# SELECT * FROM page_header(get_raw_page('t', 1)); lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid ------------+----------+-------+-------+-------+---------+----------+---------+----------- 0/D28BBF40 | 0 | 0 | 504 | 512 | 8192 | 8192 | 4 | 0 (1 row) =# SELECT * FROM page_header(get_raw_page('t', 2)); lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid ------------+----------+-------+-------+-------+---------+----------+---------+----------- 0/D28BE4F0 | 0 | 0 | 264 | 4352 | 8192 | 8192 | 4 | 0 (1 row) pageinspectでページヘッダを確認する。 pg_visibilityでもよかったかも。 フラグは立っていない。 フラグは立っていない。 フラグは立っていない。
12 © NTT CORPORATION 2025 実行例 =# VACUUM t; VACUUM
=# SELECT * FROM page_header(get_raw_page('t', 0)); lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid ------------+----------+-------+-------+-------+---------+----------+---------+----------- 0/D28B7410 | 0 | 4 | 504 | 512 | 8192 | 8192 | 4 | 0 (1 row) =# SELECT * FROM page_header(get_raw_page('t', 1)); lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid ------------+----------+-------+-------+-------+---------+----------+---------+----------- 0/D28BBF40 | 0 | 4 | 504 | 512 | 8192 | 8192 | 4 | 0 (1 row) =# SELECT * FROM page_header(get_raw_page('t', 2)); lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid ------------+----------+-------+-------+-------+---------+----------+---------+----------- 0/D28BE4F0 | 0 | 4 | 264 | 4352 | 8192 | 8192 | 4 | 0 (1 row) PD_ALL_VISIBLEのフラグが立った! PD_ALL_VISIBLEのフラグが立った! PD_ALL_VISIBLEのフラグが立った! VACUUMしてみる。
13 © NTT CORPORATION 2025 実行例 =# UPDATE t SET
data = 'hoge' WHERE id = 1; UPDATE 1 =# SELECT * FROM page_header(get_raw_page('t', 0)); lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid ------------+----------+-------+-------+-------+---------+----------+---------+----------- 0/D2929418 | 0 | 2 | 504 | 512 | 8192 | 8192 | 4 | 91108 (1 row) 適当にUPDATEしてみる。 PD_ALL_VISIBLEのフラグがおりた!
14 © NTT CORPORATION 2025 コールスタック heap_prepare_pagescan all_visible = PageIsAllVisible(page)
&& !snapshot->takenDuringRecovery; を渡す page_collect_tuples HeapScanDescにタプルを追加 HeapTupleSatisfiesVisibility all_visible == true all_visible == false HeapTupleSatisfiesMVCC HeapTupleSatisfiesNonVacuumable HeapTupleSatisfiesSelf HeapTupleSatisfiesAny HeapTupleSatisfiesToast HeapTupleSatisfiesDirty HeapTupleSatisfiesHistoricMVCC SnapshotTypeによって 呼び出す関数が変わる
15 © NTT CORPORATION 2025 タプル単位の可視性判定
16 © NTT CORPORATION 2025 可視判定に必要なもの • タプルデータ • CLOG(Commit
LOG) • スナップショット
17 © NTT CORPORATION 2025 タプルデータ • タプルデータには以下が格納されている • 固定サイズのヘッダ
• NULLビットマップ(オプション) • OID (オプション) • 実際のユーザデータ
18 © NTT CORPORATION 2025 タプルデータ - 固定サイズのヘッダ • ヘッダはHeapTupleHeaderData構造体に格納
• src/include/access/htup_details.h • レイアウト フィールド 型 長さ 説明 t_xmin TransactionId 4バイト 挿入XIDスタンプ t_xmax TransactionId 4バイト 削除XIDスタンプ t_cid CommandId 4バイト 挿入、削除の両方または片方のCIDスタンプ(t_xvacと共有) t_xvac TransactionId 4バイト 行バージョンを移すVACUUM操作用XID t_ctid ItemPointerData 6バイト この行または最新バージョンの行の現在のTID t_infomask2 uint16 2バイト 属性の数と各種フラグビット t_infomask uint16 2バイト 様々なフラグビット t_hoff uint8 1バイト ユーザデータに対するオフセット
19 © NTT CORPORATION 2025 タプルデータ – 可視性判定に重要なもの • トランザクション間の可視判定に使用
• xmin:タプルを挿入したTxのXID • xmax:タプルを削除したTxのXID • トランザクション内の可視判定に使用 • cid:Tx内で挿入/削除/更新などに割り当てられるコマンドID • 挿入の場合cmin、削除の場合cmaxと呼ばれる • 1つのタプルに対して挿入と削除が行われた場合は Combo Command IDを保存する
20 © NTT CORPORATION 2025 タプルデータ – 可視性判定に重要なもの • ヒントビット
• 検索時間節約のためのCLOGから検索結果や、その他様々な情報を保存 • 以下のようなフラグがある(他にも色々ある) › HEAP_XMIN_COMMITTED › HEAP_XMIN_INVALID › HEAP_XMAX_COMMITTED › HEAP_XMAX_INVALID › HEAP_COMBOCID › src/include/access/htup_details.h
21 © NTT CORPORATION 2025 CLOG • Txのコミットステータスを追跡するための共有メモリ上の構造 • Txごとに2bitが割り当てられ、以下のステータスを保持
• TRANSACTION_STATUS_IN_PROGRESS • TRANSACTION_STATUS_COMMITTED • TRANSACTION_STATUS_ABORTED • TRANSACTION_STATUS_SUB_COMMITTED • src/include/access/clog.h • $PGDATA/pg_xactに永続化
22 © NTT CORPORATION 2025 スナップショット • スナップショットはSnapshotData構造体に格納 • src/include/utils/snapshot.h
• 様々な情報が格納されているが、以下の情報が重要 • xmin:実行中のTxで最小のXID • xmax:完了したTxで最大のXID • xip:実行中のTxのXIDの配列(transaction in progressの略?) • curcid:スナップショットを作成したコマンドのID
23 © NTT CORPORATION 2025 スナップショットタイプ • スナップショットタイプはSnapshotType列挙型で定義 • src/include/utils/snapshot.h
• 通常のMVCCスナップショット • SNAPSHOT_MVCC • 特殊な意味をもつスナップショット • SNAPSHOT_SELF、SNAPSHOT_ANY、SNAPSHOT_TOAST、 SNAPSHOT_DIRTY、SNAPSHOT_HISTORIC_MVCC、 SNAPSHOT_NON_VACUUMABLE
24 © NTT CORPORATION 2025 スナップショットの取得タイミング • READ COMMITTED •
SQLごとにスナップショットを取得 • SQL内で可視判定基準が同じ • REPEATABLE READ/SERIALIZABLE • Txごとにスナップショットを取得 • Tx内で可視判定基準が同じ
25 © NTT CORPORATION 2025 HeapTupleSatisfiesMVCC
26 © NTT CORPORATION 2025 4パターンにわけて説明 1. 挿入が無効なパターン 2. 挿入が有効なパターン
3. 削除が無効なパターン 4. 削除が有効なパターン
27 © NTT CORPORATION 2025 挿入が無効なパターン(1/5) xmin cmin status 100
Abort 103 Commit 110 Commit 104 10 テーブル xmin xmax xip curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) ヒントビット or CLOGから調べる タプルヘッダに保存 Tx分離レベルによって 取得タイミングが異なる
28 © NTT CORPORATION 2025 挿入が無効なパターン(2/5) xmin cmin status 100
Abort 103 Commit 110 Commit 104 10 テーブル xmin xmax xip curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) • xmin < snapshot->xminなので Tx100はスナップショット取得時点で完了 • ヒントビット or CLOGから Tx100はAbortしたとわかるので無効
29 © NTT CORPORATION 2025 挿入が無効なパターン(3/5) xmin cmin status 100
Abort 103 Commit 110 Commit 104 10 テーブル xmin xmax xip curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) • xmin in snapshot->xipなので Tx103はスナップショット取得時点で実行中 • 挿入は未Commitであるので無効 • ヒントビット or CLOGがCommitであっても、 スナップショット取得時点の状態で判断
30 © NTT CORPORATION 2025 挿入が無効なパターン(4/5) xmin cmin status 100
Abort 103 Commit 110 Commit 104 10 テーブル xmin xmax xip curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) • xmin > snapshot->xmaxなので Tx110はスナップショット取得時点で未実行 • 挿入は将来実行されるので無効 • 例) 1. Tx104がスナップショット取得&SELECT開始 Tx110はまだ開始されていないので スナップショットには含まれない 2. Tx110が開始してタプル挿入、Commit 3. Tx104のSELECTがTx110が挿入したタプルを 可視判定
31 © NTT CORPORATION 2025 挿入が無効なパターン(5/5) xmin cmin status 100
Abort 103 Commit 110 Commit 104 10 テーブル xmin xmax xip curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) • xmin == xidなので自Txが挿入 • cmin > snapshot->curcidなので スナップショット取得時点で未実行 • 挿入は自TXが将来実行するので無効 • 例) 1. Tx104(curcid=5)がスナップショット取得& カーソル作成 2. Tx104(curcid=10)がタプル挿入 3. Tx104がカーソルをFETCHし、 curcid=10が挿入したタプルを可視判定
32 © NTT CORPORATION 2025 挿入が有効なパターン(1/3) xmin cmin status 100
Commit 102 Commit 104 3 テーブル xmin xmax xip curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) • xmin < snapshot->xminなので Tx100はスナップショット取得時点で完了 • ヒントビット or CLOGから Tx100はCommitしたとわかるので有効
33 © NTT CORPORATION 2025 挿入が有効なパターン(2/3) xmin cmin status 100
Commit 102 Commit 104 3 テーブル xmin xmax xip curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) • xmin not in snapshot->xipなので Tx102はスナップショット取得時点で完了 • ヒントビット or CLOGから Tx102はCommitしたとわかるので有効
34 © NTT CORPORATION 2025 挿入が有効なパターン(3/3) xmin cmin status 100
Commit 102 Commit 104 3 テーブル xmin xmax xip curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) • xmin == xidなので自Txが挿入 • cmin < snapshot->curcidなので スナップショット取得時点で実行済み • 挿入は自TXが実行済みなので有効
35 © NTT CORPORATION 2025 削除が無効なパターン(1/4) テーブル xmin xmax xip
curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) xmax cmax status 100 Abort 103 Commit 110 Commit 104 10 • xmax < snapshot->xminなので Tx100はスナップショット取得時点で完了 • ヒントビット or CLOGから Tx100はAbortしたとわかるので無効
36 © NTT CORPORATION 2025 削除が無効なパターン(2/4) テーブル xmin xmax xip
curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) xmax cmax status 100 Abort 103 Commit 110 Commit 104 10 • xmax in snapshot->xipなので Tx103はスナップショット取得時点で実行中 • スナップショット取得時点では 未Commitであるので無効 • ヒントビット or CLOGがCommitであっても、 スナップショット取得時点の状態で判断
37 © NTT CORPORATION 2025 削除が無効なパターン(3/4) テーブル xmin xmax xip
curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) xmax cmax status 100 Abort 103 Commit 110 Commit 104 10 • xmax > snapshot->xmaxなので Tx110はスナップショット取得時点で未実行 • 削除は将来実行されるので無効
38 © NTT CORPORATION 2025 削除が無効なパターン(4/4) テーブル xmin xmax xip
curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) xmax cmax status 100 Abort 103 Commit 110 Commit 104 10 • xmax == xidなので自Txが削除 • cmax > snapshot->curcidなので スナップショット取得時点で未実行 • 削除は自TXが将来実行するので無効
39 © NTT CORPORATION 2025 削除が有効なパターン(1/3) xmax cmax status 100
100 Commit 102 102 Commit 104 3 テーブル xmin xmax xip curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) • xmax < snapshot->xminなので Tx100はスナップショット取得時点で完了 • ヒントビット or CLOGから Tx100はCommitしたとわかるので有効
40 © NTT CORPORATION 2025 削除が有効なパターン(2/3) テーブル xmin xmax xip
curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) • xmin not in snapshot->xipなので Tx102はスナップショット取得時点で完了 • ヒントビット or CLOGから Tx102はCommitしたとわかるので有効 xmax cmax status 100 100 Commit 102 102 Commit 104 3
41 © NTT CORPORATION 2025 削除が有効なパターン(3/3) テーブル xmin xmax xip
curcid 101 105 101,103,104,105 5 スナップショット(Tx104が取得) • xmin == xidなので自Txが挿入 • cmax < snapshot->curcidなので スナップショット取得時点で実行済み • 削除は自TXが実行済みなので有効 xmax cmax status 100 100 Commit 102 102 Commit 104 3
42 © NTT CORPORATION 2025 まとめ • PostgreSQLのVisibilityの仕組みについて説明した • SNAPSHOT_MVCC以外のスナップショットについても
調べたかったが時間がなく断念
43 © NTT CORPORATION 2025 参考 • https://www.postgresql.org/docs/devel/storage-page-layout.html • https://edbjapan.com/webinar/MVCC_Unmasked_211110.pdf
• https://www.highgo.ca/2024/04/19/a-deeper-look-inside-postgresql-visibility-check-mechanism/