PostgreSQL10.x版本relcache刷新异常问题分析

一、问题背景

使用过pg10.x的小伙伴,一定了解relcache刷新异常这个BUG。通常的表现是vacuum/autovacuum异常,会发生表膨胀;数据库不能进行事务号回收,甚至导致事务回卷。

根据以往的经验,我们见到过以下这些场景:

1.pglog中存在类似vacuum报错:

ERROR:  found xmin 10xxxxx from before relfrozenxid 21xxxxxxx

2.不存在长事务的情况下,多个表发生膨胀

3.$PGDATA/global目录下存在大量的pg_internal.init文件

通常我们可以通过删除pg_internal.init文件或者重启数据库来临时规避问题,将数据库版本升级至10.11可以解决此问题。

之前在平安集团时就参与了大量10.x版本实例到10.11版本升级,这篇博文简单分析下问题原理。

二、原理分析

PostgreSQL中存在两种高速缓存:syscache和relcache。syscache主要用于缓存系统表元组;relcache中包含所有访问过的表的模式信息(包含系统表)。这两个缓存在数据库中不是共享的,是每个进程独有的,通过共享消息队列来进行同步。

主要介绍下与问题相关的relcache,初始化在initpostgres时完成;其中存储的内容(RelationData)会记录到本地文件pg_internal.init中;失效和刷新常发生于执行heap_delete和heap_update类似操作后,会有对应的机制进行缓存刷新。

之前提到是relcache刷新存在bug,导致vacuum异常。鉴于篇幅问题,这里只分析这个bug是什么,怎么修改的;不再展开为什么会影响到vacuum。

重点分析两个问题

  1. pg10.11版本做了什么改动,解决了这个问题?
  2. 为什么重启可以临时规避这个问题?

这里选用10.3版本和10.11版本进行比较
首先来看问题1,10.11中做了哪些改动解决了此问题

pg10.3:
在这里插入图片描述

pg10.11:
在这里插入图片描述

RegisterRelcacheInvalidation这个函数主要用来判断initfile是否已经失效。

If判断条件是比较直观的,当已经记录在initfile中的relId(即表oid)已经为InvalidOid并且OidIsValid(dbId)为true,即databaseOid为valid,这种情况下,那么就断定initfile内容失效,需要进行刷新。

对比两个版本RegisterRelcacheInvalidation函数,可以看到对于initfile是否已经失效的if判断条件,10.11中去掉了OidIsValid(dbId)这个条件

那顺着反推下,肯定是由于在某些情况下,会导致OidIsValid(dbId)为false,导致整个if条件&&运算结果为假,造成了缓存失效判断错误,不去刷新缓存。

来看什么情况下会出现OidIsValid(dbId)返回false。

OidIsValid(dbId)宏定义如下:

#define OidIsValid(objectId)  ((bool) ((objectId) != InvalidOid))

来看CacheInvalidateRelcache函数中的场景,当传入的relation的relisshared属性为true时,就会将databaseId赋值为InvalidOid,传参至RegisterRelcacheInvalidation函数中,OidIsValid(dbId)为false,不会标记initfile已经失效。

在这里插入图片描述

那么哪些relation的relisshared属性为true?

rd_rel定义如下:

Form_pg_class rd_rel;		/* RELATION tuple */

可以看到这里的rd_rel是从pg_class中查询到的信息,那么可以通过系统表查询哪些表的relisshared属性为true。

postgres=# select relname,relisshared  from pg_class where relisshared='t';
                 relname                 | relisshared 
-----------------------------------------+-------------
 pg_authid                               | t
 pg_subscription                         | t
 pg_database                             | t
 pg_db_role_setting                      | t
 pg_tablespace                           | t
 pg_pltemplate                           | t
 pg_auth_members                         | t
 pg_shdepend                             | t
 pg_shdescription                        | t
 pg_replication_origin                   | t
 pg_shseclabel                           | t

可以看到结果中包含pg_authid,pg_database等。relisshared属性大概含义为,当表的某些数据在所有database中是共享的时,该属性值为ture。

NameTypeReferencesDescription
relissharedboolTrue if this table is shared across all databases in the cluster. Only certain system catalogs (such as pg_database) are shared

最早提到过relcache中会缓存一些系统表,pg_authid和pg_database这些是基础的系统表,肯定是需要放进缓存的。

结合以上的分析,那么OidIsValid(dbId)出现在判断条件中,就不合理,因此在10.11中去除了这个条件。

接下来看下问题2,为什么重启可以临时规避问题呢?

是因为重启时会删除之前的initfile,相当于强制刷新relcache。

来看下删除逻辑:在startup流程中会调用RelationCacheInitFileRemove删除文件。
在这里插入图片描述

RelationCacheInitFileRemove函数:

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PostgreSQL 10 中,`postgresql.conf` 文件用于指定 PostgreSQL 数据库服务器的全局配置选项。以下是一些常见的配置选项: 1. `listen_addresses`:指定数据库服务器要监听的 IP 地址或主机名。如果要允许来自所有 IP 地址的连接,则可以将其设置为 `*`。 ``` listen_addresses = '*' ``` 2. `port`:指定数据库服务器要监听的端口号。 ``` port = 5432 ``` 3. `max_connections`:指定数据库服务器同时处理的最大客户端连接数。 ``` max_connections = 100 ``` 4. `shared_buffers`:指定 PostgreSQL 要使用的共享内存缓冲区大小。该值应该足够大,以便在内存中缓存常用的数据。 ``` shared_buffers = 1GB ``` 5. `work_mem`:指定 PostgreSQL 中每个排序和哈希操作使用的内存缓冲区大小。该值应该足够大,以便在内存中缓存排序和哈希操作所需的数据。 ``` work_mem = 64MB ``` 6. `maintenance_work_mem`:指定 PostgreSQL 执行维护操作时使用的内存缓冲区大小。例如,当执行 VACUUM 操作时,该值应该足够大,以便在内存中缓存需要清理的数据。 ``` maintenance_work_mem = 256MB ``` 7. `effective_cache_size`:指定 PostgreSQL 估计的系统缓存大小。该值应该足够大,以便 PostgreSQL 可以利用系统缓存中的数据。 ``` effective_cache_size = 4GB ``` 8. `wal_level`:指定 WAL 日志的详细程度。可以设置为 `minimal`、`replica` 或 `logical`。 ``` wal_level = replica ``` 9. `max_wal_senders`:指定主服务器能够向从服务器发送 WAL 日志的最大数量。 ``` max_wal_senders = 10 ``` 以上是 PostgreSQL 10 中 `postgresql.conf` 文件的一些常见配置选项。这些选项可以根据具体的需求进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值