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
450
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
The Cult of Friendly URLs
andyhume
79
6.5k
GraphQLとの向き合い方2022年版
quramy
47
14k
Writing Fast Ruby
sferik
628
61k
RailsConf 2023
tenderlove
30
1.1k
Unsuck your backbone
ammeep
671
58k
Making the Leap to Tech Lead
cromwellryan
134
9.3k
The Cost Of JavaScript in 2023
addyosmani
51
8.4k
A Modern Web Designer's Workflow
chriscoyier
693
190k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
657
60k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
124
52k
jQuery: Nuts, Bolts and Bling
dougneiner
63
7.8k
Code Review Best Practice
trishagee
68
18k
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/