PostgreSQL Freeze 风暴预测续 - 珍藏级SQL

12 minute read

背景

PostgreSQL 目前默认的存储引擎,事务可见性需要依赖行头的事务号,因为事务号是32位的,会循环使用。

在一条记录产生后,如果再次经历了20亿个事务,必须对其进行freeze,否则数据库会认为这条记录是未来事务产生的(可见性判断)。

因此FREEZE操作是数据库在32位事务号的情况下,经常要做的。

对全表进行FREEZE操作时,会扫描整表,将大于指定阈值least(autovacuum_freeze_min_age, 表级参数vacuum_freeze_min_age)年龄的记录设置为freeze。可能导致大量的读IO,写IO(主要是写数据文件,WAL日志, full page write WAL)。

一些参数决定数据库在什么时候触发FREEZE,以及触发FREEZE时,冻结哪些记录,以及是否涉及到调度(sleep)。

同时很多参数有库级、表级选项。表级优先,然后是库级,最后是实例级。

《PostgreSQL GUC 参数级别介绍》

1、多久检查一次哪些表是否需要FREEZE

postgres=# show autovacuum_naptime ;    
 autovacuum_naptime     
--------------------    
 1s    
(1 row)    

2、哪些表需要被自动FREEZE,

超过如下阈值,如果设置了表级参数则以表级参数为准,否则以系统参数为准。

表级参数    
autovacuum_freeze_max_age    
autovacuum_freeze_table_age    
    
系统级参数    
autovacuum_freeze_max_age    

3、手工执行普通vacuum时,哪些表会被扫描全表,并freeze

超过如下阈值

系统级参数    
vacuum_freeze_table_age    

注意,现在PG支持VM文件里面记录一个PAGE是否需要被FREEZE,所以即使全表扫描,也会根据VM标记位,跳过一些BLOCK,所以FREEZE并不一定会产生大量读IO。根据表的情况而定。

3、触发FREEZE时,哪些记录需要被FREEZE

超过如下阈值的记录被FREEZE,如果设置了表级参数则以表级参数为准,否则以系统参数为准。

表级参数    
autovacuum_freeze_min_age    
    
系统级参数    
vacuum_freeze_min_age    

4、FREEZE后台进程的调度(sleep)。

  current_setting('autovacuum_vacuum_cost_delay') as v7,              -- 自动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源. -1 表示沿用vacuum_cost_delay的设置    
  current_setting('autovacuum_vacuum_cost_limit') as v8,              -- 自动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定. -1 表示沿用vacuum_cost_limit的设置    
  current_setting('vacuum_cost_delay') as v9,                         -- 手动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源.    
  current_setting('vacuum_cost_limit') as v10,                        -- 手动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定.    
  current_setting('autovacuum') as autovacuum                         -- 是否开启自动垃圾回收    

观测SQL

SQL里的注释,建议看懂它,可以掌握更多知识点。(这个SQL没有考虑toast相关的表级参数,一般人不会去设置它。如果要严谨一点,需要考虑进来)

create view v_freeze as    
select     
  e.*,     
  a.*     
