CXL池化内存动态容量调度:按需分配、回收与状态机实践

发布时间:2026/10/27 16:04:32
CXL池化内存动态容量调度:按需分配、回收与状态机实践 1. 内存池化真正的痛点不在“连得上”而在“分得动”CXL 池化内存这件事前几年大家讨论的重点基本都停在“能不能连”。链路训练过了、枚举到了、主机能看到远端内存条了Demo 就算成功。但真把几台机器挂到同一个内存池上跑业务问题立刻变味连是连上了可这块池子怎么分分多了浪费分少了宕机分完了想调回来还得停机重启。这篇讲 Dynamic Capacity Service 的论文切的就是这个缝——它不再纠结物理层而是把“容量”当成一种可以按需申请、按需归还的资源来做调度目标是让 CXL Pooled Memory 的有效利用率从静态切分下的三四成拉到接近业务真实峰值的水平。我把这篇论文读了三遍第一遍看架构第二遍抠命令时序第三遍专门盯它的评测方法和那些没明说的假设。结论先摆出来它解决的不是“内存不够”的问题而是“内存明明够、但被切死在某台机器名下导致别人用不上”的问题。适合谁来读做虚拟化/云平台资源调度的、做内存子系统验证的、以及准备把 CXL 内存池纳入容量规划的基础设施工程师。如果你只是想知道 CXL 是什么这篇会让你看得很难受但如果你手上正好有一批内存敏感型负载机器之间冷热差异巨大那这篇里的权衡思路值得逐条抄下来。2. 静态切分的浪费究竟藏在哪一层2.1 峰值预留的数学为什么账面上永远不够用先说清楚静态切分错在哪。假设一个池子挂 4 台主机池总容量 1 TB按“平均分”每台 256 GB。听起来很公平但业务不是平均分布的。真实场景里A 机器跑的是缓存型服务日常占用 180 GB大促时冲到 320 GBB 机器跑批处理白天 60 GB夜里跑完 240 GBC、D 两台长期在 80 GB 上下晃。你要保证四台都不出问题就得按各自峰值预留3202408080 720 GB 是刚需剩下 280 GB 谁敢动没人敢因为一旦 A 突发而池子里那 280 GB 恰好被别的机器“借”走了A 就会触发 OOM 或者疯狂 swap。这就是静态切分最要命的地方池子的总容量必须覆盖所有主机峰值之和而不是峰值之和的期望。利用率的分母被峰值撑大了分子却是平均值算下来能到 45% 已经算运营得不错。更尴尬的是很多平台为了避免“借出去还不回来”干脆连借都不借池子实际退化成一块谁也别碰的共享备胎。我见过一个部署池子划了 2 TB一年下来峰值占用没超过 900 GB剩下那 1.1 TB 纯当心理安慰。论文里把这类浪费归成两类容量碎片每台主机的预留额度用不满和时间错峰不同主机的峰值不在同一时刻。前者靠调小预留粒度缓解后者只能靠动态调度。静态方案对这两类浪费都无解因为它把“容量归属”和“容量使用”绑死了。2.2 从“能共享”到“会调度”中间差的是策略而不是硬件CXL 3.0 规范层面其实已经给了工具。Dynamic Capacity DeviceDCD这个概念允许设备把自己的一部分容量划成动态区域主机通过邮箱命令去申请一段 extent用完再还回去。硬件通路是通的链路层、协议层、命令格式都定义好了。但规范只告诉你“怎么申请”不告诉你“什么时候申请、申请多少、什么时候还、还的时候谁先还”。这中间的空白就是论文要填的。它提出了一个 Dynamic Capacity Service 的角色夹在设备管理器和主机之间负责三件事需求预测这台机器接下来大概要多少、容量编排池子里剩下的给谁、回收协调谁该吐出来。这个定位很关键因为它意味着 DCS 不是一个纯硬件机制而是一个带策略的软件层。你要复现它光有支持 DCD 的设备和交换机不够还得有一套跑在带外或者 Fabric 层的管理逻辑。我个人觉得这个分层选择是论文里最聪明的决定。如果把这套策略塞进设备固件升级困难、策略不可编程、出了问题没法排查如果塞进主机内核各主机只看得见自己做不了全局最优。放在中间层虽然多了一跳通信开销但换来了策略可替换、可观测、可回滚。工程上这笔买卖划算。3. 论文方案的整体骨架三层角色怎么分工3.1 设备、主机与管理器各自的职责边界论文把整个系统拆成三层。最底下是DCD 设备它维护一个容量池对外暴露若干个 DC Region每个 Region 内部按 extent 粒度切分设备自己记录哪些 extent 已分配、哪些空闲、归属于哪台主机。中间是主机侧代理跑在每台主机上负责向设备发申请/归还命令同时把拿到的容量上线成可用内存给上层业务用。最上面是管理器也就是论文里的 DCS 核心掌握全局视图决定分配策略。职责边界划得很清楚设备只管“有没有”不管“该不该”主机只管“我要多少”不管“别人还有没有”管理器只管“给谁多少”不直接碰数据面。这个划分的好处是每一层都可以独立替换。比如你换一家设备厂商只要它遵循同样的命令集上层逻辑不用动你想换一套调度算法设备固件也不用重刷。代价是决策链路变长了。一次容量调整要经过主机上报需求 → 管理器计算 → 下发指令 → 主机执行邮箱命令 → 设备更新映射 → 主机上线内存。每一跳都有延迟尤其最后一步“上线内存”如果走的是传统内存热插拔路径耗时可能到秒级。论文花了不小篇幅讨论怎么把这条链路缩短我在第 5 节会展开。3.2 extent 粒度一个必须算清楚的权衡extent 是动态容量的最小分配单位。这个参数选多大直接决定整套机制的成败但论文里它只是一笔带过我觉得值得单独拎出来算一算。假设池子 1 TB。如果 extent 取 2 GB池子里有 512 个单元。管理侧维护一张表每项记录 extent ID、归属主机、状态、时间戳按 64 字节算总共 32 KB——这点元数据连 L2 缓存都塞不满完全不是问题。但如果业务的实际需求是 1.5 GB你给 2 GB浪费 0.5 GB十台主机加起来就是 5 GB占比 0.5%可以接受。反过来extent 取 128 MB1 TB 池子有 8192 个单元元数据 512 KB还是能接受。但问题转移到命令频率上业务要 2 GB得发 16 次申请每次都要走一遍邮箱往返。按每次往返 10 μs 算链路传输 设备解码 映射更新16 次就是 160 μs而且这期间可能还涉及锁竞争。如果业务扩容很频繁光管理开销就能吃掉几个点的性能。所以 extent 粒度本质是在内部碎片和管理开销之间找平衡点。我的经验是先看业务的容量变化步长。如果负载是 JVM 堆这种一次性申请大块、很少回收的粒度可以大4 GB 甚至 8 GB 都行如果是容器化环境、Pod 频繁起落粒度就得小256 MB 到 1 GB 比较合适。论文选的是一个折中值具体数字它给了但更重要的是它给了一套推导方法——按最坏情况下的命令风暴估算开销上限再按平均浪费估算碎片成本两条曲线交点附近就是合理区间。3.3 分配与回收的状态机别让“半分配”状态流出去论文里有一张状态转换的描述我觉得是全文最容易被读者跳过的部分但它恰恰是工程落地时最容易出问题的地方。一个 extent 的状态不是简单的“空闲/占用”二态至少要经过空闲 → 已预留管理器已批准但主机还没确认→ 已分配主机已上线可用→ 待回收管理器已通知但主机还没释放→ 空闲。多出来的“已预留”和“待回收”两个中间态是为了处理超时和失败。设想管理器批准了一台主机的申请指令发下去了但主机侧因为某种原因没执行成功比如内存上线失败。如果没有“已预留”这个状态管理器会认为这块容量已经出去了实际上它悬在半空——既没被使用也没法给别人。时间一长池子就被这种幽灵容量吃光了。“待回收”同理。管理器让主机还 32 GB主机说好但它得先把上面的数据迁移走、页表清干净这个过程可能要几百毫秒。这段时间里这块容量既不能被别人用也不能算主机在用。系统必须显式记录这个状态并且设置超时兜底超时没还回来强制标记为异常并告警别指望它自己恢复。注意任何涉及跨节点资源转移的系统中间态的数量永远比你想的多。少设计一个状态线上就多一类查不出原因的资源泄漏。4. 命令通路与时序一次容量调整到底发生了什么4.1 邮箱命令的往返路径拆解主机要申请容量走的是 CXL 定义的邮箱机制不是直接写寄存器。命令打包成 Mailbox Command写到设备的命令寄存器设备处理完把结果写回主机轮询或者等中断。整个路径我拆成四段来估延迟第一段是主机侧软件栈从业务触发到命令真正写下去包括策略判断、参数组装、加锁快的话几微秒慢的话如果走了用户态到内核态的多次穿越几十微秒也正常。第二段是链路传输本地直连的话百纳秒级跨交换机可能到微秒级。第三段是设备内部处理解码命令、查容量表、更新映射、可能还要刷元数据到非易失介质这一段最不可控好的实现几微秒保守实现可能几十微秒。第四段是返回通知中断方式比轮询快但中断本身也有开销。四段加起来乐观估计 10~20 μs保守估计 100 μs 以上。这个数字本身不算大但它有个致命特性它是串行的。一台主机扩容要等命令回来才能继续如果多台主机同时申请设备侧的命令队列会成为瓶颈。论文里提到设备要支持命令流水化我认为这是必须项而不是加分项。4.2 内存上线真正的时间杀手命令返回成功只代表设备侧把 extent 划给你了。主机要真正能用这块内存还得走“上线”流程识别新容量、建立地址映射、把页框加进伙伴系统、通知上层可分配。这一步如果按传统内存热插拔的路子走涉及内存块的扫描和 zone 的重建耗时轻松上到百毫秒甚至秒级。论文对这个问题的处理思路是预分配 常驻映射管理器提前把一部分容量划给主机但主机不立即上线而是留着当“蓄水池”。业务真正需要扩容时从蓄水池里快速切一块出来此时只涉及页框级别的操作几十微秒就能完成。蓄水池见底了再异步去申请新的大块容量补充。这个设计我很认可因为它把“慢操作”和“快操作”解耦了。业务侧看到的永远是快路径慢路径在后台悄悄跑。代价是池子里有一部分容量处于“已分配但未使用”的状态牺牲一点利用率换响应速度。蓄水池设多大论文给的思路是按历史扩容速率的 P95 来定保证在补货完成之前不会饿死。我的经验是设成单次最大扩容量的 2~3 倍比较稳太小了频繁补货太大了浪费。4.3 一个完整的扩容时序走查把上面的东西串起来一次典型的扩容大概是这样的业务侧内存压力上来触发主机代理的扩容判断代理先查本地蓄水池够用就直接切整个流程结束耗时在微秒级不够用向管理器发请求带上当前使用率、增长速度、优先级管理器查全局容量表看有没有空闲 extent 或者能不能从低优先级主机回收能分配就下发指令主机执行邮箱命令设备更新映射主机把新容量加进蓄水池同时记账如果全局也没有余量管理器要么拒绝业务侧就得走 swap 或者限流要么触发回收流程向某台主机发归还请求。这里有个细节值得说回收请求发出去之后主机不一定能立即还。上面的页可能正在被访问强行回收等于制造页错误。论文的做法是让主机侧先做页面冷热标记把冷页迁移到本地内存热页保留腾出来的 extent 再还回去。这个迁移过程是异步的管理器发完请求就走等主机回调。所以完整的回收时序可能跨越几百毫秒甚至更久管理器必须能处理“请求已发但未完成”的中间状态。5. 论文评测怎么读数字背后的前提条件5.1 负载选择和指标设计读系统论文我最关心的不是它跑出多少加速比而是它用什么负载跑出来的。这篇论文选了三类内存数据库随机访问为主、对延迟敏感、数据分析型批处理顺序扫描为主、吞吐优先、以及一组混合负载多租户共存冷热差异明显。指标上除了常规的吞吐和尾延迟它专门定义了一个“有效内存利用率”算法是实际被业务使用的内存量 ÷ 池子物理总容量。这个指标比单纯的“分配率”诚实因为分配了不等于用了。另一组指标是“SLA 违例次数”用来衡量调度策略失误带来的后果——分配慢了导致业务抖动或者回收激进了导致页错误飙升都算违例。我觉得这套指标设计比很多同类工作扎实的地方在于它没有只看平均值。尾延迟和违例次数才是调度系统真正的照妖镜。平均吞吐提升 15%听着不错但如果 P99 延迟翻倍这个方案在生产环境就是不可用的。5.2 收益的来源拆解哪部分是真的哪部分是假设论文报的收益大概分三块容量节省、利用率提升、以及特定负载下的性能改善。我得给它拆一拆。容量节省是最实的。从静态按峰值预留改成动态按需分配池子规模能砍掉三到四成这部分收益不依赖任何性能假设只要调度策略不太蠢就能拿到。利用率提升同理本质是数学问题。性能改善这块要打个问号。论文说某些负载在动态方案下延迟降低了原因是“远端内存的访问被本地化了”——因为可以按需把热数据放在近端。但这个收益的前提是调度策略能准确识别冷热而且识别和迁移的开销小于收益。论文的测试环境里这个前提成立真实环境里冷热判断经常是滞后的等你看出来热了访问模式已经变了。还有一块它没怎么强调但我觉得很重要避免 OOM 的价值。静态切分下某台机器一旦超出预留额度就直接 OOM 或者进 swap性能断崖式下跌。动态方案下它可以临时借容量顶过去。这个收益在某些负载上是数量级的只是不好用平均值体现。5.3 论文没说的那些假设读到这里我列了几条论文隐含的前提复现的时候如果不满足效果会打折扣。第一它假设网络或者说 Fabric延迟足够低且稳定。如果跨交换机的往返抖动很大管理器收集上来的使用率数据就是过期的基于过期数据做的决策可能是错的。第二它假设主机侧有能力快速上线内存这依赖特定的内核配置默认配置下不一定成立。第三它假设业务的内存需求变化是“渐变”而不是“突变”如果某个负载在一秒内从 50 GB 冲到 300 GB任何基于历史预测的策略都来不及反应只能靠蓄水池硬扛。这三条里第三条是硬伤没法靠工程优化解决只能在容量规划时留够余量。论文在局限性部分提了一句但说得比较轻。6. 想自己试一遍仿真环境和参数怎么搭6.1 没有硬件时的软件仿真思路大部分读者手上不会有支持 DCD 的真机。没关系这套机制的核心逻辑——按需分配、回收、状态机、蓄水池——完全可以在软件层仿真验证策略是否合理。我的做法是写一个简化的模拟器用几个组件拼起来一个Pool类维护总容量和 extent 表一个Host类模拟单机的内存使用曲线可以用真实的监控数据回放也可以用泊松过程生成一个Manager类实现调度策略。时间轴用事件驱动每次分配/回收都推进一个模拟延迟。这样跑一遍策略的好坏一目了然不用等硬件。class Extent: def __init__(self, eid, size_gb): self.eid eid self.size_gb size_gb self.state free # free / reserved / allocated / reclaiming self.owner None self.ts 0 class Pool: def __init__(self, total_gb, extent_gb): self.extents [Extent(i, extent_gb) for i in range(int(total_gb / extent_gb))] def free_gb(self): return sum(e.size_gb for e in self.extents if e.state free) def alloc(self, host, gb, now): need int(-(-gb // self.extents[0].size_gb)) # 向上取整 picks [e for e in self.extents if e.state free][:need] if len(picks) need: return None for e in picks: e.state, e.owner, e.ts reserved, host, now return picks这里我把reserved状态显式建出来了就是前面说的中间态。模拟的时候故意让它偶尔超时看看策略会不会因此把池子漏光。这一步是很多模拟器会偷懒跳过的但它恰恰是线上事故的高发点。6.2 关键参数配置清单真要搭起来下面这几组参数必须提前定而且互相牵制不能一个个孤立地调。参数作用推荐初值调整依据extent 粒度分配最小单位1 GB业务容量变化步长变化越碎粒度越小蓄水池大小快路径可用余量单次最大扩容量的 2~3 倍小了频繁补货大了浪费分配超时预留态兜底5 s要大于主机侧上线耗时的 P99回收超时待回收态兜底30 s要大于页面迁移耗时的 P99冷页判定窗口回收迁移依据60 s太短误判热页太长回收不及时超配比例池容量 / 峰值总和0.7~0.8业务峰值的错峰程度错峰越明显可越低超配比例这个参数特别容易被误用。有人以为超配就是“卖出去的容量超过物理容量”越多越赚。实际上超配比例取决于业务峰值的时间错开程度如果四台机器的峰值严格错开超配到 1.5 都安全如果峰值高度重合比如都在每天同一时刻跑批超配就是给自己埋雷。我建议先统计一个月的历史数据算出“同一时刻的总需求”的分布取 P99 作为池子的设计容量再拿物理容量除一下得到的才是安全超配比。6.3 观测怎么做埋点比调参更重要调度系统最难的不是写策略是出了问题不知道从哪查。所以埋点要提前设计好至少四个维度每次分配/回收的耗时分布、池子的空闲容量时间序列、各主机的“已分配未使用”量、以及失败请求的拒绝原因分类。第三项特别重要它直接告诉你蓄水池设得合不合理。如果某台主机长期“已分配 200 GB 但只用 60 GB”说明蓄水池太大或者预测过于保守如果它频繁在“刚好够用”和“不够用”之间横跳说明蓄水池太小。这个指标比任何间接的利用率指标都直观。7. 实操中遇到的坑与排查路径7.1 现象与原因的对应关系下面这些是我在仿真和贴近真实的测试里踩过的按现象归类。申请成功但主机看不到容量。最常见的原因是 DC Region 没有正确 attach或者主机侧的地址解码器没更新。检查顺序是先看设备侧容量表里这块 extent 是否真的标成了已分配再看主机侧有没有收到完成通知最后看内存是不是被加进了可分配区。三层里哪一层断了现象都是“申请成功了但用不了”。容量归还了但池子没变大。落在“待回收”状态出不来。多数是主机侧的页面迁移没跑完或者跑完了没回调。如果是异步回调机制回调丢包的概率不低必须靠超时兜底。分配延迟忽高忽低。先看是不是并发申请导致的命令队列排队再看是不是设备内部在做元数据持久化。前者靠限流或者批量合并缓解后者只能换更快的存储介质或者改成异步刷。业务侧延迟抖动但各项指标都正常。这种最难受。我的经验是先怀疑页迁移回收触发的冷页迁移虽然异步但它会抢内存带宽。查一下迁移期间的带宽占用如果峰值明显抬升就把迁移速率限一下。7.2 速查表现象优先怀疑快速验证处置申请成功不可见DC Region 未 attach查设备容量表与主机可分配区重新 attach补齐上线流程归还后池子不涨待回收态卡住查主机侧迁移任务状态加超时兜底强制释放分配延迟抖动命令队列排队看并发申请数与队列深度批量合并、限流尾延迟抬升页迁移抢带宽对比迁移期与平稳期带宽限制迁移速率池子逐渐变少幽灵预留泄漏统计 reserved 态停留时长缩短分配超时加对账任务回收成功率低冷页判定失准对比判定窗口与真实访问模式调窗口改用多级判定7.3 几条不那么显然的经验第一条对账任务必须常驻。不管状态机设计得多严密跨节点系统一定会有状态漂移。我建议每隔几分钟跑一次全量对账把管理器记录的分配情况跟设备侧的实际表比对一遍发现不一致就以设备为准修正同时打告警。这个任务平时看起来毫无产出出事的时候能救命。第二条别在回收路径上做重操作。有些实现会在回收时顺手做内存压缩、碎片整理觉得反正都要动了不如一次做干净。结果就是回收耗时不可控管理器那边等超时触发强制释放数据完整性出问题。回收就老老实实回收整理另开窗口。第三条优先保证拒绝的语义清晰。容量不够的时候返回“暂时不可用请稍后重试”和“永久拒绝”对上层是完全不同的处理逻辑。前者业务可以退避重试后者业务得走降级路径。很多系统把这两种混成一个错误码导致业务侧无从判断只能无脑重试把压力放大。第四条蓄水池要做分级。一级池留几十 MB 应付瞬时小需求二级池留几个 GB 应付中等等扩容三级才去走完整申请流程。分级之后绝大多数扩容请求在一级池就解决了管理器的压力小一个数量级。8. 这套思路还能往哪挪几点延伸的想法论文的 DCS 是为 CXL 内存池设计的但它抽出来的东西其实很通用把资源的所有权和使用权分离中间加一层带状态机的调度器用蓄水池平滑慢操作用超时兜底处理失败。这套骨架换个场景照样成立。比如 GPU 显存的多卡共享本质是一样的矛盾静态切分浪费动态共享又怕互相踩。再比如分布式缓存节点的容量调度冷热不均导致的倾斜问题处理手法几乎可以照搬——冷数据驱逐、蓄水池预留、全局视图决策。甚至连存储层的精简配置thin provisioning都有相似的结构只不过那边的“上线”成本更高对预测准确性的要求更苛刻。我在实际搭这套东西的时候最大的体会是策略的重要性被高估了状态管理的重要性被低估了。几乎所有线上疑难问题最后都追到某个中间态没处理好而不是调度算法不够聪明。论文给出的价值与其说是那个具体的调度策略不如说是它把状态机、命令时序、边界条件这些东西摆得比较清楚让你知道该在哪几个点上小心。真要落地调参能调出来的收益有限把状态流转画明白、把超时兜底做扎实才是那 80% 的功夫所在。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询