【etcd】treeIndex 与 Apply 管道:propose → commit → apply

💡 原文中文,约5800字,阅读约需14分钟。
📝

内容提要

本文介绍etcd v3.5.33中Raft提交条目如何通过apply管道转为MVCC revision并触发Watch。核心是区分committed index与applied index:commit仅表示日志复制完成,apply才更新MVCC状态。applyAll循环处理快照和条目,treeIndex内存索引过滤revision后批量读bbolt。Watch在事务End时同步通知。apply滞后会通过ErrTooManyRequests背压客户端,排障需区分Raft复制慢还是apply慢。

🔎

延伸解读

commit 与 apply 的分离是理解 etcd 一致性的关键

本文强调 committed index 与 applied index 是两个不同的概念:commit 只表示日志已复制到多数派,而 apply 才真正更新 MVCC 状态。因此,即使一个条目已被提交,客户端也可能暂时读不到它(除非使用线性读)。这种分离意味着在排查写入延迟时,需要区分是 Raft 复制慢还是 apply 慢,两者对应的证据和优化方向完全不同。

treeIndex 与 bbolt 的分工避免全表扫描

treeIndex 是内存中的 B-tree,维护每个 key 的 revision 链,而 bbolt 是磁盘上的持久化存储。Range 操作先在 treeIndex 中过滤出符合条件的 revision,再批量读取 bbolt,从而避免全表扫描。这种设计是 etcd 高性能读的关键,也解释了为什么 compaction 需要同时清理内存索引和异步删除磁盘数据。

Watch 触发时机与 apply 延迟直接相关

Watch 事件在 MVCC 事务 End 时同步通知,因此 Watch 的延迟等于 apply 延迟加上通知队列的调度延迟,而不是 Raft commit 的延迟。当 apiserver 的 Watch 卡住时,应优先检查 applied index 和 MVCC revision,而不是只关注 Raft 状态。这有助于快速定位是 apply 慢还是 watcher 背压问题。

apply 滞后会通过背压机制反馈到写路径

当 committed index 与 applied index 的差距超过阈值时,etcd 会返回 ErrTooManyRequests 来背压客户端 proposal,这是 apply 滞后直接反馈到写路径的门。此外,snapshot apply 会阻塞整个管道,期间 normal entry 不推进。因此,监控 applied index 与 committed index 的差距,以及 applySnapshotInProgress 指标,是预防和诊断 etcd 性能问题的有效手段。

Q&A

etcd中committed index和applied index有什么区别?

committed index表示Raft日志已提交的位置,即日志已复制到多数派并持久化;applied index表示已应用到MVCC存储的位置。commit只表示日志复制完成,apply才更新MVCC状态。如果applied index落后于committed index,客户端可能读到旧数据(线性读会等待)。

etcd的apply管道是如何工作的?

apply管道由EtcdServer.applyAll循环驱动,每个tick执行:applySnapshot(处理新快照)、applyEntries(消费已提交但未应用的条目)、applyWait.Trigger唤醒等待线性读的goroutine。applyEntries调用apply()逐条处理EntryNormal和EntryConfChange,其中Normal条目反序列化为InternalRaftRequest后经applyV3执行Put/Txn等操作。

etcd中treeIndex的作用是什么?

treeIndex是etcd的内存索引,基于google/btree实现,用于维护key到revision的映射。它记录每个key的revision链和tombstone,在Range操作时先在treeIndex中过滤出符合条件的revision,再批量读取bbolt,避免全表扫描。

etcd中Watch事件是在什么时候触发的?

Watch事件在MVCC写事务结束时触发。具体在watchableStoreTxnWrite.End()中,先构造事件列表,然后在watchableStore.mu锁保护下调用notify(rev, evs)同步通知watcher。事件revision为apply后的新main rev。

etcd中apply滞后会导致什么现象?

apply滞后会导致客户端可能读到旧数据(线性读会等待),并且当committed index与applied index差距超过maxGapBetweenApplyAndCommitIndex时,processInternalRaftRequestOnce会返回ErrTooManyRequests,对客户端proposal进行背压。

如何区分etcd是Raft复制慢还是apply慢?

可以通过观察索引来区分:如果committed index落后于leader index,说明是Raft复制/WAL慢;如果applied index明显小于committed index,说明是apply/backend batch/大事务慢。另外,线性读慢但写正常可能指向ReadIndex+applyWait问题。

etcd中consistent index的作用是什么?

consistent index用于确保崩溃恢复时不会重复应用已持久化的v3状态。在applyEntryNormal中,当entry index大于consistent index时,会设置ShouldApplyV3=ApplyBoth,并在bbolt事务锁内更新consistent index,与Raft applied index对齐。这样重复启动时,已持久化的entry可跳过MVCC重放,加速恢复。

🏷️

标签

➡️

继续阅读