from    
(select     
  current_setting('autovacuum_freeze_max_age')::int as v1,            -- 如果表的事务ID年龄大于该值, 即使未开启autovacuum也会强制触发FREEZE, 并告警Preventing Transaction ID Wraparound Failures    
  current_setting('autovacuum_multixact_freeze_max_age')::int as v2,  -- 如果表的并行事务ID年龄大于该值, 即使未开启autovacuum也会强制触发FREEZE, 并告警Preventing Transaction ID Wraparound Failures    
  current_setting('vacuum_freeze_min_age')::int as v3,                -- 手动或自动垃圾回收时, 如果记录的事务ID年龄大于该值, 将被FREEZE    
  current_setting('vacuum_multixact_freeze_min_age')::int as v4,      -- 手动或自动垃圾回收时, 如果记录的并行事务ID年龄大于该值, 将被FREEZE    
  current_setting('vacuum_freeze_table_age')::int as v5,              -- 手动垃圾回收时, 如果表的事务ID年龄大于该值, 将触发FREEZE. 该参数的上限值为 %95 autovacuum_freeze_max_age    
  current_setting('vacuum_multixact_freeze_table_age')::int as v6,    -- 手动垃圾回收时, 如果表的并行事务ID年龄大于该值, 将触发FREEZE. 该参数的上限值为 %95 autovacuum_multixact_freeze_max_age    
  current_setting('autovacuum_vacuum_cost_delay') as v7,              -- 自动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源. -1 表示沿用vacuum_cost_delay的设置    
  current_setting('autovacuum_vacuum_cost_limit') as v8,              -- 自动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定. -1 表示沿用vacuum_cost_limit的设置    
  current_setting('vacuum_cost_delay') as v9,                         -- 手动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源.    
  current_setting('vacuum_cost_limit') as v10,                        -- 手动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定.    
  current_setting('autovacuum') as autovacuum                         -- 是否开启自动垃圾回收    
) a,     
LATERAL (   -- LATERAL 允许你在这个SUBQUERY中直接引用前面的table, subquery中的column     
select     
pg_size_pretty(pg_total_relation_size(oid)) sz,   -- 表的大小(含TOAST, 索引)    
oid::regclass as reloid,    -- 表名(物化视图)    
relkind,                    -- r=表, m=物化视图    
coalesce(    
  least(    
    substring(reloptions::text, 'autovacuum_freeze_max_age=(\d+)')::int,     
    substring(reloptions::text, 'autovacuum_freeze_table_age=(\d+)')::int     
  ),    
  a.v1    
)    
-    
age(case when relfrozenxid::text::int<3 then null else relfrozenxid end)     
as remain_ages_xid,   -- 再产生多少个事务后, 自动垃圾回收会触发FREEZE, 起因为事务ID    
coalesce(    
  least(    
    substring(reloptions::text, 'autovacuum_multixact_freeze_max_age=(\d+)')::int,     
    substring(reloptions::text, 'autovacuum_multixact_freeze_table_age=(\d+)')::int     
  ),    
  a.v2    
)    
-    
age(case when relminmxid::text::int<3 then null else relminmxid end)     
as remain_ages_mxid,  -- 再产生多少个事务后, 自动垃圾回收会触发FREEZE, 起因为并发事务ID    
coalesce(    
  least(    
    substring(reloptions::text, 'autovacuum_freeze_min_age=(\d+)')::int    
  ),    
  a.v3    
) as xid_lower_to_minage,    -- 如果触发FREEZE, 该表的事务ID年龄会降到多少    
coalesce(    
  least(    
    substring(reloptions::text, 'autovacuum_multixact_freeze_min_age=(\d+)')::int    
  ),    
  a.v4    
) as mxid_lower_to_minage,   -- 如果触发FREEZE, 该表的并行事务ID年龄会降到多少    
case     
  when v5 <= age(case when relfrozenxid::text::int<3 then null else relfrozenxid end) then 'YES'    
  else 'NOT'    
end as vacuum_trigger_freeze1,    -- 如果手工执行VACUUM, 是否会触发FREEZE, 触发起因(事务ID年龄达到阈值)    
case     
  when v6 <= age(case when relminmxid::text::int<3 then null else relminmxid end) then 'YES'    
  else 'NOT'    
end as vacuum_trigger_freeze2,    -- 如果手工执行VACUUM, 是否会触发FREEZE, 触发起因(并行事务ID年龄达到阈值)    
reloptions                        -- 表级参数, 优先. 例如是否开启自动垃圾回收, autovacuum_freeze_max_age, autovacuum_freeze_table_age, autovacuum_multixact_freeze_max_age, autovacuum_multixact_freeze_table_age    
from pg_class     
  where relkind in ('r','m')    
) e     
order by     
  least(e.remain_ages_xid , e.remain_ages_mxid),  -- 排在越前, 越先触发自动FREEZE, 即风暴来临的预测    
  pg_total_relation_size(reloid) desc   -- 同样剩余年龄, 表越大, 排越前    
;    

并行事务ID指多会话对同一记录加共享锁,将记录并行事务ID:

《并发事务, 共享行锁管理 - pg_multixact manager for shared-row-lock implementation》

返回解释如下:

