【etcd】MVCC 数据模型:Revision、keyIndex 与 generation

💡 原文中文,约7100字,阅读约需17分钟。
📝

内容提要

本文介绍etcd v3.5.33的MVCC数据模型,核心是Revision(main/sub)全序、keyIndex的generation生命周期,以及内存treeIndex与bbolt后端的职责分工。相比ZooKeeper的覆盖写和一次性Watch,etcd v3保留历史版本直到compaction,支持持久Watch和Txn compare可见性。文章还涉及与Watch、Txn、K8s resourceVersion的接口,以及历史保留与磁盘占用的权衡。

🔎

延伸解读

MVCC 与 ZooKeeper 的取舍

etcd v3 选择保留历史版本直到 compaction,而 ZooKeeper 采用覆盖写和一次性 Watch。这一设计使 etcd 支持持久 Watch 和从任意 revision 回放,但代价是磁盘占用和 compaction 运维成本。理解这一取舍有助于在选型时权衡功能与运维复杂度。

Revision 与 Raft index 的区别

Revision 是 MVCC 层的事务 ID,Raft index 是共识日志位置。虽然通常同步,但 internal 操作和 lease checkpoint 也会消耗 revision,因此排障时需区分:用 Revision 对齐 Watch 客户端,用 Raft index 对齐 WAL 轴。混淆二者可能导致错误定位。

generation 与 key 生命周期

generation 表示 key 的一个生命周期段,delete 不是抹掉历史,而是写入 tombstone 并开启新代。这使 Watch 能观察到 DELETE_EVENT,且 historical query 在 compaction 前仍可访问旧版本。理解 generation 有助于分析 key 的版本历史和 Watch 事件还原逻辑。

compaction 的运维权衡

auto-compaction 间隔设置需权衡:过长导致 WAL 和 bbolt 膨胀,过短则 historical Watch 易触发 ErrCompacted,进而引发 apiserver relist。文章指出没有 workload 无关的默认值,提示运维需根据实际负载调整,并关注高频更新 key 的内存占用。

Q&A

etcd v3 的 Revision 由哪两部分组成,它们分别代表什么?

Revision 由 main 和 sub 两部分组成。main 是全局事务 ID,每次 mutating 事务递增;sub 是同一事务内操作的序号,从 0 开始递增。

etcd v3 与 ZooKeeper 在数据模型和 Watch 机制上有何主要区别?

ZooKeeper 的 znode 是覆盖写,Watch 是一次性的;etcd v3 保留历史版本直到 compaction,支持持久 Watch 和从任意 revision 回放。

etcd 中 keyIndex 的 generation 是什么?delete 操作如何影响 generation?

generation 表示 key 的一个生命周期段,由创建时的 put 开始,后续 put 追加 revision,delete 写入 tombstone revision 并开启新的空 generation。delete 不会抹掉历史,而是标记墓碑并开启新代。

etcd 的 treeIndex 和 bbolt 后端分别存储什么内容,各自的作用是什么?

treeIndex 在内存中存储 key 到 keyIndex 的映射,用于范围查询和当前 key 集合;bbolt 在磁盘上存储 revision 到 (key, value, tombstone) 的映射,用于持久化和历史 revision 扫描。

etcd 的 Txn compare 中 mod_revision、create_revision 和 version 分别对应 keyIndex 的哪些字段?

mod_revision 对应 keyIndex.modified.main,create_revision 对应当前 generation 的 created.main,version 对应当前 generation 内 live 版本数。

Kubernetes 的 metadata.resourceVersion 与 etcd 的 Revision 有何关系?

apiserver 将 etcd 对象的 mod_revision(或等价 MVCC revision)暴露为 metadata.resourceVersion,controller 用它判断对象是否更新,语义上依赖 Revision 全序而非 Raft index。

etcd 中 compaction 是如何修剪 keyIndex 的?

Compaction 按 main revision 回收旧版本,删除 rev 小于等于 compact 点的版本,但保留该 compact 点内的最大版本。若某 generation 被清空则移除该代,所有代清空则移除整个 keyIndex。

etcd 的 Revision 在 bbolt 中是如何编码的?这种编码有什么好处?

Revision 在 bbolt 中编码为 17 字节:8 字节 big-endian main + '_' + 8 字节 big-endian sub。这种编码使得按 revision 排序的 range scan 可在后端高效执行,有利于 Watch 从历史 revision 追赶。

🏷️

标签

➡️

继续阅读