跳到主要内容
分离式事务的动态锁所有权

第4章:AURA 骨架 —— cohort、ownership、状态机、不变式

拆解 AURA 的 12 个核心模块、cohort 数据结构、3 状态机(OWNED/TRANSFERRING/FALLBACK)与 4 个不变式(I1–I4),建立看完整论文的脚手架

AURA cohort ownership 状态机 不变式 系统架构

读到这里你应该已经认同”必须做动态锁所有权”的命题。本章不再讨论”为什么”,而是直接把 AURA 这个系统的脚手架立起来:12 个模块各自承担什么职责、数据结构怎么设计、3 个状态怎么转、4 个不变式怎么把一致性钉死。读完你应该能在白板上不查论文画出 AURA 的全套架构图,并能徒手列出每个不变式被违反时会引发什么具体 bug——这是后续 5/6/7 章细节展开的前置。

📑 目录


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, OwnerMaproute to (CN, MN)每事务
TxnExecutor数据TxnRequest执行结果 (commit/abort)每事务
OwnerLockTable数据acquire/release(granted/wait/abort)每锁请求
OwnerRpc数据跨 CN 协调RPC reply跨 cohort 时
FallbackLock数据acquire/releaseRDMA CAS to MNfallback 路径
AccessGraphProfiler控制trace samplesweighted graph每窗口 (5ms)
LockCohortGenerator控制graph deltamerge/split deltas每窗口
OwnershipPlanner控制cohort + loadnew OwnerMap每窗口
TransferController控制new OwnerMap diff4 阶段迁移触发时
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

粒度数量级例子
keyrecord 唯一 ID千万级(wid=1, did=2, cid=3)
key_group逻辑同访问组千~万级”wid=1 的所有相关行”
cohort同步单元百~千级多个 key_group 聚簇

两层映射 = key → key_group → cohortkey → 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 的传播代价

大小估算数字
单 OwnerMapEntry8B + 8B + 2B + 8B = 26B(packed 32B)
1000 cohort 的 OwnerMap32KB
5ms 一次广播到 8 CN8 × 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 的触发者

起始终止触发者条件
OWNEDTRANSFERRINGOwnershipPlannermerge/split/migrate 决策
TRANSFERRINGOWNED (new owner)TransferController4 阶段协议完成
OWNEDFALLBACKTransferControllerowner 心跳超时 / 主动降级
FALLBACKTRANSFERRINGOwnershipPlanner工作负载再次升温
FALLBACKFALLBACK(持续状态,靠 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,是故意保留的安全网

  1. owner CN 故障时,cohort 不会卡住——直接降级到 MN atomic
  2. 冷数据不值得占着 owner——降级到 fallback 释放控制面 CPU
  3. 渐进部署时,未升级的 CN 仍能通过 fallback 访问

🧠 关键洞察有 FALLBACK 这条退路,AURA 才能放心做激进的迁移决策——错了大不了回 fallback,正确率不需要 100%。


5. 4 个不变式:I1–I4

5.1 不变式总览

ID名称一句话违反后果
I1Authority uniqueness任一时刻同一 cohort 的权威只在一处双权威 race + 数据腐化
I2Epoch monotonicityepoch 永不回退老 OwnerMap 误为最新 + 路由错乱
I3Migration drain迁移期间所有 in-flight 事务必须 drain 或 abort老 owner 持锁不释放 + 死锁
I4Data commit invariance数据 commit 路径不依赖锁所有权位置迁移导致 commit 中途失败

5.2 I1:Authority Uniqueness

形式化陈述

t,cohort C:{owner(C,t)}1\forall t, \forall \text{cohort } C: \quad |\{\text{owner}(C, t)\}| \leq 1

即任意时刻 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

形式化陈述

t1<t2:map_epoch(t1)map_epoch(t2)\forall t_1 < t_2: \quad \text{map\_epoch}(t_1) \leq \text{map\_epoch}(t_2)

关键保证步骤

  • OwnerMapPublisher 使用 atomic CAS 推进 epoch
  • 客户端收到 OwnerMap 后只接受 new.epoch > local.epoch 的更新

违反场景

  • 网络乱序 → 老广播包后到 → 客户端误以为是新版本
  • 防御方法:携带 epoch 字段,version-check before apply

5.4 I3:Migration Drain

形式化陈述

cohort C,transition OWNEDOWNED:tx T on C:T committed/aborted before publish\forall \text{cohort } C, \forall \text{transition OWNED} \to \text{OWNED}': \quad \forall \text{tx } T \text{ on } C: \quad T \text{ committed/aborted before publish}

即迁移完成前所有该 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。协议层面只在以下情况刷新

  1. 收到 STALE_EPOCH 错误码(远端 CN 看到的 epoch 更新)
  2. 周期性 heartbeat(默认 100ms)
  3. 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%01.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 项目里的早期形态