postgres=# \x    
Expanded display is on.    
postgres=# select * from v_freeze ;    
-[ RECORD 1 ]----------+-------------------------------------------    
sz                     | 32 kB         -- 表的大小(含TOAST, 索引)    
reloid                 | information_schema.sql_sizing_profiles          -- 表名(物化视图)    
relkind                | r             -- r=表, m=物化视图    
remain_ages_xid        | 199739848     -- 再产生多少个事务后, 自动垃圾回收会触发FREEZE, 起因为事务ID    
remain_ages_mxid       |               -- 再产生多少个事务后, 自动垃圾回收会触发FREEZE, 起因为并发事务ID    
xid_lower_to_minage    | 0             -- 如果触发FREEZE, 该表的事务ID年龄会降到多少    
mxid_lower_to_minage   | 0             -- 如果触发FREEZE, 该表的并行事务ID年龄会降到多少    
vacuum_trigger_freeze1 | YES           -- 如果手工执行VACUUM, 是否会触发FREEZE, 触发起因(事务ID年龄达到阈值)    
vacuum_trigger_freeze2 | NOT           -- 如果手工执行VACUUM, 是否会触发FREEZE, 触发起因(并行事务ID年龄达到阈值)    
reloptions             |               -- 表级参数, 优先. 例如是否开启自动垃圾回收, autovacuum_freeze_max_age, autovacuum_freeze_table_age, autovacuum_multixact_freeze_max_age, autovacuum_multixact_freeze_table_age    
v1                     | 200000000     -- 如果表的事务ID年龄大于该值, 即使未开启autovacuum也会强制触发FREEZE, 并告警Preventing Transaction ID Wraparound Failures    
v2                     | 400000000     -- 如果表的并行事务ID年龄大于该值, 即使未开启autovacuum也会强制触发FREEZE, 并告警Preventing Transaction ID Wraparound Failures    
v3                     | 0             -- 手动或自动垃圾回收时, 如果记录的事务ID年龄大于该值, 将被FREEZE    
v4                     | 0             -- 手动或自动垃圾回收时, 如果记录的并行事务ID年龄大于该值, 将被FREEZE    
v5                     | 200           -- 手动垃圾回收时, 如果表的事务ID年龄大于该值, 将触发FREEZE. 该参数的上限值为 %95 autovacuum_freeze_max_age    
v6                     | 150000000     -- 手动垃圾回收时, 如果表的并行事务ID年龄大于该值, 将触发FREEZE. 该参数的上限值为 %95 autovacuum_multixact_freeze_max_age    
v7                     | 0             -- 自动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源. -1 表示沿用vacuum_cost_delay的设置    
v8                     | -1            -- 自动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定. -1 表示沿用vacuum_cost_limit的设置    
v9                     | 0             -- 手动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源.    
v10                    | 200           -- 手动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定.    
autovacuum             | on            -- 是否开启自动垃圾回收    
-[ RECORD 2 ]----------+-------------------------------------------    
sz                     | 192 kB    
reloid                 | information_schema.sql_implementation_info    
relkind                | r    
remain_ages_xid        | 199739848    
remain_ages_mxid       |     
xid_lower_to_minage    | 0    
mxid_lower_to_minage   | 0    
vacuum_trigger_freeze1 | YES    
vacuum_trigger_freeze2 | NOT    
reloptions             |     
v1                     | 200000000    
v2                     | 400000000    
v3                     | 0    
v4                     | 0    
v5                     | 200    
v6                     | 150000000    
v7                     | 0    
v8                     | -1    
v9                     | 0    
v10                    | 200    
autovacuum             | on    
-[ RECORD 3 ]----------+-------------------------------------------    
sz                     | 192 kB    
reloid                 | information_schema.sql_languages    
relkind                | r    
remain_ages_xid        | 199739848    
remain_ages_mxid       |     
xid_lower_to_minage    | 0    
mxid_lower_to_minage   | 0    
vacuum_trigger_freeze1 | YES    
vacuum_trigger_freeze2 | NOT    
reloptions             |     
v1                     | 200000000    
v2                     | 400000000    
v3                     | 0    
v4                     | 0    
v5                     | 200    
v6                     | 150000000    
v7                     | 0    
v8                     | -1    
v9                     | 0    
v10                    | 200    
autovacuum             | on    
-[ RECORD 4 ]----------+-------------------------------------------    
sz                     | 192 kB    
reloid                 | information_schema.sql_packages    
relkind                | r    
remain_ages_xid        | 199739848    
remain_ages_mxid       |     
xid_lower_to_minage    | 0    
mxid_lower_to_minage   | 0    
vacuum_trigger_freeze1 | YES    
vacuum_trigger_freeze2 | NOT    
reloptions             |     
v1                     | 200000000    
v2                     | 400000000    
v3                     | 0    
v4                     | 0    
v5                     | 200    
v6                     | 150000000    
v7                     | 0    
v8                     | -1    
v9                     | 0    
v10                    | 200    
autovacuum             | on    
-[ RECORD 5 ]----------+-------------------------------------------    
sz                     | 192 kB    
reloid                 | information_schema.sql_parts    
relkind                | r    
remain_ages_xid        | 199739848    
remain_ages_mxid       |     
xid_lower_to_minage    | 0    
mxid_lower_to_minage   | 0    
vacuum_trigger_freeze1 | YES    
vacuum_trigger_freeze2 | NOT    
reloptions             |     
v1                     | 200000000    
v2                     | 400000000    
v3                     | 0    
v4                     | 0    
v5                     | 200    
v6                     | 150000000    
v7                     | 0    
v8                     | -1    
v9                     | 0    
v10                    | 200    
autovacuum             | on    

统计SQL

例如每隔100万个事务, 统计一下有多少表, 多少容量会参与FREEZE, 会导致读多少IO, 写IO多少?

create view v_freeze_stat as    
select     
wb,                                                     -- 第几个BATCH, 每个batch代表流逝100万个事务     
cnt,                                                    -- 这个batch 有多少表    
pg_size_pretty(ssz) as ssz1,                            -- 这个batch 这些 表+TOAST+索引 有多少容量    
pg_size_pretty(ssz) as ssz2,                            -- 这个batch FREEZE 会导致多少读IO    
pg_size_pretty(ssz*3) as ssz3,                          -- 这个batch FREEZE 最多可能会导致多少写IO (通常三份 : 数据文件, WAL FULL PAGE, WAL)    
pg_size_pretty(min_sz) as ssz4,                         -- 这个batch 最小的表多大    
pg_size_pretty(max_sz) as ssz5,                         -- 这个batch 最大的表多大    
pg_size_pretty(avg_sz) as ssz6,                         -- 这个batch 平均表多大    
pg_size_pretty(stddev_sz) as ssz7,                      -- 这个batch 表大小的方差, 越大, 说明表大小差异化明显    
min_rest_age,                                           -- 这个batch 距离自动FREEZE最低剩余事务数    
max_rest_age,                                           -- 这个batch 距离自动FREEZE最高剩余事务数    
stddev_rest_age,                                        -- 这个batch 距离自动FREEZE剩余事务数的方差, 越小,说明这个batch触发freeze将越平缓, 越大, 说明这个batch将有可能在某些点集中触发freeze (但是可能集中触发的都是小表)    
corr_rest_age_sz,                                       -- 表大小与距离自动freeze剩余事务数的相关性,相关性越强(值趋向1或-1) stddev_rest_age 与 sz7 说明的问题越有价值    
round(100*(ssz/(sum(ssz) over ())), 2)||' %' as ratio   -- 这个BATCH的容量占比,占比如果非常不均匀,说明有必要调整表级FREEZE参数,让占比均匀化    
from         
(    
select a.*, b.* from     
(    
select     
  min(least(remain_ages_xid, remain_ages_mxid)) as v_min,   -- 整个数据库中离自动FREEZE的 最小 剩余事务ID数    
  max(least(remain_ages_xid, remain_ages_mxid)) as v_max    -- 整个数据库中离自动FREEZE的 最大 剩余事务ID数    
from v_freeze    
) as a,    
LATERAL (  -- 高级SQL    
select     
width_bucket(    
  least(remain_ages_xid, remain_ages_mxid),     
  a.v_min,    
  a.v_max,    
  greatest((a.v_max-a.v_min)/1000000, 1)   -- 100万个事务, 如果要更改统计例如,修改这个值即可    
) as wb,      
count(*) as cnt,     
sum(pg_total_relation_size(reloid)) as ssz,     
stddev_samp(pg_total_relation_size(reloid) order by least(remain_ages_xid, remain_ages_mxid)) as stddev_sz,     
min(pg_total_relation_size(reloid)) as min_sz,     
max(pg_total_relation_size(reloid)) as max_sz,     
avg(pg_total_relation_size(reloid)) as avg_sz,     
min(least(remain_ages_xid, remain_ages_mxid)) as min_rest_age,     
max(least(remain_ages_xid, remain_ages_mxid)) as max_rest_age,     
stddev_samp(least(remain_ages_xid, remain_ages_mxid) order by least(remain_ages_xid, remain_ages_mxid)) as stddev_rest_age,     
corr(least(remain_ages_xid, remain_ages_mxid), pg_total_relation_size(reloid)) as corr_rest_age_sz     
from v_freeze     
group by wb     
) as b     
) t     
order by wb;      

