🎉

PostgreSQL - 可視性マップの更新タイミング

2022/07/22に公開

PostgreSQL では追記型アーキテクチャーをとっていることにより、各ページにあるデッドタプルを回収するvacuumという仕組みが必要。このvacuumを高速化する仕組みとしてヒープのリレーションファイルと別にVisibility Map(VM) というファイルが各ヒープごとに存在する。

なお、公式マニュアルに記載があるが、各ページごとに2bitで表現されていて、1bit目はページがall visibleか、2bit目は全てのタプルが凍結されているかを示す。

https://www.postgresql.org/docs/current/storage-vm.html

The visibility map stores two bits per heap page. The first bit, if set, indicates that the page is all-visible, or in other words that the page does not contain any tuples that need to be vacuumed. This information can also be used by index-only scans to answer queries using only the index tuple. The second bit, if set, means that all tuples on the page have been frozen. That means that even an anti-wraparound vacuum need not revisit the page.

なお、all-visible なページが32個以上連続で続かないと、vacuum はそのページをスキップしないらしい。

https://github.com/postgres/postgres/blob/master/src/backend/access/heap/vacuumlazy.c#L111-L115

Visibility Map の更新タイミングは、タプルの更新時やvacuum実行時であるが、実際にファイルの中身を見て検証してみた。

事前準備

drop table test1;
CREATE TABLE test1 AS SELECT id FROM generate_series(1, 10) id;
vacuum test1;

postgres=# SELECT datid, datname FROM pg_stat_database;
 datid  |  datname  
--------+-----------
  13881 | postgres
 103532 | toast
      1 | template1
  13880 | template0
 318189 | sample
  35720 | repl
 327028 | partition
  70186 | dms01
  78578 | db01
(9 rows)

postgres=# select relname, relfilenode from pg_class where relname='test1';
 relname | relfilenode 
---------+-------------
 test1   |      699260
 
# 可視性マップが生成されている
ls -l 11/data/base/13881/699260_vm 
-rw------- 1 postgres postgres 8192 Jul 22 22:47 11/data/base/13881/699260_vm

ヒープの更新時の可視性マップの更新

ヒープの更新を行った結果ステートメントの実行完了時点で可視性マップが更新(t->f)されることを確認。
そして、トランザクションをロールバックしても元に戻らない。なお、バキュームが走ると可視性マップに自動でbitが立ってしまうので自動バキュームは発動しない前提での検証。

  • 可視性マップ確認。全可視ビットがt
postgres=# select pg_visibility_map('test1');
 pg_visibility_map 
-------------------
 (0,t,f)
(1 row)
  • セッション1でトランザクションを開始し、ヒープを更新
postgres=# begin;
BEGIN
postgres=# update test1 set id=999;
UPDATE 10
  • セッション2で再び可視性マップ確認。その結果、全可視ビットがfに変わっている
postgres=# select pg_visibility_map('test1');
 pg_visibility_map 
-------------------
 (0,f,f)
(1 row)
  • セッション1でトランザクションロールバック
postgres=# rollback;
ROLLBACK
  • セッション2で再び可視性マップ確認。その結果、全可視ビットがfのままで元に戻らない
postgres=# select pg_visibility_map('test1');
 pg_visibility_map 
-------------------
 (0,f,f)
(1 row)

vacuum時の可視性マップの更新

前段の検証の後に手動でvacuum。その結果、可視性マップのビットがf->t

postgres=# vacuum test1;
VACUUM

postgres=# select pg_visibility_map('test1');
 pg_visibility_map 
-------------------
 (0,t,f)
(1 row)

Discussion