Silo:High Performance OLTP(论文笔记)
发布于:
论文:Silo: Exploiting Message Passing and Shared Memory for OLTP(Tu et al., SOSP 2013)
Silo 常被用来理解“现代高性能 OLTP 引擎”的关键路径:在多核/NUMA 时代,真正的瓶颈往往不是算法,而是争用(contention)与缓存一致性开销(cache coherence)。
Silo 的主线是:
- 用 OCC(Optimistic Concurrency Control) 把冲突处理集中在提交阶段
- 让读路径无锁/低开销
- 提交阶段用版本号/锁位进行验证与安装(install)
本文重点讲:Silo 的提交路径、版本/锁的组织、epoch 的作用、以及为什么它能跑得快。
1. 问题背景:多核时代的 OLTP 痛点
1.1 传统锁(2PL)在高并发下的成本
- 锁表/锁队列本身会成为热点
- 临界区扩大导致并发度下降
- cache line ping-pong(锁变量在不同核间来回)
flowchart TD
A[High concurrency] --> B[Lock contention]
A --> C[Cache coherence traffic]
A --> D[NUMA remote access]
B --> E[Throughput drops]
C --> E
D --> E
1.2 Silo 的策略
- 读路径尽量不加锁
- 提交时再验证:如果冲突就 abort 重试
这就是 OCC 的典型思路:把大多数“不会冲突”的事务跑快。
2. Silo 的数据组织:record + version/lock
Silo 的每条记录通常包含:
- value
- 一个
TID(transaction id / version word),同时编码:- 版本号
- lock bit(写锁)
classDiagram
class Record {
+ Key key
+ Value value
+ uint64 tid_word
}
class TIDWord {
+ uint64 version
+ bool locked
+ bool latest
}
Record --> TIDWord : encodes
3. OCC 三阶段:execute / validate / commit
3.1 Execute(执行阶段)
- 读:直接读记录,拿到 value + tid
- 写:不直接写共享数据,先写到 write set(私有缓冲)
3.2 Validate(验证阶段)
验证要保证:事务读到的版本在提交时仍然有效。
典型做法:
- 对 write set 的 key 按顺序加锁(避免死锁)
- 对 read set 检查版本是否变化(tid 是否仍匹配,且没有被锁)
3.3 Commit(提交阶段)
- 安装 write set:写入新 value
- 更新 tid(版本号递增,清掉 lock bit)
sequenceDiagram
participant Tx as Transaction
participant DB as DB/Records
Note over Tx: Execute
Tx->>DB: read(key) => (value, tid)
Tx->>Tx: add to read_set
Tx->>Tx: buffer writes in write_set
Note over Tx: Validate
Tx->>Tx: lock write_set keys (ordered)
Tx->>DB: check read_set tids unchanged
alt validation ok
Note over Tx: Commit
Tx->>DB: install writes
Tx->>DB: bump tid + unlock
Tx-->>Tx: success
else validation failed
Tx->>Tx: abort + unlock
end
4. Epoch:让 GC/可见性更容易
Silo 引入 epoch(粗粒度时间片)来做一些“全局推进”的事情,典型用途:
- 安全回收旧版本(如果有多版本/日志)
- 做 group commit / 日志刷盘节奏控制
可以将 epoch 当成一个全局“阶段号”:
- 事务开始时读取当前 epoch
- 提交时把自己归属到某个 epoch
- 系统在 epoch 推进后做清理/刷盘
(不同实现细节会有所差异,但 epoch 的价值是:减少细粒度同步)
5. 为什么 Silo 快:热点与 cache line
5.1 读路径无锁
读路径不走锁表、不改共享状态,缓存友好。
5.2 写冲突集中在提交阶段
- 大多数事务只读或冲突概率低 → 走快路径
- 冲突事务 abort 重试,把复杂性交给概率
5.3 ordered locking 降低死锁与抖动
对 write set 排序再加锁:
- 避免死锁
- 降低锁竞争的随机性
6. 需要如何用 Silo 的视角看 OLTP
- OLTP 的性能瓶颈很常见来自“共享元数据”(锁、队列、计数器),而不是表本身。
- OCC 是一种“把一致性成本后置”的策略:吞吐很好,但在高冲突场景 abort 会变多。
- NUMA/缓存一致性是现代 OLTP 的一等公民:数据结构布局与访问模式决定上限。
7. 读完后的 takeaways
- Silo 的主线是:读无锁 + 提交验证。
- 真正的关键路径在 validate/commit:锁 write set + 校验 read set + 安装写入。
- 想把 OLTP 做快,必须把“争用”当第一问题来设计。
8. 冲突与 abort:OCC 的代价是“冲突时重来”
OCC 的快路径很快,但当热点很强时,abort 会成为主要开销。
8.1 热点下会发生什么
- 多个事务读写同一批 key
- 提交阶段争抢 write-set 锁
- read-set 验证失败 → abort 重试
flowchart TD
H[Hot keys] --> C[High contention]
C --> A[More aborts]
A --> R[Retries]
R --> L[Latency spikes]
R --> T[Throughput drops]
8.2 两个实用结论
- OCC 更适合“冲突不高”的 OLTP 工作负载
- 如果业务天然热点(比如全局计数器),需要在应用/数据模型上拆热点,否则任何并发控制都难救
9. validate/commit 的更细要点:为什么要 ordered locking
Silo 通常对 write-set 按 key 排序后加锁:
- 避免死锁
- 减少随机抖动
同时验证 read-set:
- 版本号(tid)是否仍一致
- 期间是否被写锁锁住
9.1 一个更工程化的伪代码
execute:
read_set += (key, tid_seen)
write_set += (key, new_value)
validate:
lock write_set keys in key order
for (key, tid_seen) in read_set:
if current_tid(key) != tid_seen or locked(current_tid(key)):
abort
commit:
for (key, new_value) in write_set:
write value
bump tid + unlock
10. 与 2PL / MVCC 的对比(用于校准取舍)
10.1 2PL(两阶段锁)
- 冲突时:等待(blocking)
- 优点:abort 少
- 缺点:锁争用、调度开销、cache line 抖动
10.2 MVCC
- 读通常无锁(读旧版本)
- 写需要维护版本链与回收(GC)
- 优点:读写并发更好
- 缺点:实现复杂、GC/版本管理成本
10.3 OCC(Silo)
- 读快
- 冲突在提交阶段爆发,abort 重试
工程选择:取决于 workload 的冲突特征与 SLA。
11. 读完后的补充 takeaways
- Silo 的核心不是“OCC 这个概念”,而是把关键路径做成对 cache/NUMA 更友好的实现。
- 想用好 OCC,必须关注热点与 abort 行为:冲突会把所有好处吃掉。
- 与 2PL/MVCC 的对比能帮助你判断:你的业务到底更适合等待、版本、还是重试。