返回解释如下

 wb | cnt  |   sz1   |   sz2   |  sz3  |  sz4   |   sz5   |   sz6   |   sz7   | min_rest_age | max_rest_age | stddev_rest_age |  corr_rest_age_sz   |  ratio      
----+------+---------+---------+-------+--------+---------+---------+---------+--------------+--------------+-----------------+---------------------+---------    
  1 | 2083 | 13 GB   | 13 GB   | 38 GB | 32 kB  | 6326 MB | 6380 kB | 184 MB  |    199739848 |    199890546 |  18826.08737805 | -0.0818846238277424 | 70.96 %    
  2 |    2 | 5311 MB | 5311 MB | 16 GB | 224 kB | 5311 MB | 2655 MB | 3755 MB |    199999996 |    199999996 |               0 |                     | 29.04 %    
(2 rows)    

占比不均匀,有必要调整表级freeze参数。

-[ RECORD 1 ]----+--------------------    
wb               | 1                     -- 第几个BATCH, 每个batch代表流逝100万个事务     
cnt              | 2083                  -- 这个batch 有多少表    
ssz1             | 13 GB                 -- 这个batch 这些 表+TOAST+索引 有多少容量                                               
ssz2             | 13 GB	         -- 这个batch FREEZE 会导致多少 读IO                                                       
ssz3             | 38 GB	         -- 这个batch FREEZE 最多可能会导致多少 写IO (通常三份 : 数据文件, WAL FULL PAGE, WAL)     
ssz4             | 32 kB	         -- 这个batch 最小的表多大                                                                
ssz5             | 6326 MB	         -- 这个batch 最大的表多大                                                                
ssz6             | 6380 kB	         -- 这个batch 平均表多大                                                                  
ssz7             | 184 MB                -- 这个batch 表大小的方差, 越大, 说明表大小差异化明显    
min_rest_age     | 199739846             -- 这个batch 距离自动FREEZE最低 剩余事务数                                                                                                                             
max_rest_age     | 199890544		 -- 这个batch 距离自动FREEZE最高 剩余事务数                                                                                                                             
stddev_rest_age  | 18826.08737805	 -- 这个batch 距离自动FREEZE剩余事务数的方差, 越小,说明这个batch触发freeze将越平缓, 越大, 说明这个batch将有可能在某些点集中触发freeze (但是可能集中触发的都是小表)    
corr_rest_age_sz | -0.0818849525050259   -- 表大小与距离自动freeze剩余事务数的相关性,相关性越强(值趋向1或-1) stddev_rest_age 与 sz7 说明的问题越有价值    
ratio            | 70.96 %               -- 这个BATCH的容量占比,占比如果非常不均匀,说明有必要调整表级FREEZE参数,让占比均匀化    
-[ RECORD 2 ]----+--------------------    
wb               | 2    
cnt              | 2    
ssz1             | 5311 MB    
ssz2             | 5311 MB    
ssz3             | 16 GB    
ssz4             | 224 kB    
ssz5             | 5311 MB    
ssz6             | 2655 MB    
ssz7             | 3755 MB    
min_rest_age     | 199999994    
max_rest_age     | 199999994    
stddev_rest_age  | 0    
corr_rest_age_sz |     
ratio            | 29.04 %    

明细SQL

观察每个BATCH内的距离freeze剩余年龄、表大小分布

该表在该batch中的容量占比

