土法炼钢 · 系统与基础设施

【etcd】bbolt 后端:mmap、batch 与单写者约束

文章导航

分类入口
distributedkubernetes
标签入口
#etcd#bbolt#mmap#batch#mvcc#backend#v3.5

目录

第 4 篇 把 Revision、keyIndex、generation 钉在 MVCC 逻辑层;bbolt 才是这些 revision 在磁盘上的落点。读者若把 etcd 当成「带 Raft 的通用数据库」,会在 单写者 + batch commit 处撞墙:读可以并发,写路径全局串行,且 bbolt 事务未必随每条 Raft entry 立即 fsync。

本篇回答:v3.5.33 如何把 MVCC 映射到 bbolt bucket、mmap 与 batch 如何 amortize fsync、单写者约束对 apply 与线性一致读意味着什么。 Apply 管道见第 6 篇;quota 触顶见第 7 篇。

本篇在系列中的位置

篇目 核心内容
第 4 篇 · MVCC 数据模型 Revision、keyIndex、generation
第 5 篇 · bbolt 后端 mmap、batch、单写者、bucket 布局
第 6 篇 · Apply 管道 propose → commit → apply
系列目录 关键问题、阅读路径

版本锚定:etcd v3.5.33(源码 tag v3.5.33go.etcd.io/bbolt v1.3.12)。机制对齐 server/mvcc/backend/server/mvcc/buckets/ @ 该 tag。无真实集群则不粘贴伪造 etcdctl 输出或磁盘延迟数字。


一、bbolt 在 etcd 栈中的位置

etcd v3 存储栈可以粗分为三层:

组件 职责
共识 Raft + WAL 日志复制、commit index
MVCC store + treeIndex Revision 分配、内存索引、可见性
持久化 bbolt backend mmap B+ 树、批量写事务、快照

bbolt(go.etcd.io/bbolt,BoltDB 的 etcd 维护分支)是嵌入式 KV:无独立 server 进程,由 etcd 进程内 bolt.Open 打开单个 member/snap/db 文件。其设计约束——同一时刻最多一个写事务、多个只读事务可并发——直接塑造了 etcd 的 apply 串行与 backend batch 策略。

谱系:BoltDB 来自 CoreOS 时代的嵌入式 Go 存储;etcd 在 v3 全面转向 MVCC 后仍选 bbolt 而非 LSM,换取实现简单、范围读友好、单文件运维,代价是写扩展性受单写者封顶(与 TiKV Multi-Raft 的分叉见本系列第 16 篇,不在此展开)。

flowchart TB
  raft["Raft commit"] --> apply["apply → MVCC store"]
  apply --> batch["backend BatchTx"]
  batch --> bbolt["bbolt mmap file"]
  read["Range / Watch 读"] --> concurrent["ConcurrentReadTx"]
  concurrent --> bbolt
  concurrent --> buf["txReadBuffer 写缓冲"]

图:写经 BatchTx 聚合;读走 ConcurrentReadTx,合并 bbolt 与内存缓冲。


二、bucket 布局与 key 编码

server/mvcc/buckets/bucket.go 定义 bbolt 内部分区。与 KV 直接相关的主要 bucket:

Bucket 用途
key MVCC 主数据:以 revision 字节序 为 key,序列化 mvccpb.KeyValue 为 value
meta consistent_index、Raft term 等元数据
lease 租约元数据
alarm NOSPACE / CORRUPT 告警状态
cluster / members 成员与集群配置

逻辑 key(用户可见的字节串)作为 bbolt 主键;第 4 篇treeIndex(内存 B-tree)负责 (userKey → revision) 映射,Range 时先用 treeIndex 查 revision 列表,再按 revToBytes(revision)key bucket 取 value(kvstore_txn.gorangeKeys)。

key bucket 标记为 safeRangeBucket:同一 revision 不会被覆盖,Range 扫描安全。这与 LSM「同 key 多版本」的物理布局不同——etcd 的多版本是多条 revision 记录,靠 compaction 回收旧 revision。


三、mmap 与 InitialMmapSize

backend.go 打开数据库时设置 bolt.Options.InitialMmapSize,Linux 默认 InitialMmapSize = 10 GiB。注释说明意图:预映射区域大于潜在最大库文件时,写者扩容 mmap 时不至于长时间阻塞读者

// server/mvcc/backend/backend.go @ v3.5.33(节选)
var InitialMmapSize = uint64(10 * 1024 * 1024 * 1024)

bopts.InitialMmapSize = bcfg.mmapSize()
db, err := bolt.Open(bcfg.Path, 0600, bopts)

工程含义(非 benchmark):

开放问题:云盘延迟与 mmap 扩容的交互因环境而异;本站无统一实测表,排障时看 etcd_disk_backend_commit_duration_seconds(官方运维文档口径,B 级)而非臆测阈值。


四、batch commit:100ms 与 10000 条

bbolt 每次 Commit() 可能触发 fsync;若每条 Raft apply 单独 commit,写放大不可接受。etcd backend 用 batchTxBuffered 聚合:

// server/mvcc/backend/backend.go @ v3.5.33
var (
    defaultBatchLimit    = 10000
    defaultBatchInterval = 100 * time.Millisecond
)

后台 goroutine run()batchInterval 检查 pending 操作,达到阈值或定时器触发时 Commit()Unlock() 路径上还有关键分支:delete 操作会立即 commit——因为 delete 没有 put 那样的内存写缓冲,若延迟 commit 可能导致后续读从 bbolt 读到陈旧数据,破坏线性一致(注释引用 PR #17119)。

触发条件 行为
定时器(默认 100ms)且 safePending() > 0 批量 commit
pending >= batchLimit(默认 10000) 强制 commit
任意 pending delete 立即 commit

指标 etcd_disk_backend_commit_duration_seconds 反映的是这一层的 fsync 延迟,与 WAL fsync(第 3 篇)分列——排障时勿混为一谈。


五、单写者约束与读写事务

bbolt 保证:一个写事务与多个读事务并发;写事务提交前,新读事务看到旧快照。etcd 在此基础上封装:

写缓冲(batchTxBuffered.buf)是理解「apply 已完成但 bbolt 尚未 fsync」的关键:Put 可在缓冲中立刻被读路径看见;Delete 必须尽快落盘。第 6 篇 Apply 管道在此之上串行调度 Raft entry;第 8 篇 ReadIndex 保证线性读在 applied index 对齐后再读 MVCC。

Defragbackend.Defrag())会 LockOutsideApply 独占 backend,与在线 batch 互斥——运维窗口见第 12 篇。


六、与 MVCC store 的事务边界

一次 MVCC 写事务(storeTxnWrite)典型步骤:

  1. BatchTx().LockInsideApply()(由 apply 钩子保证在 apply 栈内);
  2. UnsafeSeqPut 写入 key bucket,treeIndex.Put 更新内存索引;
  3. End() 递增 currentRev,释放 store 锁;
  4. backend 层按 batch 规则异步或同步 commit。

watchableStoreTxnWrite.End() 在 MVCC 提交后调用 notify() 推送 Watch——Watch 触发点在 MVCC 层,不等待 bbolt fsync,但受 delete 立即 commit 规则保护一致读。

单写者意味着:不存在跨 key 的并行 apply 写 bbolt;Raft 已串行化日志,apply 再串行映射到 MVCC,与 bbolt 模型一致。试图用「多线程 apply」绕过此约束会与 quota、consistent index、Watch 顺序全面冲突。


参考资料

规范 / 源码(A)

站内对照

争论 / 开放问题


上一篇MVCC 数据模型

下一篇treeIndex 与 Apply 管道

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .