跳转到内容

Silo — 多核内存数据库里的高速短事务

待复核

Silo 是 2013 年 SOSP 上的一篇多核内存数据库论文:在一台机器的多个 CPU 核上,把短事务做得又快又可扩展。日常类比:商场从「一个总窗口发号」改成「每个收银员自己结账」,结账前再对一下货架有没有被别人改过;班次(epoch)到点才统一交班入账。

它用的是 OCC(乐观并发控制,Optimistic Concurrency Control):先干活,提交前再验票。类比:先把购物车装满,出门前扫一遍有没有冲突,有冲突就整单作废重来。

核心不是堆更花哨的锁,而是削掉共享热点:读过的记录尽量不写共享内存,事务号也不靠一个全局柜台发放。

传统多核 OLTP 常见两个坑:全局锁/共享元数据变成热点;事务一长,冲突回滚把吞吐拉崩。Silo 的回答是:把事务做短、做窄、提交窗口做小。

不理解 Silo,后面这些事很难讲清:

  • 为什么很多内存 OLTP 强调短事务——事务越长,冲突窗口越大,回滚越贵。
  • 为什么「只读路径尽量不碰共享写」能救命——多核上一次缓存行争用会被放大到所有核。
  • 为什么 epoch(按时间片/班次切段)能同时服务提交、日志恢复和垃圾回收。
  • 为什么现代多核数据库常避开集中式锁管理器 / 集中式事务号分配。

它也示范了内核工程的取舍:理论上有很多「更优」并发算法,上线更需要「够快、可复现、争用点可控」。

  1. OCC:先执行,提交时验证
    事务在线程本地记下读集(读过哪些货架、当时版本号)和写集(打算改什么)。提交时检查读集是否仍有效,再一次性安装写集;失败就整单回滚。类比:出门验票,票过期就重买。

  2. 只读记录尽量不写共享内存
    Silo 的关键贡献之一:对只读过的记录,提交协议避免共享内存写(不必为读加锁)。类比:只逛不买的顾客,不改货架标签,也就不跟别人抢改标签的笔。

  3. Epoch 班次:提交、恢复与清理的时间边界
    全局 epoch 周期性前进;事务挂在某个班次上,班次边界成为串行化与 group commit(批量落日志)的自然切点,也方便回收已无人引用的旧版本。类比:每 40ms 交一次班,上一班账本封存后才能扔草稿。

  4. 去掉集中式 TID 柜台
    事务标识(TID)与 epoch、本地计数结合生成,避免所有核挤向同一个「发号机」。类比:各收银台自己打小票号,班次号保证全局能排先后。

  5. 争用局部化,而不是「加更多锁」
    多核上单点共享元数据往往比算法复杂度更危险。Silo 把竞争从全局锁面,挪到短提交窗口和线程本地结构上。

案例 1:库存扣减的 OCC 迷你流程

Section titled “案例 1:库存扣减的 OCC 迷你流程”
begin_txn()
v, stock = read(item) # 记入读集:(item, v)
if stock < 1: abort()
write(item, stock - 1) # 记入写集
write(order, {...})
commit:
lock write-set keys
validate read-set versions # 版本变了 → abort
install writes + assign TID
unlock; end

逐部分解释:读库存时只记下版本;真正争用发生在很短的提交窗口。秒杀若把「算优惠券 + 发短信」塞进同一事务,读集窗口被拉长,Silo 优势会消失。

# 只读浏览:验证通过后无需改共享货架
read(catalog); validate; commit_read_only()
# 下单:短写集,提交时才碰共享写
read(stock); write(stock); write(order); validate; install()

逐部分解释:读多写少时,大多数请求像「只逛不买」,不写共享内存,吞吐更稳。这就是「结账通道」和「逛货架通道」分离的工程含义。

every ~40ms: E = E + 1 # 推进全局 epoch
on_commit: tid.epoch = E # 事务挂到当前班次
durable_when: epoch E-1 sealed # 上一班日志落稳才对外保证

逐部分解释:epoch 推进太慢,提交「对外可恢复确认」的延迟会升高;推进太勤,又变成新的共享热点。论文实现大约数十毫秒一档,在延迟与争用之间折中。

  1. 把长事务当短事务:事务里塞日志、权限、RPC,冲突窗口被拉爆,OCC 回滚风暴。
  2. epoch 推进过慢:班次迟迟不封账,提交可见/可恢复延迟被抬高,SLO 抖动。
  3. 把验证外包给应用层:各服务自己比版本,提交语义不一致,极端并发下出现诡异丢更新。
  4. 迷信「越乐观越快」:冲突率极高时反复 abort 比保守锁更慢;要按读写比和核数调,而不是拍脑袋。

一句话自检:如果你说不清「读集有多大、提交窗口有多短」,还没到能吃 Silo 红利的阶段。

适用

  • 单机多核、数据主要在内存的短事务 OLTP(论文 TPC-C 约 32 核、近 70 万 TPS、近线性扩展)。
  • 读多写少、能把业务切成小事务的库存/订单类路径。
  • 团队能接受内核级调优,并明确事务边界。

不适用

  • 跨服务分布式长事务、几秒级交互式事务主干。
  • 热点键冲突极高、大对象频繁原地重写的负载。
  • 需要复杂 SQL/存储过程且无法缩短临界区的系统。
  • 2010 前后,多核普及,集中式锁与集中式 TID 成为内存库扩展瓶颈。
  • 2013,MIT/Harvard 团队在 SOSP 发表 Silo,用 OCC + epoch 证明「可串行化也能少共享写」。
  • 同期 Hekaton 等也在推 OCC/内存 OLTP;Silo 以消除集中争用点著称。
  • 此后多核教学与系统常把 Silo 当作「短事务 + 本地化竞争 + epoch 边界」的样板。
  • 后来不少研究直接改 Silo 代码库做对比实验,说明它的工程骨架足够干净、可复用。
  1. 多核性能常死在共享写与集中发号,不只是「算法聪不聪明」。
  2. OCC 值钱的是短验证窗口读集不乱写共享内存,不是「先干了再说」的口号。
  3. Epoch 把提交、日志恢复和回收绑在同一时间边界上,是工程杠杆。
  4. 事务边界越清楚,高压下越不容易被回滚率打穿。
  5. 看吞吐时要连着看回滚率:平均延迟好看,也可能藏着冲突风暴。
  • occ —— Silo 提交协议的基础模型。
  • multiversion-concurrency-control —— 版本比较与快照读的相关支点。
  • epoch-memory-management —— epoch 边界如何服务回收与生命周期。
  • in-memory-database —— 内存 OLTP 的场景背景。
  • mvcc —— 更广义的多版本并发控制视角。
  • lock-free-queue —— 对照「少共享写」的并发数据结构思路。
  • distributed-tx —— 对照:Silo 是单机多核,不是跨机分布式事务方案。
  • mvcc —— MVCC — 让读写互不挡路的版本账本