第4章:分离式内存事务系统精读 —— FORD / Motor / LOTUS / AdaptX
拆解四篇里程碑论文的设计权衡:单版本 vs MVCC、锁集中 vs 分散、静态部署 vs 控制面反馈。建立 DM 事务系统的横向比较框架
DM 事务系统是分离式内存领域最成熟也最深度的子方向——FaRM(2014)定下 RDMA OCC 的基本盘后,FORD/Motor/LOTUS 三篇里程碑论文从 2022 到 2025 把”单版本 vs MVCC”、“锁集中 vs 分散”、“静态部署 vs 自适应”三组核心权衡逐一展开。本章不止概述,而是把每个系统的协议拆到可以自己实现的粒度——record 在内存里怎么布局、commit 几步、CAS 对什么字段、abort 怎么回滚——读完这章,看任何 DM 事务新论文你都能秒判它的位置,也能拿起 CREST 自己跑实验。
📑 目录
- 1. 阅读前置:必备的并发控制与 RDMA 基础
- 2. DM 事务的基本设定:CN / MN 分离与 OCC
- 3. FaRM(NSDI’14):奠基者
- 4. FORD(FAST’22):单版本 + cache-line 锁
- 5. Motor(OSDI’24):MVCC + 一致版本表
- 6. LOTUS(2025):锁也分离 + 反应式再均衡
- 7. AdaptX(2026,本系列):控制面抽象 + 5ms 反馈环
- 8. 横向对比矩阵:四个系统站在 design space 哪里
- 9. Abort 模式与失败场景汇总
- 10. DM 事务系统的开放问题
- 11. 自己读论文的checklist
- 自我检验清单
- 参考资料
1. 阅读前置:必备的并发控制与 RDMA 基础
读 FORD/Motor/LOTUS/AdaptX 之前,必须先有这几个基础概念。每个概念后面我会标注”在哪一篇论文里以什么形式出现”——这样你读论文时遇到能秒认出来。
1.1 隔离级别
| 级别 | 含义 | 典型论文用法 |
|---|---|---|
| Read Committed(RC) | 不读未提交的数据 | 工业数据库默认 |
| Snapshot Isolation(SI) | 事务读到一致快照,写不会被中间值污染 | Motor / LOTUS / AdaptX 默认 |
| Serializable(SR) | 等价于某种串行执行 | FORD / Motor 都支持,benchmark 默认 SR |
⭐ OCC 在 DM 系统里几乎都跑 SR/SI——RC 太弱,实际工业用 SR 就行。论文 abstract 里看到 “Serializable” 不要怵,就是这个 SR。
1.2 OCC vs 2PL
| 维度 | 2PL(两阶段锁) | OCC(乐观并发控制) |
|---|---|---|
| 事务开始 | 取锁 | 不取锁,只读快照 |
| 冲突时 | 等待(阻塞) | abort 重试 |
| 远端 CPU 参与 | 必须(锁状态机) | 不必须(用 CAS 实现锁) |
| 适合场景 | 高冲突 | 低-中冲突 |
| DM 系统选择 | ❌(需要远端 CPU) | ✅ |
🌟 关键事实:所有 DM 事务系统都用 OCC 而非 2PL——因为 2PL 的”等待”语义需要远端有线程持有锁、被唤醒,违反 disaggregation 原则。OCC 用 CAS 把”取锁”变成一次原子写,远端无感知。
1.3 OCC 三阶段模型
事务 T:
1. Read phase: 读所有需要的 record,记下版本号(read set)
本地修改写集 (write set), 但不提交
2. Validate: 重新检查 read set 的版本号是否还和当初一致
如果有变化 → abort & retry
如果没变 → 进入 commit
3. Commit: 原子写 write set + 更新版本号
用 CAS 取锁 → 写数据 → 写新版本号 → 解锁
所有 DM 事务论文都基于这个三阶段,差异在:
- read 怎么做(单版本拉一次 / MVCC 拉版本表)
- validate 怎么做(再读一次 / 用版本号字段比较)
- commit 怎么做(CAS 锁 + WRITE / 多副本同步 / 异步)
- 锁在哪里(MN-side / CN-side)
1.4 必备 RDMA 基础:三个原语
| 原语 | 在论文中的角色 |
|---|---|
| READ(rkey, addr, len) | 读 read set:一次读 record(可能整 cache-line)。FORD/Motor 大量使用 |
| CAS(rkey, addr, expected, new) | 8B 原子比较交换:取锁 / 更新版本号。所有 OCC commit 的核心 |
| WRITE(rkey, addr, data, len) | 写 write set:落数据 + 解锁。一般 doorbell-batched |
关键不支持的事:
- ❌ 超过 8B 的原子操作:CAS 只能 8B,所以”锁”必须能压进 8B
- ❌ 远端计算:不能让 MN 跑代码,所有逻辑必须 CN 侧
详见第 2 章。
1.5 必备 RDMA 基础:atomic-IOPS 瓶颈
ConnectX 系列网卡的 atomic op(CAS/FAA)有单卡硬上限:
| NIC 代际 | atomic IOPS 上限(单卡) | 影响 |
|---|---|---|
| ConnectX-3/4 | ~1 M ops/s | 早期论文(FaRM)主要瓶颈 |
| ConnectX-5 | ~1.5 M ops/s | LOTUS 提”锁分离”的硬件背景 |
| ConnectX-6/6-Dx | 5-10 M ops/s | FORD/Motor 主要测试平台 |
| ConnectX-7 | 估计 15-20 M ops/s | 仍然不够”无限” |
一旦 commit 阶段触发热点 record 的 CAS 排队 → atomic IOPS 占满 → 整个 MN 停摆。这是所有 DM 事务系统不得不绕的核心难题——论文里凡是讲”atomic-IOPS bottleneck”或”hotspot”的地方,根都在这。
1.6 必备 RDMA 基础:cache-line 对齐
RDMA NIC 的 DMA 是以 64B 为粒度(cache-line size,x86)。读一个 8B 字段实际上 NIC 也得拉一整 cache-line。FORD 的”cache-line lock + version + data 头部对齐” 设计就是利用这个:一次 READ 64B 同时拿到锁、版本号、数据头。
详见第 2 章 §2.2 + §6。
1.7 必备背景:doorbell batching + selective signaling
- doorbell batching:多个 op 一次 post,摊销 MMIO 代价
- selective signaling:只让最后一个 op 产生 CQE,前面 op 隐式确认
FORD/Motor/CREST 的 commit 都用这两个优化——第 2 章 §6 讲过。读论文里”batched commit”四个字时心里要画出”N 个 CAS + N 个 WRITE 一次 doorbell”这张图。
🌟 过完这一节,你就具备读 DM 事务论文的最小知识集。下面进入正题。
2. DM 事务的基本设定:CN / MN 分离与 OCC
DM 事务系统的标准物理模型:
Compute Node (CN) Memory Node (MN)
┌──────────────────┐ ┌──────────────────┐
│ TXN logic │ │ Tables / Indexes │
│ Scheduler │ RDMA │ Locks │
│ Worker threads │ ◄──────► │ Versions │
│ (RDMA verbs) │ 100Gbps │ (NIC-friendly │
└──────────────────┘ │ layout) │
└──────────────────┘
多节点(可扩) 多节点(可扩,共享池)
2.1 角色定义
- CN(compute node):跑事务逻辑、调度、worker 协程,所有计算在 CN
- MN(memory node):几乎不做计算,只是 NIC + 内存(可以是普通服务器,也可以是 SmartNIC + DRAM,未来甚至是 CXL pool)
- 协议:OCC 三阶段——Read → Validate → Commit。CN 通过 RDMA one-sided op 直接读写 MN 上的数据
2.2 与传统分布式数据库对比
| 维度 | 传统分布式 DB(Spanner/Aurora) | DM 事务系统 |
|---|---|---|
| 存储节点 | 全栈数据库(SQL 解析 + 优化器 + 存储) | 仅”裸内存 + NIC” |
| 通信 | gRPC / 自定义 RPC | RDMA verbs |
| 事务协议 | 2PL / 2PC + Paxos | OCC + RDMA CAS |
| 扩展性 | 跨 region | 同 LAN(RDMA 限制) |
| 延迟 | ms 级 | µs 级 |
| 适用场景 | 全球 OLTP | 高吞吐 OLTP / 内存数据库 |
🍎 直觉比喻:DM 事务就像超市自助结账——你自己扫码(CN 算)、自己装袋(CN 整理),结账机(MN)只负责扣款(CAS)。结账机数量有限,人多了排队,这就是 atomic-IOPS 瓶颈。
2.3 为什么不用 2PL
2PL 需要远端 CPU 维护锁状态机:
- 锁是有”等待队列”的(blocking)
- 锁有”持有方信息”(owner thread id)
- 锁有”释放协议”(unlock + 唤醒等待方)
所有这些都需要 MN 侧有线程在跑——违反 disaggregation 原则。
OCC 把锁简化成”用 CAS 原子写一个版本号字段”:
- 取锁 =
CAS(addr, 0, txn_id)(从 0 改成自己的 id,失败=已被占) - 解锁 =
WRITE(addr, 0)(改回 0) - 远端不需要 CPU 介入
2.4 核心难点:atomic-IOPS 瓶颈
DM 系统的 commit 必须用 RDMA CAS / FAA 提交版本号,而 ConnectX 网卡 atomic op 上限 5-10M ops/s(见 §1.5)。
热点数据 + 高并发 → 排队 → abort/retry 风暴:
100 个 CN 并发 → 同一 record CAS
│
▼
MN 网卡 atomic 单元
(5-10M IOPS 上限)
│
┌───────────┴───────────┐
│ 排队 → 后到的 CAS 失败 │
│ → CN 看到 abort │
│ → CN retry │
│ → 又一波 CAS │
│ → 死循环 (retry storm)│
└───────────────────────┘
🧠 关键洞察:所有后续设计(Motor/LOTUS/AdaptX)都在绕这个瓶颈——MVCC 减少冲突、锁分离迁回 CN、控制面 throttle 进入流量。论文里”atomic-IOPS bottleneck” 出现的地方,都是在指向这条主线。
3. FaRM(NSDI’14):奠基者
读 FORD 之前必须先理解 FaRM,FORD 的”标杆性”是相对于 FaRM 的。
3.1 FaRM 的设定
- 微软 Bing 团队,2014 NSDI
- 第一个把 RDMA + OCC 在大规模数据库 workload 上跑通的系统
- 提出 NIC-assisted transaction 的概念雏形
3.2 FaRM 的事务协议
FaRM commit (简化, 实际更复杂):
1. Lock phase: 对 write set 的每个 record, 发 CAS(lock_addr, unlocked, locked)
任何 CAS 失败 → abort
2. Validate: 再 RDMA READ read set, 检查版本号
任何不匹配 → 释放已持锁, abort
3. Commit-backup:Primary 写 backup MN(类似 2PC backup)
backup 写完才能 commit primary
4. Commit-primary:写新数据 + 新版本号到 primary
5. Truncate: 异步释放锁 + 通知 backup truncate log
3.3 FaRM 的痛点(FORD 要解决的)
- 每个 record 多次 atomic op:lock 一次、写元数据一次、unlock 一次 → atomic-IOPS 浪费
- 元数据散布:lock 字段、version 字段、data 不在同一 cache-line → 多次 RDMA READ
- commit 阶段 RDMA op 数 = O(|write set|),长事务延迟随写集线性增
🌟 FaRM 的贡献是奠基——它定义了”RDMA + OCC + MN/CN 分离”这个范式。FORD 把这个范式优化到接近网络上限。
⭕ FaRM 是闭源的(微软内部使用),所以学术界后续工作主要基于 FaRM 的论文 + 自己重写。FORD 是事实上的”开源 FaRM-like”系统。
4. FORD(FAST’22):单版本 + cache-line 锁
FORD(FAST’22, Zhang et al.)是后续所有 DM 事务系统的工程标杆——第一个把 OCC 在 RDMA 网络上跑到 接近网络带宽上限。
4.1 核心设计:cache-line 对齐 record
FORD 单条 record 在 MN 内存里的布局:
地址 ┌────────────────────────────────────────────────┐
0x1000 → │ lock (8B) │ version (8B) │ data... │ ← cache-line 1
│ (CAS target) │ (read 验证) │ │
0x1040 → ├────────────────────────────────────────────────┤
│ data... (continued) │ ← cache-line 2
...
每个 record 起始 cache-line 包含:
- lock(8B):CAS 目标。0=未锁,非零=持锁者 txn_id
- version(8B):提交时递增,validate 阶段读这个
- data(剩余 48B):数据头部直接 inline
读 record 时:一次 RDMA READ 64B(或更多)同时拿到 lock、version、data。后续步骤无需再 RDMA 拉元数据。
4.2 Doorbell-batched commit 详细步骤
假设事务 T 的 write set = {r1, r2, r3}:
Step 1 — Lock phase (一次 doorbell):
POST CAS(r1.lock_addr, 0, T_id) ─┐
POST CAS(r2.lock_addr, 0, T_id) ─┼─ 一次 ibv_post_send (链表)
POST CAS(r3.lock_addr, 0, T_id) ─┘
POLL CQ — 等三个 CAS 全完成
检查 wc.compare_value:
- 全部为 0 → 全锁住 ✅
- 有非零 → 此 record 已被别人锁,abort
Step 2 — Validate (一次 doorbell):
POST READ(read_set[i].version_addr) for each i ─┐
... │── 一次 ibv_post_send
POST READ(read_set[N].version_addr) ─┘
POLL CQ — 等所有 version 读回
比较:任意 version != 当初读到的 → abort, 释放已持锁
Step 3 — Write (一次 doorbell):
POST WRITE(r1.data_addr, new_r1_data) ─┐
POST WRITE(r2.data_addr, new_r2_data) ─┤
POST WRITE(r3.data_addr, new_r3_data) ─┤
POST WRITE(r1.version_addr, new_version) ─┤
POST WRITE(r2.version_addr, new_version) ─┤
POST WRITE(r3.version_addr, new_version) ─┤── 一次 ibv_post_send
POST WRITE(r1.lock_addr, 0) ─┤ (解锁也合并)
POST WRITE(r2.lock_addr, 0) ─┤
POST WRITE(r3.lock_addr, 0) ─┘
selective signaling: 只最后一个 SIGNALED
关键 op 数:
- 单写集 record 的 op 数 = 3-4 次 RDMA op
- 三阶段总共 3 次 doorbell(摊销 MMIO 一次)
- 整个 commit 延迟 ≈ 3 × RTT (~10 µs 量级)
4.3 FORD 跟 FaRM 的差异(关键!)
| 维度 | FaRM | FORD |
|---|---|---|
| Record 元数据布局 | lock/version/data 散布 | cache-line 对齐 + inline |
| Commit 阶段 op 数 | O(write set) × 多次 | O(write set) × 1 次(整批) |
| Doorbell 数 | 高 | 3 次(三阶段) |
| 复制 | 同步 backup commit | 类似(略有差异,本章不展开) |
🌟 FORD 的关键贡献:用 cache-line 对齐把 atomic-IOPS 从”每个字段一次”压到”每条 record 一次”,解决了 FaRM 在小事务上的瓶颈。
4.4 实测性能
(参考 FORD 论文 + 后续复现工作的数据)
- TPC-C 6 节点(1 MN + 5 CN),100GbE RDMA:~1.5 M txn/s
- 接近网卡带宽上限(commit 阶段几乎打满 RDMA 通路)
- 单事务延迟中位数 ~30-50 µs
4.5 FORD 的局限(后续工作要解的)
- 单版本 → 长事务读会被写阻塞(读到中间态需要重试)
- 锁集中在 MN → MN 网卡的 atomic-IOPS 仍是瓶颈(高冲突时 abort 风暴)
- 静态部署:replication / sharding 一次定好,负载漂移就退化
- 写放大:每次 commit 至少 N 次 WRITE(数据 + 版本号 + 解锁),热点 record 反复写
5. Motor(OSDI’24):MVCC + 一致版本表
Motor(OSDI’24, Wu et al.)把 FORD 的”单版本”升级到 多版本(MVCC),主要解长事务/混合负载的痛点。
5.1 核心设计:Consecutive Version Table (CVT)
普通 MVCC 的版本链是链表:
record A:
v1 (head) → v2 → v3 → v4 → ...
ts=100, ts=200, ...
链表对 RDMA 不友好——遍历要多次 RDMA READ(指针追逐)。
Motor 的 CVT 是紧凑、定长、cache-line 友好的:
record A 在 MN 内存里(简化布局):
地址 ┌──────────────────────────────────────────────────┐
0x2000 → │ lock │ slot_idx │ slot[0] │ slot[1] │ slot[2] │ slot[3] │
│ (8B) │ (8B) │ v1 │ v2 │ v3 │ v4 │
│ │ │ ts=100 │ ts=200 │ ts=300 │ ts=400 │
└──────────────────────────────────────────────────┘
↑ slot_idx 指向最新版本 (循环利用)
- 每条 record 有固定 K 个版本槽(K = 4-8,论文实验用 4)
- 写操作 round-robin 覆盖最旧的槽,slot_idx 指向当前最新位置
- snapshot read 一次 RDMA READ 拿整个 CVT(几个 cache-line),CN 端按时间戳挑合适版本
5.2 Snapshot Read 详细流程
1. CN 想读 record A 在 ts=250 时的版本
2. RDMA READ A 整块 (一次 op, ~256B):
→ 拿到 slot_idx + 全部 slot 数据
3. CN 在本地按时间戳挑:
slot[0]: ts=100 (太旧)
slot[1]: ts=200 ← 选这个 (最大但 <=250)
slot[2]: ts=300 (太新)
4. 返回 slot[1] 的数据
无需遍历版本链,无需多次 RDMA op。
5.3 写操作流程
1. CN 取锁: CAS(record.lock_addr, 0, txn_id) — 同 FORD
2. CN 读当前 slot_idx: 走 RDMA READ
3. CN 计算下一个 slot 位置: next_idx = (slot_idx + 1) % K
4. CN 写新 version 到 slot[next_idx]: WRITE(slot_addr, new_data + new_ts)
5. CN 更新 slot_idx 到 next_idx: WRITE(slot_idx_addr, next_idx)
6. CN 解锁: WRITE(record.lock_addr, 0)
写放大:相比 FORD 的”覆盖原版本”,Motor 多写一个 slot_idx。但老版本不丢——这是 MVCC 的本质收益。
5.4 Garbage Collection
CVT 大小 K 是定长——最旧版本会被覆盖。安全前提:没有任何活跃事务还在引用这个版本。
Motor 用 全局时间戳水位线 (GTSW):
- 每个事务开始时拿一个 ts(全局递增)
- 系统跟踪所有活跃事务的最小 ts (= GTSW)
- 任何 ts < GTSW 的版本可以被安全覆盖
GTSW 推进协议:CN 周期性把”我目前活跃的最小 ts”上报给一个 coordinator(可以用 RDMA atomic FAA 实现),coordinator 算出全局最小并广播。
5.5 Motor vs FORD 对比
| 维度 | FORD | Motor |
|---|---|---|
| 版本模型 | 单版本 | MVCC(K 个槽) |
| 长事务读 | 容易 abort(读到写中间态) | 友好(读老版本不冲突) |
| 写放大 | 1× | ~1.5-2×(写 + 整理 CVT) |
| 单条 record 内存占用 | ~64B | ~256B (K=4) |
| 适合场景 | 短 OLTP(TPC-C) | 混合 OLTP/OLAP |
🌟 Motor 的关键贡献:证明 MVCC 在 RDMA disaggregation 设定下也能做到”读多写少”工作负载下 ~2× FORD,代价是写放大与内存占用。
5.6 Motor 的局限
- 写放大仍然吃 RDMA 带宽
- CVT 大小 K 是静态参数,选小了重要版本被覆盖、选大了浪费空间
- 锁仍然集中在 MN → atomic-IOPS 瓶颈未解决
6. LOTUS(2025):锁也分离 + 反应式再均衡
LOTUS(论文 2025 年发表)是 第一个明确把”锁的位置”作为一阶设计变量 的工作。
6.1 核心洞察
atomic-IOPS 瓶颈在哪里?在 MN 网卡。那如果把锁迁回 CN 呢?
6.2 LOTUS 的锁布局
FORD/Motor: LOTUS:
┌────────┐ ┌────────────────┐
│ MN │ │ CN-side │
│ ┌──┐ │ │ hash table │
│ │锁│ │ ← CAS 集中在 MN │ (per-CN 一份)│
│ └──┘ │ └────────────────┘
└────────┘ │
│ 跨 CN 协调
┌───────┴───────┐
│ Lock service │
│ (协议) │
└───────────────┘
每个 CN 有一份 本地 lock hash table,key = (table_id, record_id)。取锁时:
- CN 在本地 hash 表里查询,如果记录已被本 CN 持有 → 直接重入(或冲突)
- 跨 CN 时,用一个分布式锁服务协议(论文里有详细协议,大致是基于 RDMA write + 自定义协议,绕开 CAS)
6.3 跨 CN 协调协议
LOTUS 论文里给的协议大致框架:
- 每个锁有一个 leader CN(根据 record_id 哈希到固定 CN)
- 申请锁时,CN A 通过 RDMA WRITE(不是 CAS!)向 leader CN 的 lock 表写”我要锁”
- Leader CN 异步处理(用一个 polling 线程检查这个表)
- Leader CN 同意后回 ACK(也是 RDMA WRITE)
🍎 直觉:把”锁原子操作”从硬件 CAS 转换为”软件协议”,牺牲单次 op 速度换吞吐——RDMA WRITE 的 IOPS 远高于 CAS。
6.4 Reactive Rebalancing(~100ms)
LOTUS 的另一创新:运行时监控负载,触发 shard 迁移。
监控
└─ 每 N ms 收集 CN-side 的负载/冲突率
负载不均判定
└─ 如果某 CN 的 lock contention 率显著高于平均
触发迁移
└─ 把热 shard 从一个 CN 迁到另一个 CN
└─ 数据复制(RDMA WRITE)
└─ 锁状态转移
反应窗口: ~100 ms
6.5 LOTUS 的局限
🌟 LOTUS 的关键贡献:第一次用控制面思想做 DM 事务——不再”一次部署到底”,而是运行时反馈调整 shard 位置。本质是控制面的雏形。
但仍然不够:
- 100ms 反应窗口对短突发热点仍然太慢(想象 200ms 的热点突发,LOTUS 100ms 之后才开始迁,迁完热点已经过了)
- 锁迁回 CN 之后,跨 CN 协调本身有开销(需要跨 CN 一致性协议)
- 仍然是单一控制环路(只看 shard 迁移),没有解决 retry storm 类问题
- shard 迁移本身代价大:数据搬运 + 锁状态转移,迁移期间该 shard 暂停服务
7. AdaptX(2026,本系列):控制面抽象 + 5ms 反馈环 ⭐
AdaptX 是本系列(本指南作者)的工作,把 LOTUS 的”反应式调整”思路进一步抽象成控制面框架,并把反应窗口从 100ms 压到 5ms。
⭐ 注:这一节是本系列作者工作,正在 ATC 投稿迭代中,数据为内部实测。
7.1 控制面 4-tuple 抽象
每个控制环路定义为 ⟨Signal(信号), Actuator(执行器), Rule(规则), Composition contract(组合契约)⟩。
| 元素 | 含义 | 例子(Loop A) |
|---|---|---|
| Signal | 测什么 | abort rate(取自 OCC validation 阶段) |
| Actuator | 控什么 | admission rate(限流/放行决策) |
| Rule | 怎么从 signal 推 actuator | AIMD(增加放行 / 倍减) |
| Composition | 与其他环路如何共存 | signal-disjoint + actuator-disjoint |
7.2 Loop A:Retry Storm Throttle
问题:OCC 的 abort 触发 retry,retry 又会触发新一轮 abort——典型 retry storm,导致 thpt 崩盘。
Loop A 设计:
- Signal:CN 端观察的 abort rate(滑动窗口,5ms 间隔统计)
- Actuator:每个 worker 的入队速率 admission control(决定 worker 是否能拿新事务)
- Rule:AIMD(TCP NewReno 风格)
- 反应窗口:5ms(阈值跨过 → 立即调整)
AIMD 数学
参数:
h:abort rate 阈值,默认 0.35α:倍减因子,默认 0.5(超阈值时 admission 减半)δ:加性增,默认 4(每次 tick 增加 4 个 admission slot)NF:noise floor,默认 2(admission 不能低于这个值)
每 5ms tick:
abort_rate = abort_count / total_count # 上一窗口
if abort_rate > h:
admission = max(NF, admission * α) # 倍减
else:
admission = admission + δ # 加性增
直觉:超热时倍减(快速降流量)、平稳时小步增(慢慢摸索上限)——和 TCP 拥塞控制完全同源。
实测数据
在 extreme regime(1/40 仓库 95% 概率热点)下:
- baseline:thpt = 0.05 Mops/s, P99 = 30ms
- Loop A:thpt = 0.095 Mops/s (1.91× baseline), P99 = 100ms (3.4×)
- Bootstrap 95% CI: [1.02×, 2.48×]
throughput 显著提升,P99 略变差(throttle 增加了排队等待,但避免了 retry storm)。
7.3 Loop B:NIC IOPS-aware Replica Read
问题:Motor MVCC 的 backup 副本本来只用作 fault tolerance,read 都打 primary。但 primary MN 的 NIC 满载时,backup MN 是闲置的——能不能动态分流?
Loop B 设计:
- Signal:每个 MN 的 NIC IOPS(从 mlx5 hw_counters 读 rx_read_requests / rx_write_requests / rx_atomic_requests)
- Actuator:CN 选择副本(primary 还是 backup)
- Rule:把读流量倾向到 IOPS 更低的副本
- 与 Loop A 组合:signal disjoint(abort rate vs IOPS)、actuator disjoint(限流 vs 副本选择)
信号采集
// MN 侧 publisher:每 5ms 读 hw_counters, 写到 MR 暴露的一个 8B slot
uint64_t rx_reads = read_sysfs("/sys/class/infiniband/mlx5_2/ports/1/hw_counters/rx_read_requests");
uint64_t rx_writes = read_sysfs(".../rx_write_requests");
uint64_t rx_atomics = read_sysfs(".../rx_atomic_requests");
uint64_t total = rx_reads + rx_writes + rx_atomics;
// 写 IOPS 速率 = (current - last) / interval
uint64_t iops = (total - last_total) * 1000 / 5; // ms → s
*mr_slot = iops; // CN 通过 RDMA READ 拉这个值
CN 侧选择
int SelectReplica(int primary_mn, vector<int> backup_mns) {
int best = primary_mn;
uint64_t best_iops = nic_iops[primary_mn];
for (int m : backup_mns) {
if (nic_iops[m] < best_iops * 0.7) { // 阈值: 比 primary 低 30%+
best = m;
best_iops = nic_iops[m];
}
}
return best;
}
Loop B 一致性策略
backup 上的版本可能略旧——读到老版本怎么办?两条策略:
- OCC version-check fallback:读 backup 拿到 (data, version),如果 OCC validate 阶段 version 不匹配 primary → fallback 重读 primary
- Stale read 容忍:对 read-only 短查询,允许稍微 stale 的快照(类似 Spanner stale read)
7.4 与 LOTUS 的对比
| 维度 | LOTUS | AdaptX |
|---|---|---|
| 反应窗口 | ~100 ms | ~5 ms (20× 快) |
| 控制环路数 | 1(shard rebalance) | 2+(Loop A + Loop B + 可扩展) |
| 抽象 | ad-hoc heuristic | 4-tuple + composition contract |
| 失败模式分析 | 简略 | alt-extreme regime 反例完整披露 |
| 开源 | 部分 | ✅ CREST patch |
7.5 AdaptX 的局限和坦白
- alt-extreme(2/40 仓库 92% 概率热点) regime 上 Loop A 反而 0.87× baseline——signal trigger 不准导致误调,这是控制面的固有 false-positive 风险
- 5ms 窗口需要 RDMA 同步的特殊编码,不是所有平台都易实现
- Loop B 端到端依赖双 MN 拓扑,需要 backup 副本(写放大 2×)
- 当前两个环路都是经验阈值(h=0.35 等),没有形式化收敛证明
⭕ AdaptX 的真正价值是”把 LOTUS 单环路扩展成框架”——这意味着第三、第四个控制环路(比如基于 PCIe utilization 的 NIC 选择,基于队列深度的 batch 大小)可以按同一抽象增量加入,而不是重写整个系统。
🌟 关键贡献:把”自适应”从 ad-hoc 启发式提升为可组合的控制面框架,并证明 5ms 反应窗口是可达的(LOTUS 100ms → AdaptX 5ms,两个数量级)。
8. 横向对比矩阵:四个系统站在 design space 哪里
数据面演化
────────────────►
单版本 MVCC 多副本 + reactive
┌──────────┬──────────┬─────────────────┐
│ FaRM │ FORD │ LOTUS │
│ (2014) │ (FAST'22)│ (2025) │
│ 锁在MN │ 锁在MN │ 锁在CN │
├──────────┼──────────┼─────────────────┤
│ │ Motor │ │
│ │ (OSDI'24)│ │
│ │ MVCC+CVT│ │
│ │ 锁在MN │ │
├──────────┴──────────┴─────────────────┤
│ AdaptX (2026) │
│ 控制面框架 + 多环路 + 5ms 反馈 │
└────────────────────────────────────────┘
▼
控制面演化
| 系统 | 版本模型 | 锁位置 | 反馈环 | atomic-IOPS 主瓶颈 | 适用场景 | 开源状态 |
|---|---|---|---|---|---|---|
| FaRM | 单版本 | MN | ❌ | 严重 | 经典 RDMA OCC 教学/对照 | 闭源 |
| FORD | 单版本 | MN | ❌ | 严重 | 短 OLTP, TPC-C | ✅ |
| Motor | MVCC | MN | ❌ | 仍严重(写) | 混合 OLTP/OLAP | ✅ |
| LOTUS | (沿 Motor) | CN | reactive shard rebalance(100ms) | 缓解 | 中等长事务 + 漂移负载 | 部分开源 |
| AdaptX | (沿 Motor/LOTUS) | CN | two control loops, 5ms | 缓解 + 限流 | 极端 hotspot + 漂移负载 | ✅(CREST patch) |
| 对照:DrTM | 混合 | MN(HTM 加速) | ❌ | atomic 用 HTM | 同机柜 + Intel HTM 硬件 | 部分 |
design space 上的几个洞察:
- 数据面(版本模型 + 锁位置)在 FORD → Motor → LOTUS 走完了一圈
- LOTUS 引入”反馈”是控制面的萌芽,但只一个环路、100ms 反应、未抽象
- AdaptX 把这条线提到框架层,让后续工作可以按同一接口加更多环路
9. Abort 模式与失败场景汇总
读论文时常碰到”Abort”这个词,但 abort 有多种触发原因——理解这些有助于看懂 retry storm:
| Abort 触发 | 发生阶段 | 系统响应 |
|---|---|---|
| 取锁失败(CAS 不为 0) | Lock phase | 释放已持锁,重试 |
| Validate 阶段 version 变 | Validate phase | 释放所有锁,重试 |
| Backup 写超时 | Commit phase | 标记事务失败 |
| 跨 CN 协议超时(LOTUS) | Lock phase | 重试 / 降级到 leader-CN local |
| Loop A 限流(AdaptX) | 入队阶段(其实不是 abort) | 等下一个 tick |
| MR 错误 / RDMA 异常 | 任意 RDMA op | 系统级恢复 |
9.1 Retry storm 的形成
Worker 1: 事务开始 → CAS 成功 → 业务读 → ...
Worker 2: 事务开始 → CAS 失败(被 W1 锁了) → abort → retry
Worker 3: 事务开始 → CAS 失败(被 W1 锁了) → abort → retry
Worker 4: ... 同上 ...
...
Worker 1: commit 完释放 → 大量 retry 同时再 CAS
→ 又只一个成功,其他全 abort
→ atomic IOPS 占满, 所有 worker 反复打转
→ thpt 崩盘
这就是 retry storm 的产生。AdaptX Loop A 的核心动机就是阻断这个循环。
9.2 与 Optimistic 假设的关系
OCC 的”乐观”假设是冲突少——大部分事务一次过、少数 retry。hotspot 场景违背这个假设——本质上是 OCC 不适合的 regime。
🌟 这就是为什么 AdaptX 提”控制面”——承认 OCC 不是普适最优,在不友好的 regime 用 admission control 把它救回来。
10. DM 事务系统的开放问题
读完前面几个系统,留下了哪些开放问题值得做研究/工程?
10.1 Per-key admission
AdaptX Loop A 的 admission control 是每 worker 粒度的——所有事务一视同仁。但 alt-extreme regime 暴露了一个问题:当多个 hotspot 形状不一(2 个 92% vs 1 个 95%)时,全局放行率失效。
frontier 方向:per-key 信号驱动的 admission——记录每个热 key 的 abort 来源,精确限流到这条 key 而不是整个 worker。
具体子问题:
- per-key 状态怎么不爆内存(只跟踪 top-N 热 key)?
- 信号怎么收集(每次 abort 标注 key)?
- 决策怎么不损害冷 key 的吞吐?
10.2 CXL pool 上的 OCC
如果 MN 是 CXL pool 而不是普通 RDMA 服务器,atomic op 的语义就完全变了——CXL.mem 的 load/store 是硬件一致的,不需要 CAS verbs。OCC 协议怎么重设计?
frontier 方向:hybrid OCC——同机柜走 CXL load/store,跨机柜走 RDMA verbs,两套协议在一个事务内组合。
具体子问题:
- CXL load/store 的原子性保证(单 cache-line 内)够不够替代 CAS?
- Validate 阶段怎么跨 CXL/RDMA 边界做?
- Failure domain 重新定义(CXL 同机柜 vs RDMA 跨 region)
10.3 MVCC backup 用作 read-replica
Motor 的 CVT 给每条 record 留 K 个版本——这些老版本有没有可能被多个 CN “并行读”?如果 backup MN 上有同样的 CVT,老版本天然是 read-only 副本,可以做 read scaling。
frontier 方向:leaderless read scaling on MVCC——读流量按版本时间戳路由到任意 backup,只 primary 处理写。
具体子问题:
- Stale read 的可接受窗口由谁定义?
- Backup 之间不一致怎么修(也是 leader 推送,还是 backup-to-backup 协议)?
- 跟 AdaptX Loop B(NIC IOPS-aware replica read)是同一思路的不同切入点
10.4 多控制环路的形式化合成
AdaptX 提出了 ⟨S, A, R, C⟩ 的 composition contract,但多于两个环路时怎么 composability?有没有控制论意义上的可证收敛?
frontier 方向:借鉴**控制论 / SDN(Onix、Maestro 的精神)**做形式化,给”控制面 DM 系统”画一个全局稳定性边界。
具体子问题:
- 怎么形式化定义”signal disjoint”的充分条件?
- 多环路同时震荡(每个都在自己的 signal 上调)的判别条件?
- 收敛到稳定 admission point 的 Lyapunov 函数?
10.5 SmartNIC offload
ConnectX-7 + Bluefield-3 SmartNIC 提供了 NIC 内嵌 ARM 核——能不能把 OCC 的部分逻辑下放到 NIC?比如 validate 阶段在 NIC 端做,绕开 CN-MN 一次往返?
frontier 方向:NIC-side OCC validate。
挑战:NIC 内核很弱(几 GB SRAM 不到、CPU 几个 ARM 核),协议要重新设计。
11. 自己读论文的 checklist
读 DM 事务相关论文时,按这个 checklist 走:
- 物理设定:CN/MN 配置如何?网卡代际?
- 数据布局:record 在 MN 内存的字节级布局图?
- OCC 三阶段:read/validate/commit 各自几次 RDMA op?
- 锁的位置:在 MN 还是 CN?用什么 op 取锁?
- 复制策略:同步 backup commit 还是异步?
- GC:版本回收、log truncate 怎么做?
- 失败模式:abort 怎么触发、retry 怎么走?
- 实验对照:跟 FaRM/FORD/Motor 比?用什么 benchmark(TPC-C/TATP/SmallBank/YCSB)?
- 数据点:thpt/latency 数字 vs baseline 多少倍?
- 创新归属:这篇论文的 Δ 是哪一格(版本模型/锁位置/反馈环)?
🌟 跑一遍这个 checklist,新论文你就能 5 分钟分类、20 分钟读懂关键创新。
✅ 自我检验清单
- OCC 三阶段:能默写 read / validate / commit 各阶段做的事
- CN/MN 拆分:能讲清为什么 RDMA 让 CN/MN 分离比 2PL/2PC 更自然
- atomic-IOPS 瓶颈:能讲清这是所有 DM 事务系统的核心难点,以及它的硬件根因
- FORD 核心:能用一句话讲清 FORD 相比 FaRM 多解决了什么(cache-line 对齐 + doorbell batch)
- FORD record 布局:能徒手画出 lock+version+data 的 cache-line 布局
- FORD commit 步骤:能列出 lock/validate/write 三阶段各自的 RDMA op
- Motor MVCC:能讲清 consecutive version table 怎么避免遍历版本链
- Motor snapshot read:能讲清”一次 RDMA READ 拿全 K 个版本”
- LOTUS 锁分离:能讲清”锁迁到 CN”如何绕开 atomic-IOPS 瓶颈
- LOTUS 100ms 反应:能讲清反应式 rebalance 的触发与代价
- AdaptX 控制面:能默写 4-tuple ⟨S, A, R, C⟩,以及 Loop A 在哪个 regime 上 1.91×
- AdaptX AIMD math:能写出 admission_rate 的更新规则(α=0.5, δ=4)
- 横向 design space:任意给一个系统名,能在矩阵里指出位置(版本/锁/反馈环)
- Abort 模式:能列出 OCC 系统的 4-5 种 abort 触发原因
- 开放问题:能列出至少 3 个 DM 事务的 frontier 方向
📚 参考资料
关键论文(必读,按时间顺序)
- FaRM: Fast Remote Memory(Dragojević et al., NSDI’14):USENIX 链接 —— 历史奠基,所有 RDMA OCC 的”祖宗”
- FaSST: Fast, Scalable and Simple Distributed Transactions with Two-sided RDMA Datagram RPCs(Kalia et al., OSDI’16):对照组,two-sided RPC 路线
- DrTM: Fast In-Memory Transaction Processing using RDMA and HTM(Wei et al., SOSP’15):HTM + RDMA 混合 OCC —— 硬件辅助路径
- NAM-DB(Zamanian et al., VLDB’17):NAM 架构,disaggregation 早期实证
- Eris: Coordination-Free Consistent Transactions(Li et al., SOSP’17):协调免除的事务,与 OCC 路线对照
- FORD(Zhang et al., FAST’22):USENIX 链接 —— 单版本 + cache-line 锁 + doorbell batch ⭐
- Motor: Enabling Multi-Versioning for Distributed Transactions on Disaggregated Memory(Wu et al., OSDI’24):USENIX 链接 —— MVCC + CVT ⭐
- LOTUS(2025):lock 分离 + reactive rebalance —— 控制面思想的雏形
- AdaptX: A Closed-Loop Control Plane for Disaggregated-Memory Transactions(2026,本系列) ⭐:控制面 4-tuple 抽象 + 5ms 反馈环
行业对照
- Polar / Aurora 的 RDMA 演进:工业云数据库的 RDMA 落地路径(对照学习)
- Spanner / CockroachDB:全球分布式事务,跟 DM 事务是不同尺度的对照
行业讨论
- USENIX 历年 DB+RDMA panel:每届 OSDI / NSDI 都会有相关 BoF
- Carnegie Mellon 15-721 高级数据库系统(Andy Pavlo)讲义中关于 RDMA-OCC 的章节
- Database Internals(Alex Petrov)第 3 部分关于分布式事务
经典 OCC / 并发控制基础
- On Optimistic Methods for Concurrency Control(Kung & Robinson, 1981):OCC 协议奠基论文,40+ 年历史
- Concurrency Control and Recovery in Database Systems(Bernstein et al., 1987):并发控制圣经
框架文档
- CREST(Capability for RDMA-Enabled System Testing):本系列实验载体,RDMA 事务实验框架,FORD 风格 OCC + Motor MVCC 集成
- Motor 开源仓库 —— 可作 Motor 协议代码学习
下一章切换视角,看 AI 推理领域怎么把同一套 disaggregation 思想用到 KV-cache 上。