PostgreSQL码农集散地

Heikki 出手,重构 PG 共享内存子系统

Heikki 出手重构 PG 共享内存子系统:统一注册机制

本文档解读 Heikki Linnakangas 提交的共享内存子系统重构,该重构引入了统一的共享内存区域注册机制,使 pg_shmem_allocations 视图更加完整,便于 DBA 诊断内存使用问题。

相关 Commit

Commit
描述
1bdbb211bbf3
Make ShmemIndex visible in the pg_shmem_allocations view
1fc2e9fbc0a3d
Introduce a registry of built-in shmem subsystems
283e823f9dcb
Introduce a new mechanism for registering shared memory areas
9b5acad3f40fa
Convert all remaining subsystems to use the new shmem allocation API
a4b6139dcceb4
Convert buffer manager to use the new shmem allocation functions
a006bc7b1699d
Convert lwlock.c to use the new shmem allocation functions
d4885af3d6532
Convert pg_stat_statements to use the new shmem allocation functions

核心价值

  • 可观测性:pg_shmem_allocations 视图现在可见 ShmemIndex
  • 统一接口:所有子系统使用相同的共享内存分配 API
  • 更好的诊断:便于识别内存泄漏或异常内存使用

原理详解

1. 共享内存索引 (ShmemIndex)

1.1 ShmemIndex 定义 (src/backend/storage/ipc/shmem.c line 250-272)

ShmemIndex 是一个全局的共享内存区域目录,本身也存储在共享内存中:

/*
 * ShmemIndex 是一个哈希表
 * 用于快速查找共享内存中的各个区域
 */

static HTAB *ShmemIndex;

/* 哈希表条目结构 */
typedefstructShmemIndexEnt
{

char    key[SHMEM_INDEX_KEYSIZE];  /* 字符串名称(最多48字符)*/
void   *location;                   /* 共享内存中的位置 */
    Size    size;                       /* 请求的字节数 */
    Size    allocated_size;             /* 实际分配的字节数 */
} ShmemIndexEnt;

1.2 访问方式

ShmemIndex 通过内部函数访问,不对外公开。子系统通过 ShmemRequestStruct() 和 ShmemRequestHash() 注册后,由 ShmemInitStruct() 分配和返回指针。

2. 共享内存请求机制

2.1 ShmemRequestStructWithOpts (src/backend/storage/ipc/shmem.c line 316-325)

子系统调用此函数注册其共享内存需求:

/*
 * 请求一个命名的共享内存区域
 * 通常在 postmaster 启动早期调用
 */

void
ShmemRequestStructWithOpts(const ShmemStructOpts *options)
{
    ShmemStructOpts *options_copy;

    options_copy = MemoryContextAlloc(TopMemoryContext,
sizeof(ShmemStructOpts));
memcpy(options_copy, options, sizeof(ShmemStructOpts));

    ShmemRequestInternal(options_copy, SHMEM_KIND_STRUCT);
}

2.2 ShmemRequestHash (src/backend/storage/ipc/shmem.c)

用于请求共享内存哈希表:

ShmemRequestHash(.name = "LOCK hash",
                 .nelems = max_table_size,
                 .ptr = &LockMethodLockHash,
                 .hash_info.keysize = sizeof(LOCKTAG),
                 .hash_info.entrysize = sizeof(LOCK),
                 .hash_info.num_partitions = NUM_LOCK_PARTITIONS,
                 .hash_flags = HASH_ELEM | HASH_BLOBS | HASH_PARTITION,
);

3. pg_shmem_allocations 视图增强

3.1 视图定义

pg_shmem_allocations 视图显示所有共享内存分配:

CREATEVIEW pg_shmem_allocations AS
SELECT * FROM pg_get_shmem_allocations();

pg_shmem_allocations 视图返回 4 列:

  • name: 分配名称
  • off: 偏移量
  • size: 请求的大小
  • allocated_size: 实际分配的大小

3.2 新增可见内容

现在 ShmemIndex 本身也可见于视图,便于诊断:

可见项:
- ShmemIndex  # 共享内存索引本身
- Buffer Descriptors
- WAL Buffers
- Lock Data
- ...

4. ShmemStructOpts 结构

子系统使用 ShmemStructOpts 结构指定共享内存需求:

typedefstructShmemStructOpts
{

constchar *name;        /* 子系统名称 */
    Size        size;         /* 请求的大小 */
void      **ptr;          /* 输出:分配的指针 */
    Size        alignment;    /* 对齐要求 */
} ShmemStructOpts;

5. 典型子系统转换示例

5.1 Buffer Manager 请求 (src/backend/storage/buffer/bufmgr.c)

