大家好,今天大表哥和大家分享一下 PG的 Free Space Map 和 Visibility Map.
大家从名字上就可以看出这2个概念的含义:
FSM: 每个 heap 表和索引 都会有一个相对应的 FSM 文件来追踪记录一下对象的可用空间。
VM: 每个 heap 表的对象 都会有一个对应的VM文件追踪记录:
VISIBILITYMAP_ALL_VISIBLE: 标记哪些page里面包含的所有元祖对 active transaction 的可见的。
VISIBILITYMAP_ALL_FROZEN: 标记哪些page里面包含的所有元祖都是frozen的状态
我们先简单的创建一个表:我们可以看到 FSM和VM 2个文件会自动生成,
命名规则如下: oid_fsm, oid_vm
dbtest@[local:/tmp]:1992=#32679 create table t_vm_fsm (id int, name varchar(200));
CREATE TABLE
dbtest@[local:/tmp]:1992=#32679 select oid,relname from pg_class where relname = 't_vm_fsm';
oid | relname
-------+----------
33240 | t_vm_fsm
(1 row)
dbtest@[local:/tmp]:1992=#32679 insert into t_vm_fsm values (generate_series(1,10000),'hello world!');
INSERT 0 10000
INFRA [postgres@wqdcsrv3352 32963]# ls -lhtr | grep 33240
-rw------- 1 postgres postgres 24K Jul 26 14:54 33240_fsm
-rw------- 1 postgres postgres 512K Jul 26 14:54 33240
-rw------- 1 postgres postgres 8.0K Jul 26 14:54 33240_vm
oid_fsm 和 oid_vm 是2个 二进制的文件, 并不能直接查看里面的内容。 不过官方提供了 2个 extension 来查看 二进制文件的内容
pg_freespacemap 是用来查看 FSM文件中记录的 blkno 块号和 avi free space 的大小关系。
我们来安装一下 pg_freespacemap 这个 extension:
dbtest@[local:/tmp]:1992=#32679 create extension pg_freespacemap;
CREATE EXTENSION
里面包含了一个函数 的2个 overload 的方法
dbtest@[local:/tmp]:1992=#32679 \df pg_freespace
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+--------------+------------------+----------------------------------------------------+------
public | pg_freespace | smallint | regclass, bigint | func
public | pg_freespace | SETOF record | rel regclass, OUT blkno bigint, OUT avail smallint | func
(2 rows)
我们查询一下表 t_vm_fsm的大小: 512KB, 默认的page的 大小是 8K, 所以 会有 512/8 = 64 个page (或者是 block)
dbtest@[local:/tmp]:1992=#32679 select pg_size_pretty(pg_relation_size('t_vm_fsm'));
pg_size_pretty
----------------
512 kB
(1 row)
我们可以利用函数 pg_freespace 查看一下 64个 block 的使用情况,我们可以看到 0-62 号 block 中 (注意下标是从0开始的) avail space 是0 , 也就是说没有了可用的空间。 63号 block 是有 2496 byte 的空间的.
dbtest@[local:/tmp]:1992=#32679 SELECT * FROM pg_freespace('t_vm_fsm');
blkno | avail
-------+-------
0 | 0
1 | 0
2 | 0
...
...
...
62 | 0
63 | 2496
(64 rows)
如果要单独查 63号 block 的 AVI 的空间大小,可以 传入2个参数
dbtest@[local:/tmp]:1992=#32679 SELECT * FROM pg_freespace('t_vm_fsm',63);
pg_freespace
--------------
2496
(1 row)
如果我们现在要对表,进行一些 delete 操作, 再次查看空间大小:
dbtest@[local:/tmp]:1992=#32679 delete from t_vm_fsm where id < 1000;
DELETE 999
由于我们表的基数是 10000 , autovacuum_vacuum_threshold 是 50 , autovacuum_vacuum_scale_factor 是 0.2
所以触发 auto_vacuumn 的阀值是 50 + 10000 * 0.2 = 2050,
999 条数据的删除,达不到阀值,所以不会触发 auto vacuum
我们再次查看 pg_freespace : 并没有什么变化
dbtest@[local:/tmp]:1992=#32679 SELECT * FROM pg_freespace('t_vm_fsm');
blkno | avail
-------+-------
0 | 0
1 | 0
2 | 0
...
...
...
62 | 0
63 | 2496
(64 rows)
我们手动执行一下 vacuum table 的操作:
dbtest@[local:/tmp]:1992=#32679 vacuum t_vm_fsm;
VACUUM
再次查看 pg_freespace : 我们可以发现 block 0-6 号 已经释放了可重用的空间
dbtest@[local:/tmp]:1992=#32679 select * from (SELECT * FROM pg_freespace('t_vm_fsm')) fsm where fsm.avail > 0;
blkno | avail
-------+-------
0 | 8160
1 | 8160
2 | 8160
3 | 8160
4 | 8160
5 | 8160
6 | 2720
63 | 2496
(8 rows)
我们再一次运行一下 vacuum full , 回收一下物理的空间 ,注意这个是锁表的,生产上需要禁用:
dbtest@[local:/tmp]:1992=#48460 vacuum full t_vm_fsm;
VACUUM
再次观察: page 已经进行物理上的重组, 之前的 64个 block 变成了 58个 block ,
物理存储从 512 kB 下降到 464kB
dbtest@[local:/tmp]:1992=#48460 select * from (SELECT * FROM pg_freespace('t_vm_fsm')) fsm where fsm.avail > 0;
blkno | avail
-------+-------
(0 rows)
dbtest@[local:/tmp]:1992=#48460 SELECT count(1) FROM pg_freespace('t_vm_fsm');
count
-------
58
(1 row)
dbtest@[local:/tmp]:1992=#48460 select pg_size_pretty(pg_relation_size('t_vm_fsm'));
pg_size_pretty
----------------
464 kB
(1 row)
从代码的角度上来说 FSM 是用数据结构 二叉树来维护的, 对算法感兴趣的同学们可以看看:
src/backend/storage/freespace/README
FSM page structure
------------------
Within each FSM page, we use a binary tree structure where leaf nodes store
the amount of free space on heap pages (or lower level FSM pages, see
"Higher-level structure" below), with one leaf node per heap page. A non-leaf
node stores the max amount of free space on any of its children.
For example:
4
4 2
3 4 0 2 <- This level represents heap pages
下面我们接着来看 Visibility Map :
官方文档上的定义是 The visibility map stores two bits per heap page。 为每一个 page 或者 block 存储一个2 bit 的标记
a)第一个 bit 是标记 VISIBILITYMAP_ALL_VISIBLE,如果一个page 中所有的 tuple 都是 all visible 的, 那么进行标记
b)第二个bit 是标记 VISIBILITYMAP_ALL_FROZEN,如果一个page 中所有的 tuple 都是 forzen 的, 那么进行标记
Visibility Map 的2大用途是:
1.触发Index only scan 的访问,加快SQL 执行效率
2.加速 vacuum 的执行效率
我们可以通过 extension : pg_visibility 来观测一下:
dbtest@[local:/tmp]:1992=#80334 create extension pg_visibility;
CREATE EXTENSION
dbtest@[local:/tmp]:1992=#80334 create table t_vm (id int , name varchar(200));
CREATE TABLE
dbtest@[local:/tmp]:1992=#80334 insert into t_vm values(generate_series(1,10000),'hello VM !');
INSERT 0 10000
dbtest@[local:/tmp]:1992=#80334 create index concurrently idx_name_vm on t_vm(name);
CREATE INDEX
我们可以看到,目前的表没有任何修改,删除等操作,所以在VM中的元祖都是可见的。
dbtest@[local:/tmp]:1992=#80334 select count(0) from pg_visibility('t_vm') ;
count
-------
55
(1 row)
dbtest@[local:/tmp]:1992=#80334 select count(0) from pg_visibility('t_vm') where all_visible = 'f' ;
count
-------
0
(1 row)
dbtest@[local:/tmp]:1992=#80334 select all_visible, count(1) from pg_visibility('t_vm') group by all_visible ;
all_visible | count
-------------+-------
t | 55
(1 row)
这个时候我们运行一条SQL 可以出发 index only scan 的执行计划:
几个重要的信息指标:
Index Only Scan using idx_name_vm on t_vm
Heap Fetches: 0
dbtest@[local:/tmp]:1992=#80334 explain(analyze) select name from t_vm where name = 'test';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_name_vm on t_vm (cost=0.29..4.30 rows=1 width=11) (actual time=0.016..0.017 rows=0 loops=1)
Index Cond: (name = 'test'::text)
Heap Fetches: 0
Planning Time: 0.470 ms
Execution Time: 0.045 ms
(5 rows)
下面我们要的更新一些数据, 注意更新的数据不要触发 auto vacuum 的进程, 因为vacuum 的进程结束后, 会更新VM
由于我们表的基数是 10000 , autovacuum_vacuum_threshold 是 50 , autovacuum_vacuum_scale_factor 是 0.2
所以触发 auto_vacuumn 的阀值是 50 + 10000 * 0.2 = 2050,
1000条数据的更新,不会触发 auto vacuum
dbtest@[local:/tmp]:1992=#80334 update t_vm set name = 'test' where id <= 1000;
UPDATE 1000
我们可以看到 有 12个 page (block ) 执行完更新的操作后, 标记更新成了 f
dbtest@[local:/tmp]:1992=#80334 select all_visible, count(1) from pg_visibility('t_vm') group by all_visible ;
all_visible | count
-------------+-------
f | 12
t | 48
(2 rows)
我们再次执行SQL: 可以看到 Heap Fetches: 1000 变成了 1000
dbtest@[local:/tmp]:1992=#80334 explain(analyze) select name from t_vm where name = 'test';
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_name_vm on t_vm (cost=0.29..4.30 rows=1 width=11) (actual time=0.030..0.179 rows=1000 loops=1)
Index Cond: (name = 'test'::text)
Heap Fetches: 1000
Planning Time: 0.065 ms
Execution Time: 0.225 ms
(5 rows)
我们手动 vacuum 一下表 t_vm :
dbtest@[local:/tmp]:1992=#80334 vacuum t_vm;
VACUUM
再次观察 VM 的标志位: 全部标记为 visibility
dbtest@[local:/tmp]:1992=#80334 select all_visible, count(1) from pg_visibility('t_vm') group by all_visible ;
all_visible | count
-------------+-------
t | 60
(1 row)
再次查看SQL的执行计划: Heap Fetches: 0 变成了 0
dbtest@[local:/tmp]:1992=#80334 explain(analyze) select name from t_vm where name = 'test';
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_name_vm on t_vm (cost=0.29..4.30 rows=1 width=11) (actual time=0.020..0.093 rows=1000 loops=1)
Index Cond: (name = 'test'::text)
Heap Fetches: 0
Planning Time: 0.120 ms
Execution Time: 0.147 ms
(5 rows)
我们继续看一下 VISIBILITYMAP_ALL_FROZEN 这个标记位:
我们来删除500条记录:
dbtest@[local:/tmp]:1992=#80334 delete from t_vm where id <= 500;
DELETE 500
我们可以观察 frozen block 的信息: blkno [0-4] 一共5个块 被标记为了 all_frozen = t
dbtest@[local:/tmp]:1992=#80334 select blkno, all_frozen from pg_visibility('t_vm') where all_frozen = 't';
blkno | all_frozen
-------+------------
0 | t
1 | t
2 | t
3 | t
4 | t
(5 rows)
dbtest@[local:/tmp]:1992=#80334 select * from pg_visibility_map_summary('t_vm');
all_visible | all_frozen
-------------+------------
57 | 5
(1 row)
我们可以通过 extension pageinspect 查看一下 标记为 forzen 的 blkno [0-4] 里面的内容都是空的
dbtest@[local:/tmp]:1992=#80334 create extension pageinspect;
CREATE EXTENSION
dbtest@[local:/tmp]:1992=#80334 select * from heap_page_items(get_raw_page('t_vm',0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+--------
1 | 0 | 0 | 0 | | | | | | | | | |
(1 row)
dbtest@[local:/tmp]:1992=#80334 select * from heap_page_items(get_raw_page('t_vm',4));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+--------
1 | 0 | 0 | 0 | | | | | | | | | |
(1 row)
正常的 block 页面的信息如下 blkno 为5号 的页面信息
dbtest@[local:/tmp]:1992=#80334 select * from heap_page_items(get_raw_page('t_vm',5)) where t_data is not null limit 10;
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
----+--------+----------+--------+---------+--------+----------+--------+-------------+------------+--------+--------+-------+----------------------------------
76 | 8152 | 1 | 39 | 1523617 | 0 | 0 | (5,76) | 2 | 2306 | 24 | | | \xe90300001768656c6c6f20564d2021
77 | 8112 | 1 | 39 | 1523617 | 0 | 0 | (5,77) | 2 | 2306 | 24 | | | \xea0300001768656c6c6f20564d2021
78 | 8072 | 1 | 39 | 1523617 | 0 | 0 | (5,78) | 2 | 2306 | 24 | | | \xeb0300001768656c6c6f20564d2021
79 | 8032 | 1 | 39 | 1523617 | 0 | 0 | (5,79) | 2 | 2306 | 24 | | | \xec0300001768656c6c6f20564d2021
80 | 7992 | 1 | 39 | 1523617 | 0 | 0 | (5,80) | 2 | 2306 | 24 | | | \xed0300001768656c6c6f20564d2021
81 | 7952 | 1 | 39 | 1523617 | 0 | 0 | (5,81) | 2 | 2306 | 24 | | | \xee0300001768656c6c6f20564d2021
82 | 7912 | 1 | 39 | 1523617 | 0 | 0 | (5,82) | 2 | 2306 | 24 | | | \xef0300001768656c6c6f20564d2021
83 | 7872 | 1 | 39 | 1523617 | 0 | 0 | (5,83) | 2 | 2306 | 24 | | | \xf00300001768656c6c6f20564d2021
84 | 7832 | 1 | 39 | 1523617 | 0 | 0 | (5,84) | 2 | 2306 | 24 | | | \xf10300001768656c6c6f20564d2021
85 | 7792 | 1 | 39 | 1523617 | 0 | 0 | (5,85) | 2 | 2306 | 24 | | | \xf20300001768656c6c6f20564d2021
(10 rows)
最后我们总结一下:
Free Space Map: 记录追踪每个(blk 或 page)的空闲空间的信息,文件的命名形式 xxx_fsm, 可以通过 extension pg_freespacemap 查看具体的详细信息
Visibility Map: 记录追踪每个(blk 或 page)的 VISIBILITYMAP_ALL_VISIBLE 和 VISIBILITYMAP_ALL_FROZEN 信息。 文件的命名形式 xxx_vm, 可以通过 extension pg_visibility 查看具体的详细信息
Have a fun ! 🙂