我们知道max_replication_slots是一个重要的参数,它代表复制槽的最大个数。这个参数的修改必须重启数据库实例才行。在实践中,我们必须把这个参数的值设置的足够大,才能避免未来重启数据库。它的缺省值是10,对于真实的生产环境是不够的,建议把它调整为32或者64。
问题来了,如果增加这个参数的值,会带来什么开销呢?我们直接撸源代码。
在src/backend/postmaster/postmaster.c中有一个函数是在启动数据库实例时创建共享内存。这个内存一旦创建好了,就不能改变,除非重启数据库:
/*
* Set up shared memory and semaphores.
*
* Note: if using SysV shmem and/or semas, each postmaster startup will
* normally choose the same IPC keys. This helps ensure that we will
* clean up dead IPC objects if the postmaster crashes and is restarted.
*/
CreateSharedMemoryAndSemaphores();
我们顺藤摸瓜,找到这个函数的定义在src/backend/storage/ipc/ipci.c中:
/*
* CreateSharedMemoryAndSemaphores
* Creates and initializes shared memory and semaphores.
*/
void
CreateSharedMemoryAndSemaphores(void)
{
PGShmemHeader *shim;
PGShmemHeader *seghdr;
Size size;
int numSemas;
Assert(!IsUnderPostmaster);
/* Compute the size of the shared-memory block */
size = CalculateShmemSize(&numSemas);
elog(DEBUG3, "invoking IpcMemoryCreate(size=%zu)", size);
/*
* Create the shmem segment
*/
seghdr = PGSharedMemoryCreate(size, &shim);
我们可以看到,CalculateShmemSize()函数计算整个共享内存的尺寸,我们继续追踪这个函数的实现:
size = add_size(size, ReplicationSlotsShmemSize());
函数ReplicationSlotsShmemSize()计算复制槽的大小,我们查看这个函数,在src/backend/replication/slot.c中:
/*
* Report shared-memory space needed by ReplicationSlotsShmemInit.
*/
Size
ReplicationSlotsShmemSize(void)
{
Size size = 0;
if (max_replication_slots == 0)
return size;
size = offsetof(ReplicationSlotCtlData, replication_slots);
size = add_size(size,
mul_size(max_replication_slots, sizeof(ReplicationSlot)));
return size;
}
在上述函数的逻辑中,我们看到,最终复制槽的尺寸 size = a + m * n,其中
a = offsetof(ReplicationSlotCtlData, replication_slots);
m = max_replication_slots
n = sizeof(ReplicationSlot)
我们写一个小程序,把这些数据结构提取出来,就可以容易知道如果增加max_replication_slots会带来多少字节的共享内存的开销了。假设max_replication_slots的当前值是x, 我们把它增加到y,增加的共享内存尺寸就是:
(y - x) * sizeof(ReplicationSlot)
所以关键是看结构体ReplicationSlot占多少个字节。如何查看这个结构体占据多少字节呢?你可以使用笨方法,把它里面的各个字段占多少个字节统计一下即可。下面我介绍一个简单的方法,直接修改PG的源码,利用GDB来下断点,查看它的大小。
wget https://ftp.postgresql.org/pub/source/v18beta1/postgresql-18beta1.tar.bz2 ### 下载最新的源代码包
tar jxvf postgresql-18beta1.tar.bz2 ### 解压缩
cd postgresql-18beta1 ### 进入源代码包中
vi src/backend/postmaster/postmaster.c ### 编辑这个文件,加入如下一行
#include "replication/slotsync.h"
#include "replication/slot.h" //这一行是新加的
#include "replication/walsender.h"
然后在下面增加几行
/*
* Postmaster main entry point
*/
void
PostmasterMain(int argc, char *argv[])
{
int opt;
int status;
char *userDoption = NULL;
bool listen_addr_saved = false;
char *output_config_variable = NULL;
size_t rs = sizeof(ReplicationSlot); // 这一行是我加的
if(rs == 0) // 也是我加的
return; // 也是我加的,避免编译器抱怨。
InitProcessGlobals();
### 然后三部曲编译源代码
./configure --prefix=/home/postgres/pg18b1 --enable-debug --enable-cassert CFLAGS="-O0" --without-icu
make
make install
### 使用gdb调试/home/postgres/pg18b1/bin/postgres这个程序:
gdb -tui /home/postgres/pg18b1/bin/postgres
b PostmasterMain ## 在这个函数下断点
r ## 执行到断点,然后单步执行
s ## 单步执行,获得rs的值
p rs ## 打印rs的值
结果看到rs的值为280
通过gdb,我们知道结构体ReplicationSlot的大小为280个字节。如果你把max_replication_slots从缺省值10变成32,增加了 22 * 280 = 6160个字节,开销是极小的。
结论:
建议把max_replication_slots调整为32,避免未来重启数据库。