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

第7章:闭环控制 —— AdaptX 折叠进 AURA

AURA 控制面的两条反馈环(owner-side back-pressure / NIC counter ingestion)来自 AdaptX,5ms 窗口选定的频谱分析、抖动 vs 反应速度的工程权衡

AURA AdaptX 闭环控制 NIC counter AIMD back-pressure 5ms

AURA 的决策不是一次性做完——它需要持续观察工作负载、持续调整 cohort 与 owner 位置。这个闭环来自原本独立的 AdaptX 工作的两条 Loop——Loop A(owner-side back-pressure)和 Loop B(NIC counter ingestion)。本章把这两条 Loop 折进 AURA 的控制面,解释 5ms 窗口为什么不能更短也不能更长,并展开 AIMD 反压算法的实现细节。读完你应该能解释”如果把窗口调到 1ms 会怎样”,并能用频谱分析的语言描述抖动来源。

📑 目录


1. 控制面闭环鸟瞰

1.1 AdaptX 三条 Loop 的回顾

AdaptX 是 AURA 的前身设计,定义了三条独立的反馈 Loop:

Loop功能频率当前在 AURA 里的位置
Loop Aowner-side back-pressure(防 owner CN 过载)5ms折进 OwnerLockTable + AffinityRouter
Loop BNIC IOPS counter 摄取(防 MN atomic 触顶)5ms折进 AccessGraphProfiler + OwnershipPlanner
Loop C锁置换(hypothetical)5ms直接成为 AURA 主体(cohort 划分 + 迁移)

🌟 关键事实AURA = AdaptX Loop C 主干 + Loop A/B 折进控制面。原本独立的 AdaptX 工作被吸收成为 AURA 的支撑机制。

1.2 完整闭环图

   ┌────────── 数据面 (per-txn, µs 级) ──────────┐
   │                                              │
   │  TxnExecutor → OwnerLockTable                │
   │    │                  │                      │
   │    ▼                  ▼                      │
   │  AccessGraph        Stats                    │
   │  (trace)         (latency, qps)              │
   │                                              │
   └──────────┬──────────────┬───────────────────┘
              │              │
              │ 5ms 一次     │
              ▼              ▼
   ┌────────────────────── 控制面 (5ms tick) ──────────┐
   │                                                   │
   │  AccessGraphProfiler ◄── trace samples           │
   │      │                                            │
   │      ▼                                            │
   │  Cohort merge/split                               │
   │      │                                            │
   │      ▼                                            │
   │  OwnershipPlanner ◄── { Loop A signals,          │
   │      │                  Loop B signals }          │
   │      ▼                                            │
   │  TransferController                               │
   │      │                                            │
   │      ▼                                            │
   │  OwnerMapPublisher → 广播 epoch                   │
   │                                                   │
   └───────────────────────────────────────────────────┘


   ┌─────────────────┴─────────────────────────────┐
   │            监测面(高频采样)                  │
   │                                                │
   │  Loop A: owner-side counters                   │
   │     - in-flight tx queue depth                 │
   │     - lock_table acquire latency P99           │
   │     - CPU pct                                  │
   │                                                │
   │  Loop B: NIC counters (MN side, RDMA)          │
   │     - port_xmit_wait                           │
   │     - atomic_queue_depth (vendor specific)     │
   │     - abort_rate (derived)                     │
   └────────────────────────────────────────────────┘

1.3 三层信号的融合策略

   每 5ms 一次 OwnershipPlanner.solve() 时输入三类信号:
   ──────────────────────────────────────────────────
   
   1. 访问图(来自 AccessGraphProfiler)
      → 决定 cohort 边界
   
   2. owner-side 信号(来自 Loop A)
      → 决定 owner CN 是否需要降级 / cohort 是否需要 split
   
   3. NIC counter 信号(来自 Loop B)
      → 决定是否需要更激进迁移 / 进入 FALLBACK

🧠 关键洞察单信号容易失真,多信号 voting 才稳健——这是 AURA 闭环的核心设计原则。


2. Loop A:owner-side back-pressure

2.1 为什么 owner CN 会成为新瓶颈

把锁从 MN 提到 CN,就是把 atomic 压力从 MN RNIC 转移到 owner CN 的 OwnerLockTable + RPC 队列。owner CN 也有自己的容量上限:

维度owner CN 上限
OwnerLockTable QPS~10M ops/s(CPU 决定)
OwnerRpc QPS~5M ops/s
网卡接收 QPS~150 Mpps(远超 atomic)
CPU 利用率80% threshold(保留 20% 应急)