create view v_freeze_stat_detail as      
select     
pg_size_pretty(t.ssz) as ssz2,     -- 这个batch FREEZE 会导致多少读IO (表+TOAST+索引)    
pg_size_pretty(t.ssz*3) as ssz3,   -- 这个batch FREEZE 最多可能会导致多少写IO (通常三份 : 数据文件, WAL FULL PAGE, WAL)    
pg_size_pretty(t.ssz_sum) as ssz4, -- 所有batch 所有表的总大小  (表+TOAST+索引)    
round(100*(t.ssz/t.ssz_sum), 2)||' %' as ratio_batch,     -- 这个BATCH的容量占比,目标是让所有BATCH占比尽量一致    
round(100*(pg_total_relation_size(t.reloid)/t.ssz), 2)||' %' as ratio_table,     -- 这个表占整个batch的容量占比,大表尽量错开freeze    
t.*      
from         
(    
select a.*, b.* from       
(    
  select     
    min(least(remain_ages_xid, remain_ages_mxid)) as v_min,   -- 整个数据库中离自动FREEZE的 最小 剩余事务ID数    
    max(least(remain_ages_xid, remain_ages_mxid)) as v_max    -- 整个数据库中离自动FREEZE的 最大 剩余事务ID数    
  from v_freeze     
) as a,     
LATERAL (     -- 高级SQL    
select     
  count(*) over w as cnt,                                                -- 这个batch 有多少表      
  sum(pg_total_relation_size(reloid)) over () as ssz_sum,                -- 所有batch 所有表的总大小  (表+TOAST+索引)    
  sum(pg_total_relation_size(reloid)) over w as ssz,                     -- 这个batch 的表大小总和 (表+TOAST+索引)    
  pg_size_pretty(min(pg_total_relation_size(reloid)) over w) as min_sz,  -- 这个batch 最小的表多大    
  pg_size_pretty(max(pg_total_relation_size(reloid)) over w) as max_sz,  -- 这个batch 最大的表多大    
  pg_size_pretty(avg(pg_total_relation_size(reloid)) over w) as avg_sz,  -- 这个batch 平均表多大    
  pg_size_pretty(stddev_samp(pg_total_relation_size(reloid)) over w) as stddev_sz,  -- 这个batch 表大小的方差, 越大, 说明表大小差异化明显                                                                                                                 
  min(least(remain_ages_xid, remain_ages_mxid)) over w as min_rest_age,             -- 这个batch 距离自动FREEZE最低剩余事务数                                                                                                                             
  max(least(remain_ages_xid, remain_ages_mxid)) over w as max_rest_age,             -- 这个batch 距离自动FREEZE最高剩余事务数                                                                                                                             
  stddev_samp(least(remain_ages_xid, remain_ages_mxid)) over w as stddev_rest_age,  -- 这个batch 距离自动FREEZE剩余事务数的方差, 越小,说明这个batch触发freeze将越平缓, 越大, 说明这个batch将有可能在某些点集中触发freeze (但是可能集中触发的都是小表)    
  corr(least(remain_ages_xid, remain_ages_mxid), pg_total_relation_size(reloid)) over w as corr_rest_age_sz,  -- 表大小与距离自动freeze剩余事务数的相关性,相关性越强(值趋向1或-1) stddev_rest_age 与 stddev_sz 说明的问题越有价值    
  t1.*     
from     
  (    
  select     
    width_bucket(    
      least(tt.remain_ages_xid, tt.remain_ages_mxid),     
      a.v_min,    
      a.v_max,    
      greatest((a.v_max-a.v_min)/1000000, 1)         -- 100万个事务, 如果要更改统计例如,修改这个值即可    
    )     
    as wb,                                           -- 第几个BATCH, 每个batch代表流逝100万个事务      
    * from v_freeze tt    
  ) as t1      
  window w as     
  (    
    partition by t1.wb     
  )     
) as b    
) t    
order by     
  t.wb,      
  least(t.remain_ages_xid, t.remain_ages_mxid),       
  pg_total_relation_size(t.reloid) desc       
;      

返回解释如下

