第4章:AURA 骨架 —— cohort、ownership、状态机、不变式
拆解 AURA 的 12 个核心模块、cohort 数据结构、3 状态机(OWNED/TRANSFERRING/FALLBACK)与 4 个不变式(I1–I4),建立看完整论文的脚手架
读到这里你应该已经认同”必须做动态锁所有权”的命题。本章不再讨论”为什么”,而是直接把 AURA 这个系统的脚手架立起来:12 个模块各自承担什么职责、数据结构怎么设计、3 个状态怎么转、4 个不变式怎么把一致性钉死。读完你应该能在白板上不查论文画出 AURA 的全套架构图,并能徒手列出每个不变式被违反时会引发什么具体 bug——这是后续 5/6/7 章细节展开的前置。
📑 目录
- 1. 12 模块鸟瞰图
- 2. cohort 数据结构:不要求 key 相邻
- 3. OwnerMap 与 epoch:全局快照模型
- 4. 3 状态机:OWNED / TRANSFERRING / FALLBACK
- 5. 4 个不变式:I1–I4
- 6. AffinityRouter 路由协议
- 7. OwnerLockTable 与 OwnerRpc
- 8. FallbackLock:降级回 MN atomic 的开关
- 自我检验清单
- 参考资料
1. 12 模块鸟瞰图
1.1 三层架构
AURA 的 12 个模块按职责分三层:
┌─────────────────────────────────────────────────────────┐
│ ★ 数据面 (Data Plane) │
│ │
│ ❶ AffinityRouter ❷ TxnExecutor ❸ OwnerLockTable │
│ ❹ OwnerRpc ❺ FallbackLock │
│ │
│ → 每事务都走这条路径,决定 commit 落到哪里 │
└─────────────────────────────────────────────────────────┘
▲ ▼ 控制 / 反馈
┌─────────────────────────────────────────────────────────┐
│ ★ 控制面 (Control Plane) │
│ │
│ ❻ AccessGraphProfiler ❼ LockCohortGenerator │
│ ❽ OwnershipPlanner ❾ TransferController │
│ ❿ OwnerMapPublisher │
│ │
│ → 每 5ms 一轮,决定下一个 epoch 的 OwnerMap │
└─────────────────────────────────────────────────────────┘
▲ ▼ 信号 / 计数
┌─────────────────────────────────────────────────────────┐
│ ★ 监测面 (Telemetry Plane) │
│ │
│ ⓫ TraceCollector ⓬ AuraStats │
│ │
│ → 持续采集 trace + NIC 计数,喂给控制面 │
└─────────────────────────────────────────────────────────┘
🌟 关键事实:三层完全解耦——数据面只看 OwnerMap snapshot,不知道控制面何时改决策;控制面只看 trace + 计数,不参与单事务路径;监测面只采集,不决策。这是为什么 AURA 数据面可以 µs 级低延迟跑事务而控制面可以 ms 级慢慢做决策。
1.2 12 模块完整职责表
| # | 模块 | 层 | 输入 | 输出 | 频率 |
|---|---|---|---|---|---|
| ❶ | AffinityRouter | 数据 | TxnRequest, OwnerMap | route to (CN, MN) | 每事务 |
| ❷ | TxnExecutor | 数据 | TxnRequest | 执行结果 (commit/abort) | 每事务 |
| ❸ | OwnerLockTable | 数据 | acquire/release | (granted/wait/abort) | 每锁请求 |
| ❹ | OwnerRpc | 数据 | 跨 CN 协调 | RPC reply | 跨 cohort 时 |
| ❺ | FallbackLock | 数据 | acquire/release | RDMA CAS to MN | fallback 路径 |
| ❻ | AccessGraphProfiler | 控制 | trace samples | weighted graph | 每窗口 (5ms) |
| ❼ | LockCohortGenerator | 控制 | graph delta | merge/split deltas | 每窗口 |
| ❽ | OwnershipPlanner | 控制 | cohort + load | new OwnerMap | 每窗口 |
| ❾ | TransferController | 控制 | new OwnerMap diff | 4 阶段迁移 | 触发时 |
| ❿ | OwnerMapPublisher | 控制 | new OwnerMap + epoch | 广播给所有 CN | 每次 publish |
| ⓫ | TraceCollector | 监测 | TxnRequest 路径 | sampled trace | 每事务(采样) |
| ⓬ | AuraStats | 监测 | RNIC counters / OS stats | 多信号聚合 | 高频采样 |
1.3 一次事务的端到端路径
1. Client → TraceCollector.record(tx)
2. AffinityRouter.lookup(tx.keys, OwnerMap_v_e)
→ returns (owner_cn, fallback_flag)
3. if owner_cn == self:
TxnExecutor.run() → OwnerLockTable.acquire()
else if owner_cn != self:
OwnerRpc.send(tx) → 远端 owner CN 上重复 step 2
else if fallback_flag:
TxnExecutor.run() → FallbackLock.cas_mn()
4. 若 acquire OK → write data → release lock → commit
5. AuraStats.observe(commit_latency, abort_count, ...)
🍎 直觉比喻:AffinityRouter 是”大堂经理”——根据客户找对应的支行;OwnerLockTable 是”支行柜员”——本地处理;OwnerRpc 是”跨支行调单”;FallbackLock 是”打回总行”。
2. cohort 数据结构:不要求 key 相邻
2.1 cohort 是逻辑同步单元,不是物理分区
很多人第一次看 cohort 这个词以为是”连续 key 区间”,这是错的。AURA 的 cohort 定义:
cohort = 任意 key_group 的子集,由 LockCohortGenerator 根据访问亲和性聚合而成。
key 之间不要求物理相邻、不要求同表、甚至不要求同分片。唯一的要求是访问图上聚簇。
2.2 为什么必须支持非相邻 key
TPC-C NewOrder 是经典反例:
NewOrder 事务访问的表(按调用顺序):
──────────────────────────────────────
1. Warehouse[wid] 读
2. District[wid, did] 读 + 写(更新 d_next_o_id)
3. Customer[wid, did, cid] 读
4. Stock[wid, iid] × 5–15 读 + 写(多个 stock items)
5. Item[iid] × 5–15 读
6. NewOrder[wid, did, oid] 写(插入)
7. OrderLine[wid, did, oid, line] × 5–15 写(插入)
🌟 观察:所有这些 record 都共享 wid。如果 cohort 必须 contiguous,要嘛把 7 张表所有 wid=1 的行连续布局(破坏数据结构),要嘛 cohort 只能是单表内的连续区间——丢失 cross-table 亲和性。
AURA 的 cohort 直接表示成 vector<key_group_id>,物理上完全无序。kg2cohort 反向索引让 lookup 仍然 O(1)。
2.3 数据结构 C++ 实现
// key_group:一组逻辑上一起被访问的 key(粒度比单 record 大)
using key_group_id_t = uint64_t;
using cohort_id_t = uint32_t;
using cn_id_t = uint16_t;
using epoch_t = uint64_t;
enum class CohortState : uint8_t {
OWNED = 1,
TRANSFERRING = 2,
FALLBACK = 3,
};
struct Cohort {
cohort_id_t id;
epoch_t epoch; // 该 cohort 上一次状态变更的 epoch
cn_id_t owner; // 仅 OWNED / TRANSFERRING 有意义
cn_id_t next_owner; // TRANSFERRING 时的目标 owner
CohortState state;
std::vector<key_group_id_t> members; // 任意子集
// ★ 控制面统计字段
uint64_t access_count; // 当前窗口访问次数
double local_hit_rate; // 命中本 owner CN 的比例
};
// 反向索引:key_group → cohort
struct CohortIndex {
std::unordered_map<key_group_id_t, cohort_id_t> kg2cohort;
std::unordered_map<cohort_id_t, Cohort> cohorts;
epoch_t map_epoch; // OwnerMap 整体 epoch
};
// AffinityRouter 一次 lookup 的伪代码
inline cn_id_t AffinityRouter::lookup(key_group_id_t kg, const CohortIndex* idx) {
auto it = idx->kg2cohort.find(kg);
if (it == idx->kg2cohort.end()) return CN_FALLBACK; // OwnerMap miss
const auto& cohort = idx->cohorts.at(it->second);
switch (cohort.state) {
case CohortState::OWNED: return cohort.owner;
case CohortState::TRANSFERRING: return CN_TRANSFERRING; // caller 决定 retry/wait
case CohortState::FALLBACK: return CN_FALLBACK;
}
}
2.4 key 与 key_group 的两层映射
为什么不直接用 key → cohort?因为单 key 粒度太细,OwnerMap 会爆。AURA 引入中间层 key_group:
| 层 | 粒度 | 数量级 | 例子 |
|---|---|---|---|
| key | record 唯一 ID | 千万级 | (wid=1, did=2, cid=3) |
| key_group | 逻辑同访问组 | 千~万级 | ”wid=1 的所有相关行” |
| cohort | 同步单元 | 百~千级 | 多个 key_group 聚簇 |
两层映射 = key → key_group → cohort。key → key_group 由 schema / hash 决定(应用提供 group key 函数即可);key_group → cohort 由 AURA 在线学习。
🧠 关键洞察:key_group 是应用提示,cohort 是 AURA 自己学——这跟 LOTUS 不同(LOTUS 直接要求应用提供 partition)。AURA 的”应用先验”只是粗粒度 group key,比 LOTUS 的 critical field 弱得多。
2.5 cohort 大小的取舍
| 大小 | 优点 | 缺点 |
|---|---|---|
| 小 cohort(10 key_group) | 命中率高、迁移成本低 | OwnerMap 大、跨 cohort 事务多 |
| 大 cohort(1000 key_group) | OwnerMap 小、跨事务少 | 迁移成本高、owner CN 自身瓶颈 |
🌟 经验:cohort 大小 ≈ 一个 owner CN 5ms 内能处理的”独立锁请求数”。CX-6 上一个 CN 大约能处理 100k–500k local lock op/s × 5ms = 500–2500 ops,对应 cohort 容量 几百级。
3. OwnerMap 与 epoch:全局快照模型
3.1 OwnerMap 的物理表示
OwnerMap 是 cohort_id → (state, owner, epoch) 的全局映射,每次 publish 时整体推进 epoch。
struct OwnerMapEntry {
cohort_id_t id;
CohortState state;
cn_id_t owner;
epoch_t cohort_epoch;
};
struct OwnerMap {
epoch_t map_epoch;
std::vector<OwnerMapEntry> entries; // 排序,便于二分
std::unordered_map<cohort_id_t, size_t> id2index;
};
3.2 epoch 的三层粒度
AURA 区分三种 epoch(不要混淆):
| epoch 类型 | 粒度 | 推进时机 |
|---|---|---|
| map_epoch | 全局 OwnerMap 整体版本 | 每次 publish |
| cohort_epoch | 单个 cohort 上次状态变更 | 该 cohort 迁移完成时 |
| txn_epoch | 事务进入时持有的快照版本 | 事务开始时 |
🍎 直觉比喻:map_epoch 是”全本目录的版本号”;cohort_epoch 是”目录里某一章的版本号”;txn_epoch 是”读者手里那本目录是哪一版”。
3.3 epoch 单调性的实现
class OwnerMapPublisher {
std::atomic<epoch_t> current_epoch_{0};
public:
epoch_t publish(OwnerMap new_map) {
// 1) 用 CAS 推进 epoch(避免并发 publish)
epoch_t old = current_epoch_.load();
epoch_t next = old + 1;
if (!current_epoch_.compare_exchange_strong(old, next)) {
return 0; // 别人已经 publish,本次放弃
}
new_map.map_epoch = next;
// 2) 广播到所有 CN
for (auto cn : all_cns_) {
broadcast_owner_map(cn, new_map);
}
return next;
}
};
⭐ 关键约束:epoch 全局单调递增,永不回退。客户端看到 epoch_t e1 > e2 就知道 e1 是更新的版本——这是分布式快照模型的本钱。
3.4 客户端如何处理 OwnerMap 不一致
不同客户端可能看到不同 epoch 的 OwnerMap(广播延迟)。处理策略:
// 客户端事务开始
epoch_t my_epoch = local_owner_map_.epoch;
auto target_cn = AffinityRouter::lookup(key, local_owner_map_);
// 发到 target_cn 的 OwnerRpc 携带 my_epoch
RpcRequest req{my_epoch, txn_data};
auto reply = OwnerRpc::send(target_cn, req);
if (reply.status == STALE_EPOCH) {
// target_cn 看到的 epoch 比我新(且 cohort 已迁走)
// → 拉取最新 OwnerMap,重 lookup
refresh_owner_map();
goto retry;
}
🌟 关键设计:stale epoch 不是错误,是协议预期路径。AURA 不要求所有 CN epoch 完美一致,而是用”携带 epoch 的请求 + 远端验证”实现 lazy 一致。
3.5 OwnerMap 的传播代价
| 大小估算 | 数字 |
|---|---|
| 单 OwnerMapEntry | 8B + 8B + 2B + 8B = 26B(packed 32B) |
| 1000 cohort 的 OwnerMap | 32KB |
| 5ms 一次广播到 8 CN | 8 × 32KB = 256KB / 5ms ≈ 51 MB/s |
⭕ 互补:全量广播代价不大——但聪明做法是广播 delta(只传变更的 cohort)。AURA 默认做 delta + 周期性全量校准。
4. 3 状态机:OWNED / TRANSFERRING / FALLBACK
4.1 状态定义
| 状态 | 含义 | 数据面行为 |
|---|---|---|
| OWNED | 某 CN 唯一持有该 cohort 的锁权威 | 走 OwnerLockTable / OwnerRpc 路径 |
| TRANSFERRING | 正在迁移(freeze-drain-handoff-publish 中) | 数据面 stall 或 abort 该 cohort 上的事务 |
| FALLBACK | 退回到 MN atomic CAS 路径 | 走 FallbackLock,使用 RDMA CAS |
4.2 状态转换图
┌──── split / migrate decided ────┐
│ │
▼ │
OWNED ──────────────► TRANSFERRING ────┘
│ │
│ │ migration done
│ ▼
│ OWNED (new owner)
│
│ owner crash / heat dropped
▼
FALLBACK
│
│ heat returns / planner picks new owner
▼
TRANSFERRING ──► OWNED (new owner)
4.3 每条 transition 的触发者
| 起始 | 终止 | 触发者 | 条件 |
|---|---|---|---|
| OWNED | TRANSFERRING | OwnershipPlanner | merge/split/migrate 决策 |
| TRANSFERRING | OWNED (new owner) | TransferController | 4 阶段协议完成 |
| OWNED | FALLBACK | TransferController | owner 心跳超时 / 主动降级 |
| FALLBACK | TRANSFERRING | OwnershipPlanner | 工作负载再次升温 |
| FALLBACK | FALLBACK | — | (持续状态,靠 5ms 窗口决定是否退出) |
4.4 TRANSFERRING 持续多久
Phase 1 Freeze: ~10µs(owner 接受新 acquire 的 cutoff)
Phase 2 Drain: ~50–200µs(等 in-flight 完成或 abort)
Phase 3 Handoff: ~50µs(一次大 RPC 传锁状态)
Phase 4 Publish: ~50µs(广播 OwnerMap delta)
───────────────────────
总计:~150–350µs
⭐ 关键事实:TRANSFERRING 是 µs 级状态,与 5ms 决策窗口比是 1/30 量级。这是 AURA 控制面 vs 数据面解耦的物理基础。
4.5 FALLBACK 的存在价值
⭕ 互补:FALLBACK 不是 bug,是故意保留的安全网:
- owner CN 故障时,cohort 不会卡住——直接降级到 MN atomic
- 冷数据不值得占着 owner——降级到 fallback 释放控制面 CPU
- 渐进部署时,未升级的 CN 仍能通过 fallback 访问
🧠 关键洞察:有 FALLBACK 这条退路,AURA 才能放心做激进的迁移决策——错了大不了回 fallback,正确率不需要 100%。
5. 4 个不变式:I1–I4
5.1 不变式总览
| ID | 名称 | 一句话 | 违反后果 |
|---|---|---|---|
| I1 | Authority uniqueness | 任一时刻同一 cohort 的权威只在一处 | 双权威 race + 数据腐化 |
| I2 | Epoch monotonicity | epoch 永不回退 | 老 OwnerMap 误为最新 + 路由错乱 |
| I3 | Migration drain | 迁移期间所有 in-flight 事务必须 drain 或 abort | 老 owner 持锁不释放 + 死锁 |
| I4 | Data commit invariance | 数据 commit 路径不依赖锁所有权位置 | 迁移导致 commit 中途失败 |
5.2 I1:Authority Uniqueness
形式化陈述:
即任意时刻 t 任意 cohort C 至多一个 owner。
关键保证步骤:
- Phase 1 Freeze:旧 owner 收到 freeze cmd 后立刻拒绝新 acquire
- Phase 4 Publish:新 owner 发布前广播 OwnerMap delta,新事务才路由到新 owner
违反场景:如果 Phase 4 在 Phase 1 之前完成(顺序错乱),就有”两个 owner 同时接受新事务”的窗口——双权威。
🧠 保证手段:TransferController 必须先 freeze 再 publish,不能并行。
5.3 I2:Epoch Monotonicity
形式化陈述:
关键保证步骤:
- OwnerMapPublisher 使用 atomic CAS 推进 epoch
- 客户端收到 OwnerMap 后只接受
new.epoch > local.epoch的更新
违反场景:
- 网络乱序 → 老广播包后到 → 客户端误以为是新版本
- 防御方法:携带 epoch 字段,version-check before apply
5.4 I3:Migration Drain
形式化陈述:
即迁移完成前所有该 cohort 上的事务必须已经收尾。
关键保证步骤:
- Phase 2 Drain 显式等待 in-flight tx 完成
- 超时则强制 abort
违反场景:
- 老 owner 在 Drain 期间还接受了新事务(Phase 1 Freeze 没生效)
- 新 owner 接管后老事务的 release 落到老 owner → 死锁
5.5 I4:Data Commit Invariance
形式化陈述:
一个事务一旦开始 commit phase(取得所有锁),它的数据写路径只依赖于 (record_addr, version, write_buffer),与锁所有权位置无关。
关键保证步骤:
- 数据写仍然走 RDMA WRITE → MN(无论锁在哪里)
- release 操作根据”取锁时记录的 owner”路由(不重新查 OwnerMap)
违反场景:
- 取锁时 owner=CN_A,写完数据准备 release 时 owner 已变成 CN_B
- 如果 release 重新查 OwnerMap,会发到错误的 owner → release 丢失 → 死锁
🌟 设计原则:事务一旦取锁成功,“取锁的那个 owner”就是它本次 commit 的归属——OwnerMap 后续怎么变都不影响。这就是 I4 的本质。
5.6 不变式之间的耦合
I2 (epoch monotonicity)
│
├─► 帮助 I1:通过 epoch 比较防止老 owner 误以为自己仍是 owner
│
└─► 帮助 I3:drain 要等到看到新 epoch 的 OwnerMap 才结束
I3 (migration drain)
│
└─► 帮助 I1:drain 把"双 owner 窗口"压到 0
I4 (commit invariance)
│
└─► 解耦 I1:commit 路径不依赖 owner,I1 短暂违反也不会引发数据腐化
🧠 设计师视角:I4 是兜底——即使 I1 在某个边界 case 短暂被破坏(比如时钟偏移),I4 保证数据本身不会损坏。这是工程上”多重防御”的体现。
6. AffinityRouter 路由协议
6.1 lookup 的三种结果
enum class RouteResult {
LOCAL, // owner == self → 本地执行
REMOTE_OWNER, // owner == 其他 CN → OwnerRpc
FALLBACK, // 走 MN atomic
RETRY, // 暂时 TRANSFERRING,short retry
};
RouteResult AffinityRouter::route(const TxnRequest& tx) {
auto kg = group_key(tx.first_key);
auto* cohort = lookup_cohort(kg);
if (!cohort) return FALLBACK;
switch (cohort->state) {
case OWNED:
return cohort->owner == self_cn_id_ ? LOCAL : REMOTE_OWNER;
case TRANSFERRING:
return RETRY;
case FALLBACK:
return FALLBACK;
}
}
6.2 路由策略:先看第一个 key 还是看全部 key
| 策略 | 优点 | 缺点 |
|---|---|---|
| 看第一个 key | 简单、O(1) | 跨 cohort 事务被误路由 |
| 看全部 key | 准确 | 慢、要求所有 key 在同一 cohort |
| 投票 | 折中 | 复杂 |
AURA 默认:看第一个 key,配合 OwnerRpc 做跨 cohort 协调(详见 §7.3)。
6.3 OwnerMap miss 时的退路
什么时候会 miss?
- 新创建的 record 还没被采样(cold path)
- 该 cohort 处于 FALLBACK 状态
- 客户端 OwnerMap 太老
退路:直接走 FallbackLock + RDMA CAS to MN。这是 AURA 的”安全默认”——miss 不会卡住事务。
6.4 epoch lazy refresh
客户端不需要主动拉取最新 OwnerMap。协议层面只在以下情况刷新:
- 收到 STALE_EPOCH 错误码(远端 CN 看到的 epoch 更新)
- 周期性 heartbeat(默认 100ms)
- AffinityRouter 命中率显著下降(触发紧急 refresh)
⭕ 互补:lazy refresh 比 active push 更省带宽,但代价是部分事务可能跑在过期 OwnerMap 上——靠 STALE_EPOCH 错误路径兜底。
7. OwnerLockTable 与 OwnerRpc
7.1 OwnerLockTable 数据结构
class OwnerLockTable {
struct LockEntry {
std::atomic<uint64_t> holder_tx_id; // 0 = unlocked
epoch_t cohort_epoch; // 防止 stale 请求
std::queue<TxnId> waiters; // 等待者队列(OCC 不阻塞,这个用于 fairness 监控)
};
std::unordered_map<key_group_id_t, LockEntry> table_;
std::shared_mutex table_mtx_; // 保护 table_ 本身
public:
// 取锁:CAS 风格,O(1)
AcquireResult acquire(key_group_id_t kg, TxnId tx_id, epoch_t expected_epoch) {
auto& entry = table_[kg];
if (entry.cohort_epoch != expected_epoch) return STALE_EPOCH;
uint64_t expected = 0;
if (entry.holder_tx_id.compare_exchange_strong(expected, tx_id)) {
return GRANTED;
}
return BUSY;
}
void release(key_group_id_t kg, TxnId tx_id) {
table_[kg].holder_tx_id.store(0);
}
};
🌟 关键性质:
- 本地 atomic 操作(
std::atomic而不是 RDMA atomic)→ ~10ns,对比 RDMA CAS 的 ~5µs,500× 快 - 不需要全局锁——每个 entry 独立 CAS
- 死锁不可能(OCC 不等待)
7.2 取锁失败的处理
OCC 风格:取锁失败 → abort + retry。但 AURA 的 OwnerLockTable 在 abort 后还要做一件事:喂 trace:
auto res = lock_table.acquire(kg, tx_id, my_epoch);
if (res == BUSY) {
aura_stats.record_contention(kg); // 喂给控制面
abort_and_retry();
}
contention 信号是 LockCohortGenerator 决定 split 的输入之一——热点本身在喂养系统。
7.3 OwnerRpc 协议
跨 cohort 事务必须经过 OwnerRpc。AURA 不是 sharded transaction——而是把整个事务发到”主 cohort 的 owner”,由它代理执行:
Client (CN_X) ──TxnRequest──► CN_A (owner of cohort_1)
│
├──► OwnerLockTable (本地取 cohort_1 的锁)
│
│ 事务里也访问 cohort_2 (owner = CN_B)
│
├──OwnerRpc(acquire, cohort_2 keys)──► CN_B
│ │
│ 本地取锁
│ │
│ ▼
│ ◄─────────── reply (granted) ────────┘
│
│ RDMA WRITE 数据到 MN
│
│ 释放 cohort_1 锁 (本地)
├──OwnerRpc(release, cohort_2)────────► CN_B
▼
Client ←── commit OK ──
🍎 直觉比喻:跨 cohort 事务像是”跨支行业务”——客户走进主支行,主支行替他打电话给协作支行批准业务。
7.4 OwnerRpc 的 4 种请求类型
| RPC 类型 | 方向 | 用途 |
|---|---|---|
| acquire(keys) | 主 owner → 协作 owner | 跨 cohort 取锁 |
| release(keys) | 主 owner → 协作 owner | 跨 cohort 释放 |
| migrate_handoff(state) | 旧 owner → 新 owner | 第 6 章 Phase 3 |
| epoch_check(epoch) | 任意 → 任意 | 验证 epoch 一致 |
7.5 跨 cohort 比例对吞吐的影响
| 跨 cohort 比例 | 单事务 RPC 数 | 与 baseline 吞吐比 |
|---|---|---|
| 0% | 0 | 1.0× |
| 10% | 0.1 | ~0.95× |
| 30% | 0.3 | ~0.85× |
| 50% | 0.5 | ~0.70× |
| 80% | 0.8 | ~0.40× → 退化到 routing-only |
⭐ 关键观察:跨 cohort 比例 > 50% 时 AURA 退化得比 LOTUS 还差——因为 OwnerRpc 的延迟本身就比 RDMA CAS 高。这是 AURA 必须把 cohort 划准的根本动机。
8. FallbackLock:降级回 MN atomic 的开关
8.1 触发 FALLBACK 的条件
| 条件 | 解释 |
|---|---|
| owner CN 心跳超时(>500ms 无响应) | owner crash |
| owner CN 主动声明降级 | owner 自身过载 |
| cohort 访问频率持续低于阈值 | 冷数据,不值得占 owner |
| epoch 推进失败超过 N 次 | 控制面异常 |
8.2 FALLBACK 期间的事务路径
// 进入 fallback 后,AffinityRouter 返回 FALLBACK
TxnExecutor::run() {
if (route_result == FALLBACK) {
for (auto& key : write_set) {
if (!fallback_lock.cas_mn(key)) abort();
}
// 数据写、release 都走原 CREST 路径
return commit_via_mn();
}
}
class FallbackLock {
public:
bool cas_mn(KeyGroupId kg) {
// 直接发 RDMA CAS 到 MN(与原 CREST 完全相同)
return rdma_cas_8B(addr_of(kg), expected=0, new=tx_id) == 0;
}
};
🌟 重要性质:FALLBACK 路径就是原 CREST 路径——这意味着 AURA 与 CREST 二进制兼容,可以混合部署。
8.3 FALLBACK 退出条件
| 退出条件 | 触发动作 |
|---|---|
| 工作负载升温(access_count 超过阈值) | OwnershipPlanner 选新 owner,进入 TRANSFERRING |
集群人为干预(/admin/promote_cohort) | 强制选 owner |
8.4 FALLBACK 与渐进部署
⭕ 互补:渐进部署期间,集群里同时存在:
- AURA-aware CN:能跑 OWNED / TRANSFERRING / FALLBACK 全套
- legacy CN(未升级):只能跑 FALLBACK 路径(认 RDMA CAS)
AURA 把所有未升级 CN 上的事务自动当 FALLBACK 处理——legacy CN 完全无感。这是 AURA 设计上能渐进发布的关键。
✅ 自我检验清单
- 12 模块:能默写 12 个模块及其层级归属与频率
- 三层解耦:能解释为什么数据面 / 控制面 / 监测面必须解耦
- cohort 数据结构:能写 C++ struct 草案并解释 kg2cohort 反向索引的必要性
- 非相邻 key:能用 TPC-C NewOrder 例子说明为什么 cohort 不能要求 contiguous
- 三层 epoch:能区分 map_epoch / cohort_epoch / txn_epoch
- OwnerMap publish:能写 atomic CAS 推进 epoch 的伪代码
- 状态机:能画出 3 状态 + 6 条 transition + 各自触发者
- TRANSFERRING 时长:能列出 4 个 phase 的 µs 级估算
- 4 不变式:能复述 I1–I4 的形式化陈述 + 各自违反后的 bug
- I4 的兜底意义:能解释为什么 I1 短暂违反时 I4 仍能保数据安全
- AffinityRouter:能写 lookup 的 4 种结果伪代码
- stale epoch:能解释 lazy refresh 比 active push 的 trade-off
- OwnerLockTable:能解释为什么本地 atomic 比 RDMA CAS 快 500×
- OwnerRpc 4 类型:能列出 4 种 RPC 请求类型
- 跨 cohort 退化:能描述跨 cohort 比例 > 50% 时 AURA 的行为
- FALLBACK 兼容性:能解释为什么 FALLBACK 路径 = 原 CREST 路径
📚 参考资料
概念入门
- AURA 论文 §3 Design Overview:本仓库
paper_lock_ownership_cn/sections/3_design_overview.tex - AURA 论文 §3b Component Workflows:本仓库
paper_lock_ownership_cn/sections/3b_component_workflows.tex
关键论文
- FaRM (Dragojević et al., NSDI’14) —— 最早的”epoch + RDMA OCC”模式
- Spanner (Corbett et al., OSDI’12) —— 全局 epoch / TrueTime 的设计参考
- Calvin (Thomson et al., SIGMOD’12) —— deterministic transaction ordering 的另一种思路
- LOTUS (Liu et al., arXiv’25) —— static partition 对照
行业讨论
- 分布式快照综述:epoch-based snapshot 模型在 RAMCloud / FaRM / Spanner 的对比
框架文档
- CREST 仓库
src/db/PoolHashIndex.h:作为 cohort 实现参考的 hash 索引模板 - 本仓库
caesar/项目:12 模块结构在 Caesar 项目里的早期形态