典型触发:单个 cohort 太热 + cohort 内有 hot key → owner CN CPU 100% → 后续请求 queue 起来。

2.2 Loop A 的反馈机制

class LoopABackPressure {
    DecayingCounter cpu_pct_;       // 5ms 半衰期的 CPU 利用率
    DecayingCounter queue_depth_;
    DecayingCounter lock_p99_;
    
    SignalLevel current_signal() {
        if (cpu_pct_.read() > 0.8) return OVERLOAD_HIGH;
        if (queue_depth_.read() > 1000) return OVERLOAD_HIGH;
        if (lock_p99_.read() > 50_us) return OVERLOAD_MEDIUM;
        return NORMAL;
    }
    
    void on_window_end() {
        if (current_signal() == OVERLOAD_HIGH) {
            // 通知 OwnershipPlanner:该 owner 过载
            planner_.flag_owner_overload(self_cn_id_);
            // AIMD 减速:少接新 cohort 任务
            admission_budget_.multiplicative_decrease();
        } else {
            admission_budget_.additive_increase();
        }
    }
};

2.3 触发的具体动作

信号动作
OVERLOAD_HIGH 持续 1 窗口OwnershipPlanner 优先选别的 CN 接收新 cohort
OVERLOAD_HIGH 持续 3 窗口主动 split 该 owner 上最热的 cohort
OVERLOAD_HIGH 持续 5 窗口强制把部分 cohort 迁出
NORMAL 持续 N 窗口缓慢恢复 admission budget

🌟 关键作用Loop A 防止 AURA 把 atomic 瓶颈”从 MN 搬到某个 CN”——保证 cohort 在 owner 之间均衡分布。

2.4 Loop A 与 cohort split 的协同

owner CN 过载有两种解法:

解法何时选
把热 cohort 整个迁走该 owner 上的总负载偏高、cohort 之间没有内聚力
split 热 cohort单个 cohort 太大、split 后能分散到其他 CN

LockCohortGenerator 与 Loop A 协作

void OwnershipPlanner::handle_overload(cn_id_t cn) {
    auto cohorts_on_cn = get_cohorts(cn);
    auto hottest = max(cohorts_on_cn, key=load);
    
    // 决策:split or migrate?
    if (hottest.size > 100 and hottest.internal_cut_score > 0.5) {
        // 内部能切干净 → split
        cohort_generator_.force_split(hottest);
    } else {
        // 整体迁走
        auto target = pick_least_loaded_cn();
        transfer_controller_.elect(hottest, target);
    }
}

2.5 Loop A 的信号噪声

CPU pct 和 queue depth 都有短时 burst(GC、page fault)。Loop A 用衰减计数 + 多窗口确认避免误触发:

噪声源抑制
GC pause(10–50ms)衰减半衰期 5ms 自动滤掉
短时 burst必须连续 N 窗口才升级 signal
测量抖动多信号 voting(CPU + queue + p99)

3. Loop B:NIC counter 摄取

3.1 NIC counter 是什么

ConnectX 网卡暴露一组硬件计数器(/sys/class/infiniband/<dev>/ports/<port>/counters/):

计数器含义用途
port_xmit_data发出字节数带宽监测
port_rcv_data接收字节数带宽监测
port_xmit_wait发出但未 ack 的累积时间atomic 排队压力
port_rcv_wait接收 backpressure接收过载
port_xmit_packets发出包数IOPS

关键计数port_xmit_waitatomic 排队压力的代理指标——atomic 在 NIC 内部排队时,发出的请求数减但 wait 累积。

3.2 perfquery 与 sysfs 两种读法

# 方式 1: perfquery(OFED 自带)
perfquery -x mlx5_2 1
# 输出:
#   PortXmitData: 12345678
#   PortRcvData: 9876543
#   PortXmitWait: 56789
#   ...

# 方式 2: sysfs(rdma-core 也支持)
cat /sys/class/infiniband/mlx5_2/ports/1/counters/port_xmit_wait
# 输出:56789
方式优点缺点
perfquery一次拿全计数子进程开销 ~1ms
sysfs单次读 ~50µs多个计数要读多次

AURA 默认 sysfs——按 5ms 窗口频率,子进程开销不可接受。

3.3 5ms 周期采样

class NicCounterSampler {
    std::string base_path_;
    uint64_t prev_xmit_wait_ = 0;
    uint64_t prev_xmit_pkts_ = 0;
    