-[ RECORD 1 ]----------+-------------------------------------------    
ssz2                   | 13 GB                 -- 这个batch FREEZE 会导致多少 读IO                                                       
ssz3                   | 38 GB		       -- 这个batch FREEZE 最多可能会导致多少 写IO (通常三份 : 数据文件, WAL FULL PAGE, WAL)     
ssz4                   | 18 GB		       -- 所有batch 所有表的总大小  (表+TOAST+索引)                                 
ratio_batch            | 70.96 %	       -- 这个BATCH的容量占比,目标是让所有BATCH占比尽量一致                                     
ratio_table            | 0.06 %		       -- 这个表占整个batch的容量占比,大表尽量错开freeze                                        
v_min                  | 199739844	       -- 整个数据库中离自动FREEZE的 最小 剩余事务ID数                                           
v_max                  | 200000000	       -- 整个数据库中离自动FREEZE的 最大 剩余事务ID数                                           
cnt                    | 2085		       -- 这个batch 有多少表                                                                     
ssz_sum                | 19177472000	       -- 所有batch 所有表的总大小        (表+TOAST+索引)                                        
ssz                    | 13608812544	       -- =ssz2 这个batch 的表大小总和    (表+TOAST+索引)                                        
min_sz                 | 32 kB		       -- 这个batch 最小的表多大                                                                 
max_sz                 | 6326 MB	       -- 这个batch 最大的表多大                                                                 
avg_sz                 | 6374 kB	       -- 这个batch 平均表多大                                      
stddev_sz              | 184 MB                -- 这个batch 表大小的方差, 越大, 说明表大小差异化明显                                                                                                                 
min_rest_age           | 199739844	       -- 这个batch 距离自动FREEZE最低剩余事务数                                                                                                                             
max_rest_age           | 199999998	       -- 这个batch 距离自动FREEZE最高剩余事务数                                                                                                                             
stddev_rest_age        | 19486.91462976	       -- 这个batch 距离自动FREEZE剩余事务数的方差, 越小,说明这个batch触发freeze将越平缓, 越大, 说明这个batch将有可能在某些点集中触发freeze (但是可能集中触发的都是小表)    
corr_rest_age_sz       | -0.079332345436958    -- 表大小与距离自动freeze剩余事务数的相关性,相关性越强(值趋向1或-1) stddev_rest_age 与 stddev_sz 说明的问题越有价值    
wb                     | 1                     -- 第几个BATCH, 每个batch代表流逝100万个事务     
sz                     | 8352 kB               -- 表的大小(含TOAST, 索引)                                                                                                                                                         
reloid                 | pg_attribute	       -- 表名(物化视图)                                                                                                                                
relkind                | r		       -- r=表, m=物化视图                                                                                                                                                                
remain_ages_xid        | 199739844	       -- 再产生多少个事务后, 自动垃圾回收会触发FREEZE, 起因为事务ID                                                                                                                      
remain_ages_mxid       | 		       -- 再产生多少个事务后, 自动垃圾回收会触发FREEZE, 起因为并发事务ID                                                                                                                  
xid_lower_to_minage    | 0		       -- 如果触发FREEZE, 该表的事务ID年龄会降到多少                                                                                                                                      
mxid_lower_to_minage   | 0		       -- 如果触发FREEZE, 该表的并行事务ID年龄会降到多少                                                                                                                                  
vacuum_trigger_freeze1 | YES		       -- 如果手工执行VACUUM, 是否会触发FREEZE, 触发起因(事务ID年龄达到阈值)                                                                                                              
vacuum_trigger_freeze2 | NOT		       -- 如果手工执行VACUUM, 是否会触发FREEZE, 触发起因(并行事务ID年龄达到阈值)                                                                                                          
reloptions             | 		       -- 表级参数, 优先. 例如是否开启自动垃圾回收, autovacuum_freeze_max_age, autovacuum_freeze_table_age, autovacuum_multixact_freeze_max_age, autovacuum_multixact_freeze_table_age    
v1                     | 200000000	       -- 如果表的事务ID年龄大于该值, 即使未开启autovacuum也会强制触发FREEZE, 并告警Preventing Transaction ID Wraparound Failures                                                         
v2                     | 400000000	       -- 如果表的并行事务ID年龄大于该值, 即使未开启autovacuum也会强制触发FREEZE, 并告警Preventing Transaction ID Wraparound Failures                                                     
v3                     | 0		       -- 手动或自动垃圾回收时, 如果记录的事务ID年龄大于该值, 将被FREEZE                                                                                                                  
v4                     | 0		       -- 手动或自动垃圾回收时, 如果记录的并行事务ID年龄大于该值, 将被FREEZE                                                                                                              
v5                     | 200		       -- 手动垃圾回收时, 如果表的事务ID年龄大于该值, 将触发FREEZE. 该参数的上限值为 %95 autovacuum_freeze_max_age                                                                        
v6                     | 150000000	       -- 手动垃圾回收时, 如果表的并行事务ID年龄大于该值, 将触发FREEZE. 该参数的上限值为 %95 autovacuum_multixact_freeze_max_age                                                          
v7                     | 0		       -- 自动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源. -1 表示沿用vacuum_cost_delay的设置                                                                      
v8                     | -1		       -- 自动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定. -1 表示沿用vacuum_cost_limit的设置      
v9                     | 0		       -- 手动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源.                                                                                                         
v10                    | 200		       -- 手动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定.                                         
autovacuum             | on		       -- 是否开启自动垃圾回收                                                                                                                                                            

小结

通过本文提供的三个视图,我们可以知道每个表,在什么时候会触发自动FREEZE,会产生多少IO。FREEZE的后台进程调度参数如何。哪些表在手动支持VACUUM时会触发FREEZE。

本文内容涵盖GC(垃圾回收知识),统计学,高级SQL。等知识。

使用本文提到的SQL,你可以成为PG FREEZE风暴的先知,躲避风暴,掌握PG的垃圾回收机制,了解平滑的GC策略。

视图1 (v_freeze) :

按风暴来临的时间顺序排序,返回每个剩余多少事务会触发自动FREEZE,详细信息如下

-- 表的大小(含TOAST, 索引)                                                                                                                                                         
-- 表名(物化视图)                                                                                                                                
-- r=表, m=物化视图                                                                                                                                                                
-- 再产生多少个事务后, 自动垃圾回收会触发FREEZE, 起因为事务ID                                                                                                                      
-- 再产生多少个事务后, 自动垃圾回收会触发FREEZE, 起因为并发事务ID                                                                                                                  
-- 如果触发FREEZE, 该表的事务ID年龄会降到多少                                                                                                                                      
-- 如果触发FREEZE, 该表的并行事务ID年龄会降到多少                                                                                                                                  
-- 如果手工执行VACUUM, 是否会触发FREEZE, 触发起因(事务ID年龄达到阈值)                                                                                                              
-- 如果手工执行VACUUM, 是否会触发FREEZE, 触发起因(并行事务ID年龄达到阈值)                                                                                                          
-- 表级参数, 优先. 例如是否开启自动垃圾回收, autovacuum_freeze_max_age, autovacuum_freeze_table_age, autovacuum_multixact_freeze_max_age, autovacuum_multixact_freeze_table_age    
-- 如果表的事务ID年龄大于该值, 即使未开启autovacuum也会强制触发FREEZE, 并告警Preventing Transaction ID Wraparound Failures                                                         
-- 如果表的并行事务ID年龄大于该值, 即使未开启autovacuum也会强制触发FREEZE, 并告警Preventing Transaction ID Wraparound Failures                                                     
-- 手动或自动垃圾回收时, 如果记录的事务ID年龄大于该值, 将被FREEZE                                                                                                                  
-- 手动或自动垃圾回收时, 如果记录的并行事务ID年龄大于该值, 将被FREEZE                                                                                                              
-- 手动垃圾回收时, 如果表的事务ID年龄大于该值, 将触发FREEZE. 该参数的上限值为 %95 autovacuum_freeze_max_age                                                                        
-- 手动垃圾回收时, 如果表的并行事务ID年龄大于该值, 将触发FREEZE. 该参数的上限值为 %95 autovacuum_multixact_freeze_max_age                                                          
-- 自动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源. -1 表示沿用vacuum_cost_delay的设置                                                                      
-- 自动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定. -1 表示沿用vacuum_cost_limit的设置      
-- 手动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源.                                                                                                         
-- 手动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定.                                         
-- 是否开启自动垃圾回收                                                                                                                                                            

