背景
1、产品的问题点
- 事务号xid是uint32类型, 最多只能存储40亿个事务号, 事务号必须循环使用.
2、问题点背后涉及的技术原理
- 事务号是事务的标记, 在row的头信息中存储着这条记录是由哪个事务写入、更新、删除的. 数据库需要通过事务号来判断事务是过去的还是未来的事务.
- 大于当前事务号、或者大于当前事务快照最小未分配事务号的都是未来的事务, 对当前query不可见.
由于xid只能存储40亿个事务, 所以很快就会耗尽, PG为了解决耗尽问题, XID就需要重复使用, 怎么重复使用呢?
- 通过设置一个全局的frozenxid, 将XID的40亿切成2半, 把XID的可用空间想象成一个圆, frozenxid就在这个圆上移动, 处于它顺时针方向的一半属于已消耗的事务号(在过去), 处于它逆时针方向的一半属于可分配的事务号(在未来).
- 随着数据库事务发起xid不断消耗, 这个 frozenxid 必须在事务号消耗20亿之前发生移动, 否则就变成全部都是已分配事务了.
frozenxid怎么移动呢?
- 为了让frozenxid移动, 我们必须对全集群的表进行扫描, 把已有的记录头信息中设置对应的bit位, 表示这条记录已经是frozen的, 因此这个集群的frozenxid就在圆上往前移动了. 它可以使用的事务号半圆内又有足够的事务号可以分配.
- frozenxid在圆上 老的位置 与 新的位置 这段区间内的xid就是被标记了了bit的事务.
什么时候会触发frozen操作呢?
- autovacuum会定时(参数设定)扫描系统表, 发现达到阈值的表会触发frozen操作.
3、这个问题将影响哪些行业以及业务场景
- 频繁更新的业务. 和MVCC那一期一样.
4、会导致什么问题?
- frozen操作需要扫描全表, 会产生较大IO.
- 如果frozen不及时, 严重的(如剩余可分配事务号低于100万时)要停库进入单用户模式执行frozen.
- 如果大量的表在一个较小的时间窗口内都触发了frozen, 那么会发生frozen风暴, 主库IO使用量暴增, 导致性能问题, 同时如果产生大量wal日志, 会导致从库复制延迟.
5、业务上应该如何避免这个坑
- PG 内核的优化: 对于已frozen后没有变化过的PAGE可以跳过以减少扫描. 所以更新不频繁的系统frozen发生时系统的消耗并不高.
- 海量静态数据写入时即设置为freeze标记位, 避免二次freeze.
- 使用更好的SSD, 加速frozen, 降低frozen IO影响.
- 主、从库之间采用更大带宽的网络
- 设置autovacuum sleep间隙, 降低frozen IO影响.
- 不同的表, 或者分区, 不同分区设置不同的frozen阈值, 避免同时发生frozen操作产生风暴.
6、业务上避免这个坑牺牲了什么, 会引入什么新的问题
- 增加了管理成本
- 增加了硬件成本
7、数据库未来产品迭代如何修复这个坑
- 64位xid
- 例如 zedstore, zheap, postgrespro 等引擎.