    void tick(uint64_t now_us) {
        auto cur_xmit_wait = read_sysfs(base_path_ + "port_xmit_wait");
        auto cur_xmit_pkts = read_sysfs(base_path_ + "port_xmit_packets");
        
        // 速率
        auto wait_delta = cur_xmit_wait - prev_xmit_wait_;
        auto pkts_delta = cur_xmit_pkts - prev_xmit_pkts_;
        
        // 喂给 Loop B 信号
        loop_b_.observe(wait_delta, pkts_delta);
        
        prev_xmit_wait_ = cur_xmit_wait;
        prev_xmit_pkts_ = cur_xmit_pkts;
    }
};

3.4 三个关键派生量

派生量公式用途
wait_per_pktwait_delta / pkts_delta单包平均排队时间
atomic_qpd_proxywait_delta / 窗口长度atomic queue depth 代理
abort_ratefrom AuraStats观察事务级 abort
auto wait_per_pkt = pkts_delta == 0 ? 0 : wait_delta / pkts_delta;
auto atomic_qpd  = wait_delta * 1000.0 / window_us;

if (atomic_qpd > THRESHOLD_ATOMIC_QPD) {
    // 触发紧急动作
    planner_.flag_mn_atomic_pressure();
}

3.5 NIC counter 的局限:固件依赖

⚠️ 重要:不同 ConnectX 固件版本对 counter 的支持不同:

版本port_xmit_waitatomic_queue_depth备注
ConnectX-3 mlx4支持但不准不支持只能用 abort rate 间接
ConnectX-5 firmware ≥ 16.x支持部分支持(需 vendor counter)可靠
ConnectX-6 Dx firmware ≥ 22.x支持支持推荐

实战策略:单点 NIC counter 不可靠 → §6 多信号融合。


4. AIMD 反压算法实现

4.1 AIMD 是什么

Additive Increase / Multiplicative Decrease

  • 没有过载信号 → 配额(budget)线性增加
  • 收到过载信号 → 配额折半

经典源自 TCP CC,AURA 借鉴用作 owner CN 的 acquire 接受配额

4.2 AIMD 控制器实现

class AIMDController {
    static constexpr uint64_t INITIAL_BUDGET = 10000;
    static constexpr uint64_t MAX_BUDGET     = 1'000'000;
    static constexpr uint64_t STEP_AI        = 1000;
    
    std::atomic<uint64_t> budget_{INITIAL_BUDGET};
    
public:
    bool can_admit() {
        auto b = budget_.load();
        if (b == 0) return false;
        budget_.fetch_sub(1);  // 消耗 1 配额
        return true;
    }
    
    void on_window_end(SignalLevel s) {
        if (s == OVERLOAD_HIGH) {
            // MD:折半
            budget_.store(budget_.load() / 2);
        } else if (s == NORMAL) {
            // AI:加 STEP_AI
            auto b = std::min<uint64_t>(budget_.load() + STEP_AI, MAX_BUDGET);
            budget_.store(b);
        }
    }
};

4.3 AIMD 与 PID 的对比

算法优点缺点
AIMD简单、对噪声鲁棒、有理论保证(公平性 + 收敛性)反应慢(增长是线性的)
PID反应快、可调优调参困难
Slow Start + AIMD(TCP)启动快、稳定后 AIMD实现复杂

🍎 直觉比喻:AIMD 像”小心翼翼地试水”——慢慢加,遇到问题就大步退。控制理论里的”鲁棒性优先”哲学

4.4 AIMD 在 AURA 里的具体应用

应用场景budget 单位
owner CN admission每窗口允许接受的 acquire 数
跨 cohort RPC每窗口允许发出的 OwnerRpc 数
migration 频率每秒允许的 migration 数

多个 AIMD 控制器互不干涉——每个解决一个独立 bottleneck。

4.5 AIMD 与全局信号的协同

单 CN 的 AIMD 是局部决策。全局视图由 OwnershipPlanner 持有:

   Local AIMD (each owner CN)
       ↓ throttles per-CN admission

   Global Planner (sees all CN budgets)
       ↓ if many CNs throttled → cohort 迁移更激进
       ↓ if all CNs healthy → 可以新 cohort 上线

🧠 关键洞察AIMD 是 micro 调节,Planner 是 macro 决策。两者协同,不是替代。


5. 5ms 窗口的频谱分析

5.1 工作负载漂移频率分布(实测)

实测 TPC-C / SmallBank / TATP 的访问 pattern 漂移频率:

   Power Spectral Density (log scale)

       │  ★ 主能量带:50–100 Hz(漂移基频)
       │  ▒
       │  ▒▒▒
       │  ▒▒▒▒
       │  ▒▒▒▒▒
       │  ▒▒▒▒▒▒
       │       ▒▒▒▒                  ← 干扰带 (>1kHz, NIC counter 噪声)
       │           ▒▒▒    ▒▒▒▒▒▒▒▒
       │              ▒▒▒▒
       │                 ▒
       └────┴────┴────┴────┴────┴────► 频率 (Hz)
       10   100  1000 10k  100k       

          5ms 决策窗口对应这里 (200 Hz Nyquist)

🌟 核心结论5ms 窗口对应 100 Hz Nyquist 频率上限——刚好覆盖工作负载主要漂移频段(50–100 Hz)。

5.2 为什么不是 1ms

1ms 窗口问题
Nyquist 上限 500 Hz远超漂移频段,过度采样
单窗口 sample 数 ~200统计噪声大(√N 噪声占比 ~7%)
决策开销 ~500µs / 1ms50% 时间在做控制
抖动放大每 1ms 决策一次,OwnerMap 抖动 5×

5.3 为什么不是 50ms 或 100ms

50–100ms 窗口问题
Nyquist 上限 5–10 Hz错过主漂移带(50–100 Hz)
反应迟缓漂移已发生 50ms 才检测到
LOTUS 用 100ms 反应式已知的失效场景

5.4 频谱分析的工程价值

互补:5ms 不是数学上的”最优”,是工程上的”够用”:

  • 比 1ms 大 5× → 噪声小、决策开销低
  • 比 100ms 小 20× → 反应快、跟得上漂移

🧠 关键洞察控制系统的窗口长度选择往往是”够用就好”,不是”越短越好”。频谱分析帮你确定”够用”的下界。

5.5 自适应窗口大小(未来工作)

理论上窗口长度应该自适应:

  if 当前漂移频率 < 50 Hz:
      窗口 = 10ms(更稳)
  elif 漂移频率 > 200 Hz:
      窗口 = 2ms(更快)
  else:
      窗口 = 5ms(默认)

AURA 当前不做(实现复杂、收益有限)——作为开放问题留在第 3 章 §7


6. 多信号融合:避免单点 NIC counter 失真

6.1 单信号不可靠的实例

实测中遇到的真实事故:

事故现象
ConnectX-3 firmware bugport_xmit_wait 不递增(永远 0)
测量周期对齐多 CN 的 sysfs 读发生在 NIC counter 刷新边界
时钟漂移CN 间时钟差异 → wait 速率计算错
内存 page outsysfs 读取突发延迟 100ms

6.2 三信号 voting

AURA 的策略:三个信号至少 2/3 同向才触发动作

struct AtomicPressureSignals {
    bool nic_xmit_wait_high;       // Loop B 信号
    bool abort_rate_high;          // 数据面信号
    bool acquire_p99_high;         // Loop A 信号
};

bool voted_atomic_pressure(const AtomicPressureSignals& s) {
    int votes = (int)s.nic_xmit_wait_high
              + (int)s.abort_rate_high
              + (int)s.acquire_p99_high;
    return votes >= 2;
}

🌟 关键性质任意一个信号失真都不会单独触发动作——必须至少 2 个独立来源同向。

6.3 信号源的独立性

三个信号必须真正独立(共因故障不算独立):

信号来源失败 mode
nic_xmit_wait_highsysfs / NIC firmwarefirmware bug
abort_rate_highOCC commit 路径计数TxnExecutor crash
acquire_p99_highOwnerLockTable timing时钟问题

不同来源 → 不同 failure mode,voting 才有意义。

6.4 自适应阈值

固定阈值(如 port_xmit_wait_rate > 1M/s)容易被工作负载变化打脸。AURA 用自适应阈值

class AdaptiveThreshold {
    DecayingCounter mean_;
    DecayingCounter stddev_;
    
    bool is_anomaly(double sample) {
        // 3-sigma rule
        return sample > mean_.read() + 3 * stddev_.read();
    }
    
    void observe(double sample) {
        mean_.add(sample);
        stddev_.add(std::pow(sample - mean_.read(), 2));
    }
};

🍎 直觉比喻:阈值不是”超过 1M/s 算异常”,而是”超过通常水平 3σ 算异常”——让阈值跟着工作负载走

6.5 信号融合的代码实现

class SignalFusion {
    AdaptiveThreshold nic_th_;
    AdaptiveThreshold abort_th_;
    AdaptiveThreshold p99_th_;
    
    AtomicPressureSignals current() {
        return {
            .nic_xmit_wait_high  = nic_th_.is_anomaly(loop_b_.wait_rate()),
            .abort_rate_high     = abort_th_.is_anomaly(stats_.abort_rate()),
            .acquire_p99_high    = p99_th_.is_anomaly(stats_.acquire_p99()),
        };
    }
    