视图2 (v_freeze_stat) :

统计视图,返回每N个事务为间隔,每个间隔中,有多少表要被FREEZE,表的总容量多少大。

-- 第几个BATCH, 每个batch代表流逝100万个事务                                                                                                                         
-- 这个batch 有多少表                                                                                                                                                
-- 这个batch 这些 表+TOAST+索引 有多少容量                                                                                                                           
-- 这个batch FREEZE 会导致多少 读IO                                                                                                                                  
-- 这个batch FREEZE 最多可能会导致多少 写IO (通常三份 : 数据文件, WAL FULL PAGE, WAL)                                                                                
-- 这个batch 最小的表多大                                                                                                                                            
-- 这个batch 最大的表多大                                                                                                                                            
-- 这个batch 平均表多大                                                                                                                                              
-- 这个batch 表大小的方差, 越大, 说明表大小差异化明显                                                                                                                
-- 这个batch 距离自动FREEZE最低 剩余事务数                                                                                                                           
-- 这个batch 距离自动FREEZE最高 剩余事务数                                                                                                                           
-- 这个batch 距离自动FREEZE剩余事务数的方差, 越小,说明这个batch触发freeze将越平缓, 越大, 说明这个batch将有可能在某些点集中触发freeze (但是可能集中触发的都是小表)   
-- 表大小与距离自动freeze剩余事务数的相关性,相关性越强(值趋向1或-1) stddev_rest_age 与 sz7 说明的问题越有价值                                                       
-- 这个BATCH的容量占比,占比如果非常不均匀,说明有必要调整表级FREEZE参数,让占比均匀化                                                                               

视图3 (v_freeze_stat_detail) :

1,2结合的明细视图,可以用于巡视哪些表的FREEZE参数需要被调整。

-- 这个batch FREEZE 会导致多少 读IO                                                                                                                                               
-- 这个batch FREEZE 最多可能会导致多少 写IO (通常三份 : 数据文件, WAL FULL PAGE, WAL)                                                                                             
-- 所有batch 所有表的总大小  (表+TOAST+索引)                                                                                                                                      
-- 这个BATCH的容量占比,目标是让所有BATCH占比尽量一致                                                                                                                             
-- 这个表占整个batch的容量占比,大表尽量错开freeze                                                                                                                                
-- 整个数据库中离自动FREEZE的 最小 剩余事务ID数                                                                                                                                   
-- 整个数据库中离自动FREEZE的 最大 剩余事务ID数                                                                                                                                   
-- 这个batch 有多少表                                                                                                                                                             
-- 所有batch 所有表的总大小        (表+TOAST+索引)                                                                                                                                
-- =ssz2 这个batch 的表大小总和    (表+TOAST+索引)                                                                                                                                
-- 这个batch 最小的表多大                                                                                                                                                         
-- 这个batch 最大的表多大                                                                                                                                                         
-- 这个batch 平均表多大                                                                                                                                                           
-- 这个batch 表大小的方差, 越大, 说明表大小差异化明显                                                                                                                             
-- 这个batch 距离自动FREEZE最低剩余事务数                                                                                                                                         
-- 这个batch 距离自动FREEZE最高剩余事务数                                                                                                                                         
-- 这个batch 距离自动FREEZE剩余事务数的方差, 越小,说明这个batch触发freeze将越平缓, 越大, 说明这个batch将有可能在某些点集中触发freeze (但是可能集中触发的都是小表)                
-- 表大小与距离自动freeze剩余事务数的相关性,相关性越强(值趋向1或-1) stddev_rest_age 与 stddev_sz 说明的问题越有价值                                                              
-- 第几个BATCH, 每个batch代表流逝100万个事务                                                                                                                                      
-- 表的大小(含TOAST, 索引)                                                                                                                                                        
-- 表名(物化视图)                                                                                                                                                                 
-- r=表, m=物化视图                                                                                                                                                               
-- 再产生多少个事务后, 自动垃圾回收会触发FREEZE, 起因为事务ID                                                                                                                     
-- 再产生多少个事务后, 自动垃圾回收会触发FREEZE, 起因为并发事务ID                                                                                                                 
-- 如果触发FREEZE, 该表的事务ID年龄会降到多少                                                                                                                                     
-- 如果触发FREEZE, 该表的并行事务ID年龄会降到多少                                                                                                                                 
-- 如果手工执行VACUUM, 是否会触发FREEZE, 触发起因(事务ID年龄达到阈值)                                                                                                             
-- 如果手工执行VACUUM, 是否会触发FREEZE, 触发起因(并行事务ID年龄达到阈值)                                                                                                         
-- 表级参数, 优先. 例如是否开启自动垃圾回收, autovacuum_freeze_max_age, autovacuum_freeze_table_age, autovacuum_multixact_freeze_max_age, autovacuum_multixact_freeze_table_age   
-- 如果表的事务ID年龄大于该值, 即使未开启autovacuum也会强制触发FREEZE, 并告警Preventing Transaction ID Wraparound Failures                                                        
-- 如果表的并行事务ID年龄大于该值, 即使未开启autovacuum也会强制触发FREEZE, 并告警Preventing Transaction ID Wraparound Failures                                                    
-- 手动或自动垃圾回收时, 如果记录的事务ID年龄大于该值, 将被FREEZE                                                                                                                 
-- 手动或自动垃圾回收时, 如果记录的并行事务ID年龄大于该值, 将被FREEZE                                                                                                             
-- 手动垃圾回收时, 如果表的事务ID年龄大于该值, 将触发FREEZE. 该参数的上限值为 %95 autovacuum_freeze_max_age                                                                       
-- 手动垃圾回收时, 如果表的并行事务ID年龄大于该值, 将触发FREEZE. 该参数的上限值为 %95 autovacuum_multixact_freeze_max_age                                                         
-- 自动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源. -1 表示沿用vacuum_cost_delay的设置                                                                     
-- 自动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定. -1 表示沿用vacuum_cost_limit的设置     
-- 手动垃圾回收时, 每轮回收周期后的一个休息时间, 主要防止垃圾回收太耗资源.                                                                                                        
-- 手动垃圾回收时, 每轮回收周期设多大限制, 限制由vacuum_cost_page_hit,vacuum_cost_page_missvacuum_cost_page_dirty参数以及周期内的操作决定.                                        
-- 是否开启自动垃圾回收                                                                                                                                                           

