第10章:自适应迁移与冷热演化
迁移触发条件、批量化与流水化、读写放大效应、保序与一致性、冷热演化路径、跨节点迁移——长记忆数据从生到死的完整流动学
第 9 章决定”对象该在哪一级”,这一章决定”什么时候、怎么搬”。两者是一枚硬币的两面——放置算法再聪明,如果迁移路径笨拙(同步阻塞、撕裂一致性、放大写入),所有”理论最优”都会被工程现实打回原形。本章把长记忆数据从生到死的”流动学”讲透:迁移触发的三种条件、批量化与流水化、读写放大的物理代价、搬运中不撕裂的保序协议、冷热演化的下沉路径、跨节点迁移的特殊问题、多类型协同搬运。读完这章你能回答两个工程问题:给定一份”对象 A 应该从 SSD 升到 HBM”的决策,这次搬运该怎么做才不破坏在线 SLO? 以及 整个长记忆系统怎么让数据自然”老去”而不是手动管理生命周期?
📑 目录
- 1. 放置 vs 迁移:一枚硬币的两面
- 2. 迁移触发的三种条件
- 3. 批量化与流水化:把搬运摊薄
- 4. 读写放大的物理代价
- 5. 保序与一致性:搬运中不撕裂
- 6. 冷热演化的下沉路径
- 7. 跨节点迁移的特殊性
- 8. 多类型协同迁移
- 9. 设计准则
- 10. 给本项目的整合启示
- 自我检验清单
- 参考资料
1. 放置 vs 迁移:一枚硬币的两面
1.1 两者各管什么
| 角色 | 决定 | 时机 |
|---|---|---|
| placement(Ch9) | 对象应该在哪一级 | 每次访问 / 周期 LP / 模型 |
| migration(本章) | 何时实际搬运 + 怎么搬不撕裂 | placement 决策异步触发 |
1.2 为什么必须分开
如果把”决定”和”搬运”绑成一件事(placement 一改就立即同步迁移),会撞上几个硬墙:
- 同步搬运阻塞主路径 —— Ch3 阈值 T3,GB 级搬运 ~100ms 量级
- 决策抖动浪费 IO —— LP 边界附近的对象可能反复升降
- 多对象冲突 —— 多个对象同时想升 HBM,谁先?
- 跨节点搬运本身可能失败 —— 网络抖动时不能让用户请求一起挂
🌟 关键设计:placement 输出”目标状态”,migration engine 异步执行,两者解耦——这是任何生产系统的基本架构。
placement ──output──> desired_state(每个对象的目标 tier)
│
▼
migration engine ──execute──> actual_state
(异步、限速、保序、可中断)
2. 迁移触发的三种条件
2.1 触发 1:placement 输出变化
最直接的触发——Ch9 的 placement 决策变了:
prev_placement(obj) = SSD
new_placement(obj) = DRAM
↓
migration engine 收到搬运请求
但不是立即搬——还要看下面两个条件。
2.2 触发 2:收益超过阈值
搬运本身有代价。只在”未来收益 > 搬运代价 + 安全余量”时才执行:
migrate iff
expected_future_access × (latency_old - latency_new)
> size × bandwidth^-1 + setup_cost
+ ε(hysteresis 余量)
🍎 直觉:你不会因为今晚要喝水就把整桶水从地下室搬到客厅——除非未来一周都要喝。
2.3 触发 3:容量压力
资源紧张时强制下沉优先级低的对象:
if HBM occupancy > 90%:
evict cold objects → DRAM
if DRAM occupancy > 95%:
cascade evict → SSD
if SSD 也满了:
报警 + 触发归档到对象存储
📍 设计建议:容量压力是”强触发”,优先级最高——不能让 placement 在后台慢慢决策,等到 OOM 就晚了。
2.4 三类触发的优先级
优先级 1:容量压力 (即时执行,可降级)
│
▼
优先级 2:placement 变化 (限速,平滑)
│
▼
优先级 3:预测式预取 (空闲时段执行)
⭐ 关键准则:任何迁移决策必须经过这三层过滤——避免”决策正确但搬运冲掉用户请求”。
3. 批量化与流水化:把搬运摊薄
3.1 单对象搬运 vs 批量搬运
单对象逐个搬运:
setup ── transfer ── teardown → 单对象 100ms
setup ── transfer ── teardown → 下一个 100ms
……
总耗时 = N × 100ms
批量搬运(把同向同 tier 的对象合并):
setup once ── transfer all ── teardown once → 接近线速
总耗时 ≈ N × size / bandwidth
🌟 数量级差距:对小对象批量化收益最大——KB 级 trace 单条搬运 setup 开销比 transfer 还大。
3.2 批量化的实践要点
| 维度 | 内容 |
|---|---|
| 同向合并 | 同一个 (from_tier, to_tier) 的多个对象合并一次搬运 |
| 时间窗 | 几十-几百 ms 内的请求合并(权衡延迟与批量大小) |
| 大小阈值 | 攒到一定字节量再发(避免太小的批量) |
| 优先级保留 | 高优先级对象不等批量,立即搬 |
3.3 流水化:计算与搬运重叠
主路径在跑(LLM forward / attention),迁移引擎在背景搬——用 CUDA streams / 异步 IO:
GPU compute stream: ████████████████████████████ (主路径)
GPU copy stream: ████ ████ ████ (后台 H2D / D2H)
SSD IO 队列: ████ ████ (NVMe 异步)
RDMA 队列: ████ (远端拉取)
📍 关键工程:copy stream 与 compute stream 物理上独立(不抢同一执行单元),才能真正并发。Hopper / Blackwell 的 TMA / DMA engine 给这事提供了硬件基础(模块零第 2 章)。
3.4 搬运速率限制(rate limiting)
不限制时,后台搬运可能把带宽吃光,把主路径打饿:
bandwidth_for_migration ≤ total_bandwidth × budget_ratio
(典型 10-20%)
🌟 设计准则:给迁移分配”预算配额”,剩下的留给主路径——这正是 Ch11 单 token 边际成本要纳入的项。
4. 读写放大的物理代价
4.1 读放大:迁移期间的额外读
把对象从 A 搬到 B,A 上的读不会直接消失——还要从 A 读出来才能写到 B:
migrate(obj, A→B):
read obj from A ← 物理读 A 一次
write obj to B ← 物理写 B 一次
总放大系数:从 A 看 +1 次读,从 B 看 +1 次写
4.2 写放大:LSM 风格的”等价”代价
如果用 LSM-style 的”内存接收 + 后台合并”机制(参考 FreshDiskANN / RocksDB),一份数据可能被写多次:
写 1:RAM 缓冲 → size × 1
写 2:Level 0 SSD → size × 1
写 3:Level 1 SSD → size × 1.x(合并放大)
写 N:Level N SSD → size × ~10× 合计
📍 关键事实:LSM 风格写放大典型是 10-30× ——SSD 寿命 / 实际带宽都被这个数字摊薄。
4.3 多类型混存的放大冲突
不同类型对象的写放大互相干扰:
| 场景 | 后果 |
|---|---|
| KV 大块写 + trace 零碎写共用 NVMe | trace 的小写被 KV 大写”挤”成长尾延迟 |
| 向量增量更新 + KV 卸载共用 RDMA pool | RDMA queue depth 抢占 |
| 多模态 blob 拷贝 + scratchpad 持久化共用本地 NVMe | NVMe queue 拥塞 |
⭐ 设计建议:给不同类型分配独立 IO 队列 / 独立带宽配额——避免类型间互相干扰。
4.4 缓解写放大的工程方法
| 方法 | 适用 |
|---|---|
| 大块顺序合并 | 多个 KV 块攒一起写 SSD |
| 避免回头路 | 不要 SSD → DRAM → SSD 这种振荡 |
| 冷数据直接归档 | 跳过中间层,SSD → 对象存储一步到位 |
| erasure coding 写一次 | 写 EC 比 3 副本省 50% IO |
5. 保序与一致性:搬运中不撕裂
5.1 撕裂场景示例
t=1: 用户读 obj_X → 系统从 SSD 读
t=2: migration engine 开始把 obj_X 升到 DRAM
t=3: 用户更新 obj_X(只更新 SSD 上的副本)
t=4: migration engine 完成,DRAM 上是"旧版" obj_X
↓
t=5: 系统从 DRAM 读 obj_X → 拿到旧数据!
🌟 关键问题:搬运期间数据可能被更新,新位置拿到的是旧版本——长记忆系统经常出现的一致性 bug。
5.2 保序的三种协议
协议 A:读写锁 + copy-on-write
migration:
1. 加读锁(其它读可以,写阻塞)
2. snapshot 当前数据 → 搬到 B
3. 切换 metadata → 解锁
写入:
等待迁移完成或走 copy-on-write 新版本
简单但写入有等待。
协议 B:版本号 + 双写
每个 obj 有 version
migration:
1. read v_A,搬到 B 标 v_A
2. 期间 A 上有写,A 升到 v_A+1
3. 完成后比对版本,如果 B 落后就 retry 或丢弃
读取:
从 metadata 取最新 version 的位置
无写阻塞,但有 retry 开销。
协议 C:基于 log 的同步(参考数据库 logical replication)
A 是主,B 是 replica
迁移期间:
1. 启动 log shipping(A 的更新流式发给 B)
2. 当 B 追到 A 的最新 LSN,切换主副关系
写入:
永远写主,主切换瞬时
最适合长生命周期对象的迁移——但工程复杂度高。
5.3 各协议适用场景
| 协议 | 适用对象类型 |
|---|---|
| 读写锁 + COW | 短生命周期、写少(KV Cache 大部分场景) |
| 版本号 + 双写 | 高频更新但容忍 retry(向量索引部分) |
| log-based | 长生命周期、写流式(trace、Agent 协作日志) |
📍 设计建议:LMObject 的 type_specific 字段标”使用哪种迁移协议”——不同类型默认不同协议。
5.4 metadata 切换的原子性
无论哪种协议,最终的 “metadata 切换” 必须原子:
切换前:obj_X 的位置 = (SSD, 0x1234)
切换后:obj_X 的位置 = (DRAM, 0x5678)
↓
切换瞬间,任何读取必须要么看到旧位置要么看到新位置,**不能看到中间状态**
🌟 工程要点:metadata service 用 CAS / 版本号 + 单 writer 串行化实现原子切换。
6. 冷热演化的下沉路径
6.1 数据”自然老去”模型
理想的长记忆系统,数据应该自动从热到冷下沉,不需要工程师手动管理生命周期:
create
│
▼
HBM(active)
│ 几分钟无访问
▼
DRAM(warm)
│ 几小时无访问
▼
SSD(cold)
│ 几天无访问
▼
远端归档 / 对象存储(archive)
│ 几月无访问 + TTL
▼
删除 / 仅保留摘要
6.2 不同类型的”老去时间”不同
| 类型 | HBM→DRAM | DRAM→SSD | SSD→归档 |
|---|---|---|---|
| KV(active session) | session 结束 | 几小时 | 几天 |
| KV(prefix cache) | 几分钟未命中 | 几天 | 几周 |
| 向量(热点) | 几小时 | 几天 | 几周 |
| 向量(普通) | 直接 DRAM | 几天 | 几月 |
| 多模态 embedding | 几小时 | 几天 | 几月 |
| 多模态 blob | 直接 SSD | — | 几月 |
| Trace(关键) | 任务结束 | 几天 | 永久或 TTL |
| Trace(辅助) | 任务结束 | 几小时 | 几天 |
📍 设计建议:老去策略按 LMObject.type_specific 配置——默认值合理,namespace 可覆盖。
6.3 加速下沉的两个机会
机会 1:打包归档
冷数据下沉到归档时,多个对象打包成一个大 chunk(类似 tar 包),减少元数据开销:
归档 chunk:
┌─────────────────────────────┐
│ obj_id_1: bytes │
│ obj_id_2: bytes │
│ ……(几千个对象) │
│ index(快速定位每个 obj) │
└─────────────────────────────┘
存储到 S3/OSS,metadata 只记 chunk_id + offset
🌟 效果:对象级 metadata 压缩 100×,归档读取依然 OK(用 chunk 内 index 快速定位)。
机会 2:语义级合并
类似数据可以合并/摘要化:
- 同一用户的多个相似 trace → 抽象成 pattern
- 多次相似的 KV 段 → 用最有代表性的一份代替
- 向量库的冷簇 → 重新聚类压缩
⭐ 观察:语义级合并涉及 LLM 自己的总结能力——这是 Agent Memory 上层逻辑(模块五)与本模块底层放置的协作点。
6.4 反向:激活已归档数据
冷数据被重新需要时(用户突然回访 6 个月前的对话):
归档 → 拉回 SSD(几秒-几分钟)→ 索引重建 → 进入热路径
🌟 关键准则:激活路径 P99 时延必须可预测——可以慢,但不能”看运气”。在用户 perceived 的”loading…”反馈下,几秒延迟用户能接受,几十秒就崩。
7. 跨节点迁移的特殊性
7.1 本机迁移 vs 跨节点迁移
| 维度 | 本机 HBM↔SSD | 跨节点(via RDMA pool) |
|---|---|---|
| 带宽 | 8 TB/s - 7 GB/s | 100-400 Gbps |
| 失败概率 | 极低 | 中(网络抖动) |
| 协议 | 内核 / GDS | RDMA verbs |
| 一致性 | 内核 page cache 帮 | 自己写协议 |
| 监控 | iostat / nvidia-smi | NIC counters / NCCL stats |
7.2 跨节点迁移的故障场景
t=1: A 节点开始把 obj 推到 B 节点
t=2: 网络抖动,RDMA 报错
t=3: A 不知道 B 是否收完整,B 也不知道
↓
一致性危机
📍 缓解方法:
- 写后校验(checksum 比对)
- 两阶段 commit(B 收完后回执,A 才更新 metadata)
- 超时重试 + 幂等 key(失败可安全重试)
7.3 与第二模块(分离式池化)的接口
跨节点迁移本质就是”分离式 RDMA pool”的搬运——本章 migration engine 通过模块零第 4 章的 NIXL / GPUDirect RDMA作为底层传输。
Migration Engine
│
▼
Migration Protocol(本章定义)
│
▼
Transport Layer(NIXL / NCCL / 自建 RDMA) ← 模块零 + 模块十三
│
▼
Network(IB / RoCE)
⭐ 设计含义:本章的迁移协议不重新发明传输——直接架在 NIXL / RDMA 之上,只关注协议层(保序、一致性、批量化)。
7.4 项目第二模块的角色
第二模块”分离式资源池化 + 索引访问 + 存算调度”在本章视角下:
- 提供分离式 RDMA pool 作为 LMObject 的一种 backend
- 提供跨节点的高速 P2P 通道(NIXL / DeepEP 思路)
- 提供调度层与 placement 决策对接(谁跑在谁旁边)
🌟 协作模式:第一模块出 placement 决策,第二模块负责高速执行。
8. 多类型协同迁移
8.1 sibling 数据的协同
第 8 章的 sibling 关系(KV 和 scratchpad、embedding 和 blob 同源)在迁移时必须协同:
一个用户的活跃数据集合:
KV_session(HBM)
scratchpad(HBM) ← sibling
embedding(DRAM)
image_blob(SSD)
……
用户休眠 20 分钟后:
整个集合协同下沉到下一级
而不是 KV 单独下沉、其它留着
🧠 关键洞察:sibling 集合的协同迁移,比单对象 LRU 更接近真实业务——用户的”活跃 / 休眠”状态自然影响 Ta 全部数据的冷热。
8.2 namespace 级协同迁移
更进一步:一个 namespace(用户 / 任务)的所有数据集体演化:
namespace = "user_alice/task_xyz"
↓
placement 算法把 namespace 当成"调度单元"
迁移决策一次涉及 namespace 内所有对象
📍 优势:降低 metadata 决策开销 + 提高 IO 批量化收益 + 自然反映业务。
8.3 全局重分布(rebalance)
定期触发的全集群重分布:
- 节点新加入或下线 → 重分布数据
- 工作负载长期漂移 → 重设类型先验
- 容量配置变化 → 调整下沉阈值
🌟 关键约束:rebalance 必须可中断 + 不阻塞主路径——通常用低优先级 IO 配额慢慢搬。
9. 设计准则
9.1 准则 1:placement 与 migration 解耦
placement 输出”目标状态”,migration 异步执行——两者不能耦合到同一个调用栈。
9.2 准则 2:迁移永远后台,有 rate limit
后台带宽预算 10-20%,永不挤占主路径。
9.3 准则 3:三层触发优先级:容量 > placement > 预取
容量压力即时,placement 平滑,预取空闲时做。
9.4 准则 4:批量化是小对象的生命线
setup/teardown 开销固定,KB 级对象必须批量化。
9.5 准则 5:迁移协议按对象类型选
KV 用读写锁 + COW、向量用版本号、trace 用 log-based。
9.6 准则 6:metadata 切换原子,迁移可中断
任何瞬时都能取消迁移,不留中间状态。
9.7 准则 7:sibling 协同 + namespace 级演化
按业务自然单元做迁移,不要逐对象决策。
9.8 准则 8:跨节点迁移有幂等 + 超时重试
网络抖动是常态,protocol 必须容错。
10. 给本项目的整合启示
10.1 项目示范系统的优先实现
| 优先级 | 内容 | 难度 |
|---|---|---|
| ⭐⭐⭐ | 三层触发条件 + rate limit | 中(2 个月) |
| ⭐⭐⭐ | 批量化 + 流水化(对接 GDS / NIXL) | 中(2-3 个月) |
| ⭐⭐ | 读写锁 + COW(KV 默认协议) | 中 |
| ⭐⭐ | 版本号(向量) | 中 |
| ⭐⭐ | log-based(trace,可借鉴 Kafka / RocksDB) | 中-高 |
| ⭐ | sibling / namespace 级协同 | 中 |
| ⭐ | 全局 rebalance | 高 |
10.2 项目第一模块在本章的科学问题落点
主问题:多类型 + 多协议 + 多约束的迁移调度,如何在保证 SLO 不破坏的前提下最小化总迁移开销 + 写放大?
子问题:
- sibling 协同迁移的形式化模型
- 多类型混存场景下的写放大下界(理论)
- 跨节点迁移的失败恢复一致性证明
⭐ 可发表方向:迁移协议 + 跨类型协同 + 写放大优化都能成 SOSP / FAST 级别工作。
10.3 与第二、第三模块的协作
- 第二模块:本章迁移 engine 直接调它的 RDMA pool / NIXL 通道
- 第三模块:迁移协议中的”两阶段 commit”和”幂等 + 重试”是容错的基础元素
🌟 关键整合:迁移协议是三模块的”共享词汇表”——一份对象搬运过程中,三模块各管一段,协议保证缝合不漏。
✅ 自我检验清单
- placement vs migration:能讲清两者解耦的必要性
- 三层触发:能默写容量 / placement / 预取的优先级
- 批量化收益:能用 setup/teardown 解释为什么小对象批量化收益最大
- 读写放大:能解释 LSM 写放大 10-30× 的来源
- 三种保序协议:能给 KV / 向量 / trace 各推荐一种合适协议
- metadata 原子切换:能解释为什么必须 CAS / 单 writer 串行化
- 冷热演化路径:能默写 HBM→DRAM→SSD→归档的典型时间窗
- 跨节点迁移特殊性:能列出至少 3 个本机迁移没有的故障场景
- sibling 协同:能用一个具体例子讲清”为什么 KV 和 embedding 一起搬”
- 8 条设计准则:至少默写 6 条
📚 参考资料
数据迁移 / 一致性协议经典
- Two-Phase Commit / Three-Phase Commit —— 分布式事务经典
- Logical Replication(PostgreSQL / MySQL) —— log-based 迁移
- Copy-on-Write(BSD / Linux mm 子系统)
- MVCC(经典数据库教科书)
缓存替换 / 迁移系统
- HeMem(ASPLOS 2021) —— DRAM-PMEM 自动迁移
- TPP(Meta ASPLOS 2023) —— 透明页迁移
- Pond(Microsoft ASPLOS 2023) —— Azure CXL pool 迁移
- NVIDIA Page Migration Engine —— Hopper/Blackwell 硬件页迁移
LSM-tree 与写放大
- The Log-Structured Merge-Tree (LSM-Tree) (O’Neil et al., 1996)
- RocksDB:rocksdb.org
- LevelDB / Cassandra —— 工业 LSM 实现
跨节点 RDMA 迁移
- NVIDIA NIXL:github.com/ai-dynamo/nixl
- NVIDIA NCCL —— 集合通信(可作迁移传输基础)
- GPUDirect RDMA —— 模块零第 4 章
长生命周期数据归档
- Apache Iceberg / Delta Lake —— 大数据归档与版本化
- Cassandra Tombstones / TTL —— 软删除与过期
调研笔记
- 项目调研 - 长记忆分离式存储 —— 8 篇核心论文 + 三模块清单
本系列其它模块
- 本模块第 9 章 分层放置策略 —— migration 的输入
- 本模块第 11 章 性能-成本协同优化 —— 把迁移开销纳入成本模型
- 模块零第 4 章 分布式通信与 I/O 优化 —— NIXL / RDMA 传输层
- 模块十三 新型互联与远程内存 —— 跨节点搬运的硬件底座