Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PostgreSQLのVisibilityの仕組み

Avatar for Shinya Kato Shinya Kato
June 17, 2025
420

 PostgreSQLのVisibilityの仕組み

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

2025年6月24日(火)

NTT OSSセンタ
加藤 慎也

Avatar for Shinya Kato

Shinya Kato

June 17, 2025
Tweet

More Decks by Shinya Kato

Transcript

  1. 3 © NTT CORPORATION 2025 Visibility • 日本語で「可視性」 • 各トランザクションからテーブル内の行(タプル)が見えるか

    どうかを制御する仕組み • PostgreSQLはMVCC(Multi-version Concurrency Control) を使用しており、行(タプル)には複数のバージョンがある
  2. 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。存在しなければゼロ。
  3. 8 © NTT CORPORATION 2025 pd_flags • 以下のフラグがある • PD_HAS_FREE_LINES:未使用の行ポインタがあるか

    • PD_PAGE_FULL:新しいタプルに十分な空き領域がないか • PD_ALL_VISIBLE:すべてのタプルが全Txから可視か • PD_VALID_FLAG_BITS:有効なpd_flagsビットの論理和
  4. 9 © NTT CORPORATION 2025 PD_ALL_VISIBLE • static inline void

    PageSetAllVisible(Page page)により設定 • src/include/storage/bufpage.h • VACUUM、COPY FREEZE実行時に呼び出される
  5. 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) 適当にテーブルを作成して、 データを挿入
  6. 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でもよかったかも。 フラグは立っていない。 フラグは立っていない。 フラグは立っていない。
  7. 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してみる。
  8. 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のフラグがおりた!
  9. 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によって 呼び出す関数が変わる
  10. 17 © NTT CORPORATION 2025 タプルデータ • タプルデータには以下が格納されている • 固定サイズのヘッダ

    • NULLビットマップ(オプション) • OID (オプション) • 実際のユーザデータ
  11. 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バイト ユーザデータに対するオフセット
  12. 19 © NTT CORPORATION 2025 タプルデータ – 可視性判定に重要なもの • トランザクション間の可視判定に使用

    • xmin:タプルを挿入したTxのXID • xmax:タプルを削除したTxのXID • トランザクション内の可視判定に使用 • cid:Tx内で挿入/削除/更新などに割り当てられるコマンドID • 挿入の場合cmin、削除の場合cmaxと呼ばれる • 1つのタプルに対して挿入と削除が行われた場合は Combo Command IDを保存する
  13. 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
  14. 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に永続化
  15. 22 © NTT CORPORATION 2025 スナップショット • スナップショットはSnapshotData構造体に格納 • src/include/utils/snapshot.h

    • 様々な情報が格納されているが、以下の情報が重要 • xmin:実行中のTxで最小のXID • xmax:完了したTxで最大のXID • xip:実行中のTxのXIDの配列(transaction in progressの略?) • curcid:スナップショットを作成したコマンドのID
  16. 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
  17. 24 © NTT CORPORATION 2025 スナップショットの取得タイミング • READ COMMITTED •

    SQLごとにスナップショットを取得 • SQL内で可視判定基準が同じ • REPEATABLE READ/SERIALIZABLE • Txごとにスナップショットを取得 • Tx内で可視判定基準が同じ
  18. 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分離レベルによって 取得タイミングが異なる
  19. 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したとわかるので無効
  20. 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であっても、 スナップショット取得時点の状態で判断
  21. 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が挿入したタプルを 可視判定
  22. 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が挿入したタプルを可視判定
  23. 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したとわかるので有効
  24. 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したとわかるので有効
  25. 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が実行済みなので有効
  26. 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したとわかるので無効
  27. 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であっても、 スナップショット取得時点の状態で判断
  28. 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はスナップショット取得時点で未実行 • 削除は将来実行されるので無効
  29. 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が将来実行するので無効
  30. 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したとわかるので有効
  31. 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
  32. 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