天天看点

PostgreSQL 9.6 快照过旧 - 源码浅析

digoal

2016-10-05

postgresql , 9.6 , 快照过旧 , snapshot too old

在postgresql 9.6以前,垃圾回收存在的问题。

当vacuum回收垃圾时,遇到垃圾记录的xmax大于数据库中现存的最早未提交事务xmin时,不会对其进行回收。

因此当数据库中存在很久为结束的事务时,可能会导致数据库膨胀。

PostgreSQL 9.6 快照过旧 - 源码浅析

postgresql 9.6加入了快照过旧的功能,目的是防止过长的事务导致数据库膨胀。

PostgreSQL 9.6 快照过旧 - 源码浅析

那么如何配置快照过旧,什么情况选会导致snapshot too old呢?

src/include/utils/snapshot.h

对于repeatable read与serializable隔离级别的事务来说,第一个query开始时需要获取一个快照,用于可见性判断。

对于read committed隔离级别的事务来说,事务中每条query开始都会重新获取一个快照。

快照中将包含一个xmin值,表示当前数据库中最小的正在运行的事务号,如果没有,则为最小未分配事务号。

快照xmin与事务申请的事务号有别,在pg_stat_activity视图中用两个分开的字段表示:

xid表示该事务申请的事务号,是消耗掉的。

快照中xid与xmin的数据结构如下

在pg_stat_activity中存储的xmin,xid结构如下

举一些例子帮助了解快照中的xid与xmin。

1. repeatable read与serializable隔离级别的事务,事务的第一条sql会获取快照,快照持续到事务结束释放。

2. read committed隔离级别的事务,事务的每一条sql会获取快照,sql执行结束就会释放快照。

以上例子解释了事务快照中的xmin, xid的含义,大家熟悉后,就可以看postgresql 9.6的改进了。

快照中的lsn表示获取快照时wal的写入位置,whentaken表示获取快照时的时间

1. src/include/storage/bufmgr.h

2. src/backend/storage/buffer/bufmgr.c

3. src/backend/utils/time/snapmgr.c

获取old_snapshot_threshold配置的时间

guc变量old_snapshot_threshold,表示多少分钟以前的xmin可能会被垃圾回收时清除掉。

报错与old_snapshot_threshold快照相关信息的数据结构

query获取快照时,会更新这个结构的值,开启快照过旧的功能,目前有重度性能影响(指高并发的场景)

配置old_snapshot_threshold参数,需要重启数据库

postgresql 9.6目前解决的问题是长时间持有backend_xmin的问题,它并不关心是否存在backend_xid。

1. 包含xmin,没有申请xid的只读事务

当持有xmin的query执行时间超过old_snapshot_threshold设置的阈值,并且读取到数据块的lsn大于快照存储的lsn时,报snapshot too old错误。

2. 已申请xid的写repeatable read/serializable事务,由于持有了xmin,一样可能出现snapshot too old。

3. 已申请xid的read committed写事务,由于query开始时会重新生成快照,所以通常query持有的快照lsn大于或等于访问到的page的lsn,则不会出现snapshot too old。

包含了backend_xmin的事务,只要sql的执行时间超过old_snapshot_threshold阈值,并且该sql读取到了lsn超过快照存储的lsn的数据块时。

1. snapshot too old报错通常出现在非常大的sql,同时读取的数据块在不断的变化。

2. snapshot too old也可能出现在pg_dump备份数据库时,因为pg_dump使用的是repeatable read隔离级别,快照是在事务启动后的第一条sql创建的,备份时间长的话,极有可能在备份过程中读取到lsn大于快照lsn的数据块,导致snapshot too old报错。

1. 在9.6以前,这些垃圾tuple不能回收?

postgresql通过vacuum回收垃圾tuple时,判断哪些dead tuple可以被回收,哪些不能被回收有一个很简单的规则。

产生垃圾tuple的事务号(通常在为垃圾tuple的头信息中的xmax版本号)大于或等于vacuum开启时数据库中最小的(backend_xmin, backend_xid),这条垃圾tuple就不能被回收。

9.5版本的例子

如果配置了old_snapshot_threshold.

则需要记录每分钟最大的backend_xid(没有则取最小未分配事务号) list(而非backend_xmin),vacuum时用于判断。

vacuum时,在(1. old_snapshot_threshold最老的记录 与 2.当前系统最小的backend_xmin, backend_xid)中取最大值,超过这个值的垃圾不回收。

使用这种方法可以避免在此之前的,长时间持有xmin的事务或sql,导致对其以后产生的垃圾无法回收的问题。

PostgreSQL 9.6 快照过旧 - 源码浅析

测试

1. 长时间持有xid,9.6依旧无法回收

2. 长时间持有xmin,但是不持有xid时,9.6可以回收其垃圾( 必须配置 old_snapshot_threshold > 0 , =0没有 时间<->xmin list所以vacuum使用了backend_xmin?)

对于有backend_xid的read committed写事务,9.6无法回收大于该xid的垃圾tuple,正常的理解是此后的query都不应该报snapshot too old错误。

但是,9.6的snapshot too old报错是和快照when超过阈值,并且快照lsn大于被访问的block时就会报错。

而实际上,这种情况不应该报snapshot too old。

矛盾例子

开启快照过旧,对性能有影响。

因为每次获取快照都要更新snapshot too old需要用到的数据结构。

同时get buffer page时,每次都要判断,是否满足快照过旧。

依旧有优化的空间。

4. src/backend/storage/ipc/procarray.c

old_snapshot_threshold (integer)

<a href="http://info.flagcounter.com/h9v1">count</a>