PostgreSQL内核层面可以做到更加智能,比如有更好的GC调度策略,让系统的VACUUM FREEZE更加平滑,不会出现集中爆发的情况。

流程附录

一、自动触发FREEZE流程

1、 优先使用 表级参数判断是否需要强制AUTO FREEZE(prevent xid wrapped)

2、 再使用 系统级参数判断是否需要强制AUTO FREEZE(prevent xid wrapped)

3、 AUTO VACUUM FREEZE开始

4、 跳过VM中标识为已FREEZE的PAGE

5、 SLEEP 调度

autovacuum_vacuum_cost_delay = 0        # default vacuum cost delay for  
                                        # autovacuum, in milliseconds;  
                                        # -1 means use vacuum_cost_delay  
#autovacuum_vacuum_cost_limit = -1      # default vacuum cost limit for  
                                        # autovacuum, -1 means use  
                                        # vacuum_cost_limit  

6、 年龄下降到(表级设定、系统级设定)

二、手动触发FREEZE流程

1、 判断当前表年龄是否已超出系统级FREEZE参数限制

2、 vacuum FREEZE

3、 跳过VM中标识为已FREEZE的PAGE

4、 SLEEP 调度

vacuum_cost_delay = 0                   # 0-100 milliseconds  
#vacuum_cost_page_hit = 1               # 0-10000 credits  
#vacuum_cost_page_miss = 10             # 0-10000 credits  
#vacuum_cost_page_dirty = 20            # 0-10000 credits  
#vacuum_cost_limit = 200                # 1-10000 credits  

5、 年龄下降到(表级设定、系统级设定)

三、批量导入数据时,可以选择是否自动FREEZE

https://www.postgresql.org/docs/devel/static/sql-copy.html

COPY table_name [ ( column_name [, ...] ) ]  
    FROM { 'filename' | PROGRAM 'command' | STDIN }  
    [ [ WITH ] ( option [, ...] ) ]  
  
COPY { table_name [ ( column_name [, ...] ) ] | ( query ) }  
    TO { 'filename' | PROGRAM 'command' | STDOUT }  
    [ [ WITH ] ( option [, ...] ) ]  
  
where option can be one of:  
  
    FORMAT format_name  
    OIDS [ boolean ]  
    FREEZE [ boolean ]           ## 就是它  
    DELIMITER 'delimiter_character'  
    NULL 'null_string'  
    HEADER [ boolean ]  
    QUOTE 'quote_character'  
    ESCAPE 'escape_character'  
    FORCE_QUOTE { ( column_name [, ...] ) | * }  
    FORCE_NOT_NULL ( column_name [, ...] )  
    FORCE_NULL ( column_name [, ...] )  
    ENCODING 'encoding_name'  

参考

《PostgreSQL freeze 风暴导致的IOPS飙升 - 事后追溯》

《PostgreSQL 9.6 vacuum freeze大幅性能提升 代码浅析》

《PostgreSQL 大表自动 freeze 优化思路》

《影响或控制PostgreSQL垃圾回收的参数或因素》

《PostgreSQL物理”备库”的哪些操作或配置,可能影响”主库”的性能、垃圾回收、IO波动》

《PostgreSQL 老湿机图解平安科技遇到的垃圾回收”坑”》

《PostgreSQL垃圾回收代码分析 - why postgresql cann’t reclaim tuple is HEAPTUPLE_RECENTLY_DEAD》

《PostgreSQL 垃圾回收原理以及如何预防膨胀 - How to prevent object bloat in PostgreSQL》

https://www.postgresql.org/docs/devel/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND

https://www.postgresql.org/docs/devel/static/runtime-config-client.html#GUC-VACUUM-FREEZE-TABLE-AGE

https://www.postgresql.org/docs/devel/static/sql-createtable.html

Flag Counter

digoal’s 大量PostgreSQL文章入口