Slide 1

Slide 1 text

PostgreSQLのVisibilityの仕組み 2025/6/24 第53回 PostgreSQLアンカンファレンス@オンライン NTT OSSセンタ 加藤 慎也

Slide 2

Slide 2 text

1 © NTT CORPORATION 2025 自己紹介 • 加藤 慎也(かとう しんや) • NTT OSSセンタ • @ShinyaKato_

Slide 3

Slide 3 text

2 © NTT CORPORATION 2025 発表について • 発表資料:https://speakerdeck.com/shinyakato_ • バージョン:PostgreSQL 18 Beta 1

Slide 4

Slide 4 text

3 © NTT CORPORATION 2025 Visibility • 日本語で「可視性」 • 各トランザクションからテーブル内の行(タプル)が見えるか どうかを制御する仕組み • PostgreSQLはMVCC(Multi-version Concurrency Control) を使用しており、行(タプル)には複数のバージョンがある

Slide 5

Slide 5 text

4 © NTT CORPORATION 2025 ページ単位の可視性判定

Slide 6

Slide 6 text

5 © NTT CORPORATION 2025 • PostgreSQLのテーブルの 実態はファイル • ファイルは8kBのページから 構成 PostgreSQLのテーブル ︙ 8kB

Slide 7

Slide 7 text

6 © NTT CORPORATION 2025 • あるページがどのTxからも 可視であることがわかれば、 タプルごとに可視判定しなく て済む • その情報がページヘッダに 書かれている PostgreSQLのテーブル 8kB

Slide 8

Slide 8 text

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。存在しなければゼロ。

Slide 9

Slide 9 text

8 © NTT CORPORATION 2025 pd_flags • 以下のフラグがある • PD_HAS_FREE_LINES:未使用の行ポインタがあるか • PD_PAGE_FULL:新しいタプルに十分な空き領域がないか • PD_ALL_VISIBLE:すべてのタプルが全Txから可視か • PD_VALID_FLAG_BITS:有効なpd_flagsビットの論理和

Slide 10

Slide 10 text

9 © NTT CORPORATION 2025 PD_ALL_VISIBLE • static inline void PageSetAllVisible(Page page)により設定 • src/include/storage/bufpage.h • VACUUM、COPY FREEZE実行時に呼び出される

Slide 11

Slide 11 text

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) 適当にテーブルを作成して、 データを挿入

Slide 12

Slide 12 text

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でもよかったかも。 フラグは立っていない。 フラグは立っていない。 フラグは立っていない。

Slide 13

Slide 13 text

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してみる。

Slide 14

Slide 14 text

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のフラグがおりた!

Slide 15

Slide 15 text

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によって 呼び出す関数が変わる

Slide 16

Slide 16 text

15 © NTT CORPORATION 2025 タプル単位の可視性判定

Slide 17

Slide 17 text

16 © NTT CORPORATION 2025 可視判定に必要なもの • タプルデータ • CLOG(Commit LOG) • スナップショット

Slide 18

Slide 18 text

17 © NTT CORPORATION 2025 タプルデータ • タプルデータには以下が格納されている • 固定サイズのヘッダ • NULLビットマップ(オプション) • OID (オプション) • 実際のユーザデータ

Slide 19

Slide 19 text

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バイト ユーザデータに対するオフセット

Slide 20

Slide 20 text

19 © NTT CORPORATION 2025 タプルデータ – 可視性判定に重要なもの • トランザクション間の可視判定に使用 • xmin:タプルを挿入したTxのXID • xmax:タプルを削除したTxのXID • トランザクション内の可視判定に使用 • cid:Tx内で挿入/削除/更新などに割り当てられるコマンドID • 挿入の場合cmin、削除の場合cmaxと呼ばれる • 1つのタプルに対して挿入と削除が行われた場合は Combo Command IDを保存する

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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に永続化

Slide 23

Slide 23 text

22 © NTT CORPORATION 2025 スナップショット • スナップショットはSnapshotData構造体に格納 • src/include/utils/snapshot.h • 様々な情報が格納されているが、以下の情報が重要 • xmin:実行中のTxで最小のXID • xmax:完了したTxで最大のXID • xip:実行中のTxのXIDの配列(transaction in progressの略?) • curcid:スナップショットを作成したコマンドのID

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

24 © NTT CORPORATION 2025 スナップショットの取得タイミング • READ COMMITTED • SQLごとにスナップショットを取得 • SQL内で可視判定基準が同じ • REPEATABLE READ/SERIALIZABLE • Txごとにスナップショットを取得 • Tx内で可視判定基準が同じ

Slide 26

Slide 26 text

25 © NTT CORPORATION 2025 HeapTupleSatisfiesMVCC

Slide 27

Slide 27 text

26 © NTT CORPORATION 2025 4パターンにわけて説明 1. 挿入が無効なパターン 2. 挿入が有効なパターン 3. 削除が無効なパターン 4. 削除が有効なパターン

Slide 28

Slide 28 text

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分離レベルによって 取得タイミングが異なる

Slide 29

Slide 29 text

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したとわかるので無効

Slide 30

Slide 30 text

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であっても、 スナップショット取得時点の状態で判断

Slide 31

Slide 31 text

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が挿入したタプルを 可視判定

Slide 32

Slide 32 text

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が挿入したタプルを可視判定

Slide 33

Slide 33 text

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したとわかるので有効

Slide 34

Slide 34 text

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したとわかるので有効

Slide 35

Slide 35 text

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が実行済みなので有効

Slide 36

Slide 36 text

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したとわかるので無効

Slide 37

Slide 37 text

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であっても、 スナップショット取得時点の状態で判断

Slide 38

Slide 38 text

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はスナップショット取得時点で未実行 • 削除は将来実行されるので無効

Slide 39

Slide 39 text

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が将来実行するので無効

Slide 40

Slide 40 text

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したとわかるので有効

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

42 © NTT CORPORATION 2025 まとめ • PostgreSQLのVisibilityの仕組みについて説明した • SNAPSHOT_MVCC以外のスナップショットについても 調べたかったが時間がなく断念

Slide 44

Slide 44 text

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/