    void tick() {
        auto signals = current();
        if (voted_atomic_pressure(signals)) {
            planner_.flag_atomic_pressure();
        }
    }
};

7. 抖动来源与抑制

7.1 抖动主要来源

来源频率表现
GC pause(CN 进程)偶尔(~1Hz)短时 CPU 100%
短时 workload burst高(数 Hz)1ms 内访问突增
NIC counter 周期对齐周期性(取决于固件)多 CN 同时刷新
时钟漂移慢(小时级)衰减计数器误差
OS scheduling jittersysfs 读取延迟变化

7.2 抑制策略

来源抑制方法
GC pause衰减半衰期 5ms 自动滤掉
burst必须 N 窗口连续才升级信号
周期对齐错开 CN 之间的采样相位(每 CN 加 random offset)
时钟漂移用相对时间(now - prev)而不是绝对时间
OS jitter关键路径用 sched_setattr(SCHED_FIFO) 提优先级

7.3 衰减半衰期的选择

半衰期滤掉的频率适合
1ms> 1kHz 噪声太敏感
5ms> 200Hz 噪声AURA 默认
50ms> 20Hz 噪声反应过慢

🌟 结论5ms 半衰期 ≈ 5ms 窗口长度——这是 AURA 选 5ms 的另一个理由。

7.4 抖动检测的兜底机制

如果衰减 + voting 仍然不能稳定(极少数情况),AURA 有最后兜底:

class AntiOscillation {
    std::unordered_map<cohort_id_t, CircularBuffer<bool>> migrate_history_;
    
    bool should_lock_state(cohort_id_t c) {
        auto& hist = migrate_history_[c];
        // 1 秒内 migrate > 3 次 → 锁定 5 秒不动
        return hist.count_recent(1000) >= 3;
    }
};

互补:兜底机制不是 always on——只在监测到反复抖动时启动。日常情况下不影响响应速度。

7.5 抖动的实测案例

实测中遇到的真实抖动:

案例原因解决
OwnerMap 每秒变 100 次TPC-C 50/50 split 工作负载,cohort 在两个 CN 之间反复迁移增大滞回带宽 0.05 → 0.10
单 CN CPU 利用率周期震荡GC pause 与采样周期对齐采样相位加 random offset
Loop B 信号日夜规律数据中心温度 → NIC firmware 自动调频阈值用日夜段的 mean,不用全局 mean

🧠 工程经验真实抖动总是出在你没想过的地方——所以 AURA 设计上层层防御(衰减 + voting + 兜底锁定)。


✅ 自我检验清单

  • 三 Loop:能默写 Loop A / B / C 各自的功能
  • AURA = AdaptX 折叠:能解释 Loop A/B/C 在 AURA 里的位置
  • owner-side 反压:能解释 Loop A 触发时 OwnershipPlanner 的行为
  • Loop A 与 split 协同:能描述何时选 split / 何时选 migrate
  • NIC counter:能列出至少 3 个 ConnectX 计数及其物理含义
  • port_xmit_wait:能解释为什么它是 atomic 排队压力的代理
  • AIMD 算法:能写一个最简化的 AIMD 实现
  • AIMD vs PID:能解释为什么生产系统选 AIMD
  • 5ms 窗口频谱:能用 Nyquist 频率论证为什么不是 1ms 或 100ms
  • 多信号融合:能描述 voting 机制的好处
  • 信号独立性:能列出 3 个独立信号源 + 各自 failure mode
  • 自适应阈值:能写 3-sigma 自适应阈值的代码
  • 抖动抑制:能列出至少 5 种抖动来源 + 抑制手段
  • 衰减半衰期:能解释为什么半衰期 ≈ 窗口长度

📚 参考资料

概念入门

  • TCP Congestion Control (Jacobson et al., 1988) —— AIMD 算法原创
  • 频谱分析教科书 —— Oppenheim & Schafer, Discrete-Time Signal Processing
  • Mellanox NIC counter 文档 —— Mellanox Performance Tuning Guide

关键论文

  • TCP CUBIC / BBR —— AIMD 后续改进的对照
  • AdaptX 设计文档(本仓库 caesar/ 项目下)—— Loop A/B/C 原始定义
  • AURA 论文 §3.5 / §4.2 —— 控制面闭环的详细描述

行业讨论

  • Robust Control vs Optimal Control —— 控制论中的”鲁棒性 vs 最优性”权衡
  • NIC Counter Reliability —— Mellanox community 关于 firmware bug 的讨论

框架文档