/* 在 _PG_init 中注册共享内存需求 */
void
_PG_init(void)
{
    ShmemRequestStruct(.name = "Buffer Descriptors",
                       .size = BufferDescsSize(),
                       .ptr = (void **) &BufferDescriptors,
                       .alignment = 16);
}

5.2 LWLock 请求 (src/backend/storage/lmgr/lwlock.c)

/* 请求主 LWLock 数组 */
void
LWLockShmemRequest(void)
{
    Size    size;

/* 计算主 LWLock 数组大小 */
if (!IsUnderPostmaster)
    {
int num_main_array_locks = NUM_FIXED_LWLOCKS + NumLWLocksForNamedTranches();
        size = num_main_array_locks * sizeof(LWLockPadded);
    }
else
        size = SHMEM_ATTACH_UNKNOWN_SIZE;

    ShmemRequestStruct(.name = "Main LWLock array",
                       .size = size,
                       .ptr = (void **) &MainLWLockArray);
}


使用实践

1. 查看共享内存使用

-- 查看所有共享内存分配
SELECT
name,
off,
size,
    allocated_size
FROM pg_shmem_allocations
ORDERBYsizeDESC;

-- 按名称分组统计
SELECT
name,
count(*) as instances,
sum(size) as total_size
FROM pg_shmem_allocations
GROUPBYname
ORDERBY total_size DESC;

2. 诊断内存问题

2.1 检查 ShmemIndex

-- 查看 ShmemIndex 内容(新增)
SELECT * FROM pg_shmem_allocations
WHEREname = 'ShmemIndex';

2.2 检查 Buffer Manager

-- 查看缓冲区内存使用
SELECT
name,
size,
    allocated_size
FROM pg_shmem_allocations
WHEREnameLIKE'Buffer%';

2.3 检查 Lock Manager

-- 查看锁管理器内存
SELECT
name,
size
FROM pg_shmem_allocations
WHEREnameLIKE'%Lock%';

3. 监控内存变化

-- 创建历史记录表
CREATETABLE shmem_snapshots (
idserial PRIMARY KEY,
    recorded_at timestamptz DEFAULTnow(),
nametext,
instanceint,
sizebigint
);

-- 记录快照
INSERTINTO shmem_snapshots(name, instance, size)
SELECTname, instance, size
FROM pg_shmem_allocations;

-- 对比快照
SELECT
    s1.name,
    s1.size as old_size,
    s2.size as new_size,
    s2.size - s1.size as diff
FROM shmem_snapshots s1
JOIN (
SELECTname, instance, size
FROM pg_shmem_allocations
) s2 ON s1.name = s2.name AND s1.instance = s2.instance
WHERE s2.size != s1.size
ORDERBYabs(s2.size - s1.size) DESC;

4. 诊断内存泄漏

-- 检查是否有异常的内存增长
SELECT
name,
sum(size) as total_size,
sum(allocated_size) as total_allocated_size,
count(*) as allocation_count
FROM pg_shmem_allocations
GROUPBYname
ORDERBY total_size DESC;

-- 如果某个子系统内存持续增长,可能是泄漏

5. 配置调优参考

根据 pg_shmem_allocations 的输出,可以调整:

# 共享缓冲区
shared_buffers = 8GB

# 工作内存
work_mem = 64MB

# 维护工作内存
maintenance_work_mem = 2GB

# 锁等待超时
max_locks_per_transaction = 128

6. 常见问题诊断

6.1 共享内存不足

-- 查看总共享内存使用
SELECT
sum(size) as total_shmem_used,
    pg_size_pretty(sum(size))
FROM pg_shmem_allocations;

-- 查看各个子系统的内存使用
SELECT
name,
sum(size) as requested_size,
sum(allocated_size) as allocated_size
FROM pg_shmem_allocations
GROUPBYname
ORDERBY allocated_size DESC;

6.2 内存碎片化

-- 检查分配模式
SELECT
name,
off,
size
FROM pg_shmem_allocations
WHEREname = 'Buffer Descriptors'
ORDERBYoff;

总结

共享内存子系统重构为 DBA 提供了更好的可观测性。

关键要点:

  1. pg_shmem_allocations 增强:ShmemIndex 现在可见
  2. 统一 API:所有子系统使用相同的注册和分配接口
  3. 便于诊断:可以更好地识别内存问题
  4. 向后兼容:旧的 API 仍然可用

对 DBA 的价值:

  1. 更完整的共享内存视图
  2. 便于诊断内存问题
  3. 支持内存使用历史跟踪
  4. 帮助配置调优