版本为 20devel (https://github.com/postgres/postgres/commit/6cda9dfc4f9764b6957e4e214193936ffb92906b)
PG把共享内存注册基础设施做了优化调整,本篇做一下分析。
2024-10-18 | PG18devel 开发期 | v1 PoC
|
+-- 首次提出不重启调整 shared_buffers
| 主要验证在线增大,尚未完整处理缩小、跨平台等问题
| 状态:未合入
|
2026-04-04 | PG19devel
|
+-- 整理共享哈希表的内存布局
| 固定容量;表头、目录、元素放进同一连续区域
| 提交:999e9ebb51c9 / 9fe9ecd516b0
| 状态:已合入,属于 PG19
|
2026-04-06 | PG19devel
|
+-- 统一共享内存注册、请求、初始化、附着接口
| 提交:283e823f9dcb
|
+-- 增加内置子系统注册清单
| 提交:1fc2e9fbc0a3
|
+-- 增加对齐选项,迁移 buffer manager 和其他子系统
| 提交:dacfe81a0de5 / a4b6139dcceb / 9b5acad3f40f
|
+-- 上述基础设施均已合入 PG19
| 但此时不能在线调整 shared_buffers
|
2026-06-12 | 当时上游为 PG19devel | Databricks 公开补丁
|
+-- Haoyu 提交在线扩缩容代码和设计说明
| 来自基于 PG18 的内部开发分支,公开补丁已移植到当时 master
| 状态:内部已合入;不是上游已合入
|
2026-06-29 | master 从 PG19devel 切换为 PG20devel
|
+-- 此后 master 上的新提交属于 PG20 开发线
| 需要修复 PG19 时,另行回补到 REL_19_STABLE
|
2026-07-24 | PG20devel | v20260724
|
+-- 在线扩缩容方案接入 PG19 已合入的新框架
| 吸收 Databricks 的部分思路和代码
| 增加可调整大小的共享结构、buffer 扩缩容及测试
| 状态:补丁评审中,未合入
|
2026-08-26 | PG20devel + 回补 PG19
+-- 修复单用户模式下的共享内存回调注册
|
2026-08-28 | PG20devel + 回补 PG19
+-- 注册失败后清理请求、恢复状态机
+-- 增加 initialized 标记,拒绝附着半初始化对象
|
2026-09-18 | PG20devel + 回补 PG19
+-- 修复未知大小附着到不存在的对象;增加溢出检查
|
2026-09-24 | PG20devel + 回补 PG19
+-- 修复旧接口在单用户模式下重复附着失败
|
2026-09-30 | PG20devel + 回补 PG19
+-- 允许单用户模式附着已有的未知大小对象
|
2026-10-10 | PG20devel + 回补 PG19
|
+-- 将进程私有的 writeback context 初始化移出共享内存回调
| 提交:15a947f30726
|
2026-10-20 | PG20devel 工作线 | v20261017,共 23 个补丁
|
+-- 在线扩缩容继续处理并行 worker、并发读取、性能、失败回滚
状态:整体功能仍未合入上游
1. 改了什么
共享内存对象的生命周期拆成了四部分
text
注册回调 -> 声明对象需求 -> 分配并绑定地址 -> 初始化内容 / 附着已有对象
以前子系统自己计算大小再调用分配函数,根据 found 判断是否初始化。
现在,子系统声明"对象名称、大小、对齐、指针接收位置",框架统一分配,再调用适当的回调。
| 项目 | 旧流程 | 新流程 |
|---|---|---|
| 对象大小 | XXXShmemSize() 与分配代码分别维护 |
ShmemRequestStruct() 同时声明名称和大小 |
| 内置子系统接入 | 修改 ipci.c 的大小计算和初始化清单 |
在 subsystemlist.h 登记回调 |
| 扩展接入 | 串接 shmem_request_hook、shmem_startup_hook |
_PG_init() 注册 ShmemCallbacks |
| 分配与初始化 | 常放在同一个 XXXShmemInit() 中 |
框架分配,init_fn 初始化内容 |
| 附着 | 再跑初始化入口,由 found、进程类型分支判断 |
框架恢复指针,必要时调用 attach_fn |
| 启动后注册 | 调用者自行处理对象查找、初始化同步 | 显式允许后,由框架串行处理一组请求 |
没有改变的边界:
- 这里管理的是主共享内存段里的命名对象,不是每个对象各建一个 OS 共享内存段。
- 主共享内存仍须映射到各进程的相同地址,结构中可以使用普通指针。
- 底层仍从固定大小的共享内存段中划出空间,已分配区域不能单独释放。
- 它不是 DSM/DSA,也没有让
shared_buffers自动变成可在线修改的参数。
2. 旧流程:计算和初始化两份清单
来源:旧 ipci.c、旧 buf_init.c。
text
PostmasterMain
|
+-- 加载 shared_preload_libraries
| `-- 扩展安装 request/startup hook
|
+-- process_shmem_requests()
| `-- 旧 request hook -> RequestAddinShmemSpace(bytes)
|
`-- CreateSharedMemoryAndSemaphores()
+-- CalculateShmemSize()
| +-- BufferManagerShmemSize()
| +-- LockManagerShmemSize()
| +-- ...其他 XXXShmemSize()
| `-- 加上扩展请求及预留空间
|
+-- PGSharedMemoryCreate()
+-- InitShmemAllocator()
+-- CreateOrAttachShmemStructs()
| +-- LWLockShmemInit()
| +-- ...其他 XXXShmemInit()
| `-- BufferManagerShmemInit()
| +-- ShmemInitStruct(name, size, &found)
| +-- found=false: 初始化内容
| `-- found=true: 复用已有内容
|
+-- dsm_postmaster_startup()
`-- 旧 shmem_startup_hook
以 buffer pool 为例,大小计算在 BufferManagerShmemSize():
c
/* 旧代码:估算时手动留出 IO 对齐余量。 */
size = add_size(size, PG_IO_ALIGN_SIZE);
size = add_size(size, mul_size(NBuffers, BLCKSZ));
实际定位和分配却在 BufferManagerShmemInit():
c
/* 旧代码:再次计算大小,分配后再手动对齐返回地址。 */
BufferBlocks = (char *)
TYPEALIGN(PG_IO_ALIGN_SIZE,
ShmemInitStruct("Buffer Blocks",
NBuffers * (Size) BLCKSZ + PG_IO_ALIGN_SIZE,
&foundBufs));
问题不在于不能运行,而在于同一对象的信息分散:
- 大小公式、对齐余量要在两个位置保持一致。
- 增加一个内置子系统,要改中央清单。
EXEC_BACKEND子进程也运行CreateOrAttachShmemStructs(),初始化函数同时承担"创建"和"附着"两种职责。- 一次初始化多个对象时,调用者还要保证"全部找到"或"全部新建";旧 buffer manager 就显式检查四个
found标志。
3. 新接口:回调注册与对象请求是两层
来源:shmem.h。
c
typedef struct ShmemCallbacks
{
int flags; /* 是否允许启动后注册 */
ShmemRequestCallback request_fn; /* 声明需求,不在这里操作共享内容 */
ShmemInitCallback init_fn; /* 新对象的业务初始化 */
ShmemAttachCallback attach_fn; /* 已有对象附着后的进程私有设置 */
void *opaque_arg; /* 原样传给上述回调 */
} ShmemCallbacks;
一个子系统可以请求多个对象。每个普通对象用下面的描述符:
c
typedef struct ShmemStructOpts
{
const char *name; /* 在 ShmemIndex 中查找对象的名字 */
ssize_t size; /* 字节数;未知大小仅用于附着已有对象 */
size_t alignment; /* 可选,至少按 cache line 对齐 */
void **ptr; /* 接收对象地址的进程私有指针变量 */
} ShmemStructOpts;
RegisterShmemCallbacks() 不是分配函数。启动阶段,它把回调指针放入进程私有的 registered_shmem_callbacks 链表,留待后续阶段执行。
调用 request_fn 后,ShmemRequestStruct() 才把对象需求放入 pending_shmem_requests。实现会检查当前阶段、名称、大小、对齐和本轮请求中是否重名。
这里有三个不同层次:
| 数据 | 所在位置 | 用途 |
|---|---|---|
registered_shmem_callbacks |
进程私有内存 | 子系统的回调清单,崩溃重建时仍可复用 |
pending_shmem_requests |
TopMemoryContext |
当前这轮待分配或待附着的对象描述 |
ShmemIndex |
主共享内存;本地另有 HTAB 句柄 |
保存已经分配的对象名称、地址、大小和初始化状态 |
生命周期约束:
- 回调结构只保存指针,通常声明为
static const,不能传一个即将失效的栈变量。 - 对象 options 会复制到
TopMemoryContext,但.name只复制字符串指针,名字也须长期有效。 .ptr是"指针变量的地址",不是共享内存地址。框架完成分配或查找后,才给*ptr赋值。
4. 新启动流程
来源:postmaster.c、ipci.c。
text
PostmasterMain
|
+-- RegisterBuiltinShmemCallbacks()
| `-- 按 subsystemlist.h 顺序注册内置子系统
|
+-- process_shared_preload_libraries()
| `-- 扩展 _PG_init() -> RegisterShmemCallbacks()
|
+-- InitializeMaxBackends() / InitializeFastPathLocks()
|
+-- process_shmem_requests() [保留旧扩展 hook]
| `-- 可能请求额外内存和 named LWLocks
|
+-- ShmemCallRequestCallbacks()
| `-- 逐个 request_fn
| `-- ShmemRequestStruct/Hash -> pending_shmem_requests
|
+-- InitializeShmemGUCs() [用已收集的需求计算展示值]
|
`-- CreateSharedMemoryAndSemaphores()
+-- CalculateShmemSize()
+-- PGSharedMemoryCreate() [创建主共享内存段]
+-- InitShmemAllocator() [分配器与 ShmemIndex 自举]
+-- ShmemInitRequested()
| 1. 分配全部请求,建立索引,设置各对象指针
| 2. 按注册顺序调用全部 init_fn
| 3. 标记 initialized,释放本轮请求描述
+-- dsm_postmaster_startup()
`-- 旧 shmem_startup_hook
旧 request hook 必须先于新 request callbacks 执行。原因是旧扩展可能通过 RequestNamedLWLockTranche() 增加锁需求,而新的 LWLock 请求回调需要据此计算最终大小。
4.1 内置清单不是自动发现
subsystemlist.h 使用 X-macro:同一清单既能生成 extern 声明,也能生成注册调用。ipci.c 中的展开方式是:
c
/* 每一项展开成一次 RegisterShmemCallbacks()。 */
#define PG_SHMEM_SUBSYSTEM(subsystem_callbacks) \
RegisterShmemCallbacks(&(subsystem_callbacks));
#include "storage/subsystemlist.h"
#undef PG_SHMEM_SUBSYSTEM
顺序仍然有意义。LWLock 注册在最前面,保证其他初始化回调可以使用它。新框架没有自动推导子系统依赖关系。
4.2 大小计算统一,但没有换掉底层分配器
ShmemGetRequestedSize() 汇总:
text
ShmemIndex 所需空间(请求项数 + 128 个预留项)
+ 每个请求的对齐填充
+ 每个请求的 size
CalculateShmemSize() 再加上原有的 100000 字节余量、旧扩展额外请求,并按 8KB 边界补齐。这仍是带余量的容量计算,不是运行时可无限增长的内存池。
实际分配仍由 ShmemAllocRaw() 推进 free_offset,函数内的关键片段如下:
c
SpinLockAcquire(&ShmemAllocator->shmem_lock);
rawStart = ShmemAllocator->free_offset;
newStart = TYPEALIGN(alignment, rawStart);
/* 对齐后检查加法溢出和共享段边界,成功才推进游标。 */
if (!pg_add_size_overflow(newStart, size, &newFree) &&
newFree <= ShmemSegHdr->totalsize)
{
newSpace = (char *) ShmemBase + newStart;
ShmemAllocator->free_offset = newFree;
}
else
newSpace = NULL;
SpinLockRelease(&ShmemAllocator->shmem_lock);
锁的职责不要混淆:
shmem_lock自旋锁保护分配游标。ShmemIndexLock保护命名目录,以及后文的启动后初始化过程。- 对象投入使用后的读写,仍由各子系统自己的锁、原子变量负责。
4.3 先绑定所有指针,再调用初始化回调
ShmemInitRequested() 的主体:
c
/* 第一轮:分配、登记对象,并设置调用者的指针变量。 */
foreach_ptr(ShmemRequest, request, pending_shmem_requests)
{
InitShmemIndexEntry(request);
}
/* 第二轮:执行子系统的内容初始化。 */
foreach_ptr(const ShmemCallbacks, callbacks, registered_shmem_callbacks)
{
if (callbacks->init_fn)
callbacks->init_fn(callbacks->opaque_arg);
}
/* 全部初始化成功后,才标记完成。这里释放的是请求描述,不是共享对象。 */
foreach_ptr(ShmemRequest, request, pending_shmem_requests)
{
request->index_entry->initialized = true;
pfree(request->options);
}
list_free_deep(pending_shmem_requests);
pending_shmem_requests = NIL;
shmem_request_state = SRS_DONE;
InitShmemIndexEntry() 会先建立目录项,再划出空间,记录 location、size、allocated_size,把 initialized 设为 false。普通结构直接写入 *ptr;HASH 和 SLRU 则执行各自的基础初始化。
因此,init_fn 运行时,对象地址已经就绪;但这不表示后面其他子系统的 init_fn 也已经执行完。跨子系统的内容依赖仍受注册顺序约束。
5. Buffer manager 的具体变化
最新代码里的回调定义:
c
const ShmemCallbacks BufferManagerShmemCallbacks = {
.request_fn = BufferManagerShmemRequest,
.init_fn = BufferManagerShmemInit,
};
BufferManagerShmemRequest() 一次声明四块区域:
| 名称 | 大小 | 接收指针 |
|---|---|---|
| Buffer Descriptors | NBuffers * sizeof(BufferDescPadded) |
BufferDescriptors |
| Buffer Blocks | NBuffers * BLCKSZ |
BufferBlocks |
| Buffer IO Condition Variables | NBuffers * sizeof(ConditionVariableMinimallyPadded) |
BufferIOCVArray |
| Checkpoint BufferIds | NBuffers * sizeof(CkptSortItem) |
CkptBufferIds |
其中数据页的请求变成:
c
ShmemRequestStruct(.name = "Buffer Blocks",
.size = NBuffers * (Size) BLCKSZ,
.alignment = PG_IO_ALIGN_SIZE, /* 不再由调用者多申请再挪指针 */
.ptr = (void **) &BufferBlocks,
);
与旧代码相比:
- 删除独立的
BufferManagerShmemSize(),不再维护第二份大小公式。 - 删除四个
found标志和"创建还是附着"的分支。 BufferManagerShmemInit()只初始化 buffer descriptor、原子状态、等待队列和 IO 条件变量。- 替换策略和 buffer 映射哈希表分别作为
StrategyCtlShmemCallbacks、BufTableShmemCallbacks登记,不再由 buffer 初始化函数间接调用。
共享哈希表:数据共享,操作句柄私有
buffer 映射表的实际请求:
c
size = NBuffers + NUM_BUFFER_PARTITIONS;
ShmemRequestHash(.name = "Shared Buffer Lookup Table",
.nelems = size,
.ptr = &SharedBufHash, /* 接收本进程的 HTAB 句柄 */
.hash_info.keysize = sizeof(BufferTag),
.hash_info.entrysize = sizeof(BufferLookupEnt),
.hash_info.num_partitions = NUM_BUFFER_PARTITIONS,
.hash_flags = HASH_ELEM | HASH_BLOBS | HASH_PARTITION | HASH_FIXED_SIZE,
);
额外的 NUM_BUFFER_PARTITIONS 是为 buffer 映射更新中"先插入新项、再删除旧项"预留容量,不是可动态扩容的空间。
shmem_hash.c 先用 hash_estimate_size() 计算整个哈希表的大小,再以 SHMEM_KIND_HASH 进入同一请求队列:
text
ShmemRequestHash()
-> 计算 HASHHDR、目录、元素等所需的连续区域
-> ShmemRequestInternal(..., SHMEM_KIND_HASH)
首次分配 -> shmem_hash_init() -> 创建共享内容和本地 HTAB
已有对象 -> shmem_hash_attach() -> HASH_ATTACH,重建本地 HTAB
不能把 HTAB * 当作所有进程共同使用的控制对象。共享的是表头、目录和元素等数据;HTAB 操作句柄是进程私有的。当前实现统一使用 HASH_FIXED_SIZE,删除元素可以复用槽位,但不会向主共享段追加容量。
6. "附着"具体做什么
6.1 普通 fork
text
postmaster 完成共享对象初始化
-> fork()
-> 子进程继承共享映射
-> 子进程也继承自己的指针变量和 HTAB 句柄副本
-> 不重跑 request_fn、init_fn、attach_fn
最后一条只针对启动时已登记的对象。后续按需加载的扩展,仍可能走启动后附着路径。
6.2 EXEC_BACKEND
来源:launch_backend.c、proc.c、ShmemAttachRequested()。
text
EXEC_BACKEND 子进程启动
+-- PGSharedMemoryReAttach() [先把共享段映射到相同地址]
+-- 读取 GUC
+-- RegisterBuiltinShmemCallbacks()
+-- 重新加载 shared_preload_libraries
| `-- 重新取得本进程有效的回调函数地址
+-- InitShmemAllocator() [连接已有分配器及 ShmemIndex]
+-- ShmemCallRequestCallbacks() [重建对象请求描述,不分配共享数据]
|
`-- InitProcess() 等完成 MyProc / LWLock 本地状态
`-- AttachSharedMemoryStructs()
`-- ShmemAttachRequested()
+-- 持 ShmemIndexLock 共享锁
+-- 按名称查找,检查 initialized 和大小
+-- 设置 *ptr / 重建 HTAB 等本地句柄
+-- 调用 attach_fn
`-- 释放锁,进入 DONE
attach_fn 不是再初始化一遍共享数据。它用于恢复不能通过 exec 继承的进程私有状态。普通共享结构只需要框架替它设置指针,所以可以没有 attach_fn。
当前 buffer manager 就没有 attach_fn。早期迁移版本还有一个,用于初始化纯进程私有的 BackendWritebackContext;2026-10-10 的补丁把这件事移到了 InitBufferManagerAccess(),避免把普通后端初始化混进共享内存生命周期。
6.3 postmaster 崩溃重建
普通子进程退出不会重新初始化共享对象。发生需要重建共享内存的崩溃恢复时,postmaster.c 则执行:
text
清理旧共享内存
-> ResetShmemAllocator()
清空分配器、目录指针和本轮请求,保留 registered_shmem_callbacks
-> ShmemCallRequestCallbacks()
-> CreateSharedMemoryAndSemaphores()
保留回调清单是为了重新执行需求声明,不必重新加载全部预加载扩展。
7. 启动后注册:允许按需使用,不保证还有空间
RegisterShmemCallbacks() 在 SRS_DONE 阶段只接受设置了 SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP 的调用,并立即执行回调。
text
RegisterShmemCallbacks(ALLOW_AFTER_STARTUP)
-> request_fn 收集这一组请求
-> ProcessShmemRequestsAfterStartup()
持 ShmemIndexLock 排他锁
|
+-- 所有对象都存在且初始化完成 -> attach 全部对象 -> attach_fn
+-- 所有对象都不存在 -> allocate 全部对象 -> init_fn
`-- 只存在一部分 / 有半初始化项 -> 报错
|
标记 initialized,释放锁
-> 清理请求描述,恢复 SRS_DONE
一组请求必须"全部存在"或"全部不存在",否则框架无法确定应该调用哪个回调。init_fn、attach_fn 都在这把排他锁内执行,防止两个后端同时初始化。回调不要再调用会重入取得该锁的共享内存接口。
启动阶段没有并发后端,批量初始化不需要这把锁;启动后的首次加载有并发,不能照搬启动阶段的无锁假设。
失败处理分两层
- 进程私有状态要恢复。
CallShmemCallbacksAfterStartup()用PG_TRY/PG_FINALLY清理请求描述,并把状态恢复到SRS_DONE,不让下一次调用处理残留请求。 - 共享对象不会随之回滚。 对象分配成功、但初始化中途失败时,目录项留下
initialized=false。后续附着被拒绝,通常要重启才能清理,不是再次注册就能重试。
这一点是读初版补丁容易漏掉的:清理请求链表,不等于释放共享内存。
SHMEM_ATTACH_UNKNOWN_SIZE 也不是动态大小申请。它只用于附着一个已存在的对象,并跳过大小相等检查;对象不存在就报错,不能拿 -1 当作待分配大小。
旧扩展的兼容路径仍在
ShmemInitStruct() 现在包装上述查找和分配逻辑,仍返回 found。新建时它立即把目录项标为完成,调用者再做自己的内容初始化。因此,旧扩展仍须保留原有的初始化同步,不能把这个包装当成新回调模式的完整替代。
实际迁移可参考 pg_stat_statements.c:在 _PG_init() 注册回调,在 pgss_shmem_request() 请求状态结构及哈希表,在 pgss_shmem_init() 初始化锁、状态并恢复持久化统计数据。
8. 补丁如何演进
8.1 基础改造与迁移
主框架作者为 Heikki Linnakangas,283e823f9dcb 的共同作者为 Ashutosh Bapat。主体在 PG19 开发期合入,REL_19_RC1 已包含这些提交。
| 时间 | 提交 | 实际变化 |
|---|---|---|
| 2026-04-04 | 999e9ebb51c9、9fe9ecd516b0 |
先将共享哈希表固定容量化,再将表头、目录和元素收拢为一个连续区域;对象目录和内存统计才能反映整张表的占用。这两项已包含在本文旧流程基准中。 |
| 2026-04-06 | 283e823f9dcb |
引入请求、初始化、附着回调;统一对象描述;保留旧函数兼容层。不是这个提交单独完成所有子系统迁移。 |
| 2026-04-06 | 1fc2e9fbc0a3 |
引入内置子系统清单,供随后迁移逐步接入。 |
| 2026-04-06 | dacfe81a0de5 |
增加显式 alignment,消除 buffer pool 等调用者手动多分配再对齐的做法。 |
| 2026-04-06 | a4b6139dcceb |
迁移 buffer manager,将替换策略、映射哈希表分别登记。 |
| 2026-04-06 | 9b5acad3f40f |
完成剩余内置子系统迁移;旧接口仍为扩展保留。LWLock、SLRU、AIO 已在同系列前面的提交中迁移。 |
| 2026-04-06 | ed71d7356e3b |
修复请求描述的内存泄漏:清理请求时也要释放 request->options。不是释放共享对象。 |
8.2 初版之后的重要修正
| 时间 | 提交 | 为什么需要 |
|---|---|---|
| 2026-07-07 | 6f7199a1245c |
明确启动后回调持有 ShmemIndexLock,避免扩展误判初始化时的锁环境。 |
| 2026-08-26 | edc095a26529 |
正确识别单用户模式下的启动后注册;postmaster 启动后不能进入需要拿锁的注册路径。 |
| 2026-08-28 | c54b6979e033 |
注册或回调报错后恢复状态机,清理残留请求,避免下一次注册访问已释放的描述或走错阶段。 |
| 2026-08-28 | 07fdee7c8a8b |
增加 initialized 标志,区分"分配成功"和"初始化完成";拒绝使用失败后留下的半成品。 |
| 2026-09-18 | 311df1dc0392 |
禁止为不存在的对象使用未知大小标记;同时在分配器增加整数加法溢出检查,避免把 -1 当容量。 |
| 2026-09-24 | 4e277b424a53 |
修复旧 ShmemInitStruct() 在单用户模式重复附着失败:是否先查找已有对象不应由 IsUnderPostmaster 决定。 |
| 2026-09-30 | c712b0d49b97 |
单用户模式也可附着未知大小对象。判断依据改为共享内存是否已初始化,而不是进程是否为 postmaster 子进程。 |
| 2026-10-10 | 15a947f30726 |
将纯进程私有的 writeback context 初始化移出 buffer manager 的共享内存回调;最新代码因此不再需要对应的 attach 回调。 |
这些修正主要解决生命周期边界,不是在线扩缩容。读代码时应以当前状态机和 initialized 检查为准,不能只看 4 月的初始补丁。
9. 与动态 shared_buffers 的关系
已经合入的是"对象可声明、可统一管理"的框架,不是"对象可改变大小"的框架:
ShmemStructOpts只有当前请求大小,没有最大容量或 resize 回调。AttachShmemIndexEntry()默认要求请求大小与既有大小一致;跳过检查不会改变对象大小。ShmemAllocRaw()只在现有共享段内推进游标,没有回收接口。- 当前源码没有
max_shared_buffers、pg_resize_shared_buffers();shared_buffers仍为PGC_POSTMASTER参数。
这套注册机制让后续扩缩容更容易接入:对象大小、位置及生命周期集中可见。但地址空间预留、物理页回收、跨进程协调,以及 buffer pin、AIO、旧 buffer 编号等问题,仍需要另外的实现。
databricks再26年6月给了一份实现,但是没有复用共享内存基础设施的修改,社区没采纳。
10. 其他
- 原始设计讨论:Better shared data structure management and resizable shared data structures。
- 当前版本的扩展共享内存文档源码。
- test_shmem 的生命周期测试:预加载、启动后注册、大小不匹配、未知大小附着和单用户模式。
- CF #6652:后续可调整大小的共享内存结构,不是上述注册框架的合入状态。