【etcd】写入路径深读:Txn、mod revision 与 quota/alarm
内容提要
本文介绍etcd v3.5.33的写入路径与容量管理。写入经gRPC→Propose→Raft提交→quota检查→MVCC apply,ModRevision在事务内共享。当后端大小超配额时触发NOSPACE alarm,拒绝写操作但允许读和删除,需defrag恢复。还涉及apply积压导致的ErrTooManyRequests错误,以及K8s控制面相关故障排查。
延伸解读
ModRevision 与 Txn 内 sub revision 的语义
同一事务内多次 Put 共享同一个 ModRevision,仅以 sub 区分物理行。这意味着客户端若在事务内多次写入同一 key,最终 ModRevision 为事务的 main revision,而 Version 会递增。理解这一点有助于排查依赖 ModRevision 做 CAS 或 watch 的场景,避免误以为每次 Put 都会产生新的 ModRevision。
ErrTooManyRequests 与 quota 超限的区分
ErrTooManyRequests 表示 apply 积压,即 committed index 领先 applied index 过多,与 quota 无关。此时应检查 backend batch、大事务或 snapshot,而非直接 defrag。而 quota 超限会触发 NOSPACE alarm,拒绝写但允许读和删除。区分两者有助于快速定位 etcd 写入失败的原因。
quota 度量的是物理空间而非逻辑大小
quota 判断基于 bbolt 文件物理分配字节(含未回收空洞),而非 key 数量。因此 delete 操作不释放 quota,必须 compaction 加 defrag 才能回收空间。这解释了为何只 disarm alarm 不 defrag 会再次触顶,也提示运维需定期清理历史数据。
Q&A
etcd 中写事务(Txn)和普通 Put 在写入路径上有什么区别?
在 etcd v3.5.33 中,写事务(包含写操作的 Txn)和普通 Put 都通过 raftRequest 提交到 Raft 日志,经过 Propose 和 apply 流程。只读事务(仅包含读操作的 Txn)则不走 Raft 日志,而是通过 linearizableReadNotify 或本地 applyV3Base.Txn 处理。
etcd 中 ModRevision 在事务内是如何分配的?
在 etcd 中,每次改变 MVCC 状态的 apply 操作会在事务结束时递增 currentRev,作为该事务的 main revision。事务内第 i 个变更(从 0 开始)写入 bbolt 时使用 (main, sub) 作为修订号,其中 main 为 beginRev+1,sub 为 i。ModRevision 字段取 main,因此同一事务内的多个 Put 共享同一个 ModRevision,通过 sub 区分物理行。
etcd 返回 ErrTooManyRequests 错误的原因是什么?
etcd 返回 ErrTooManyRequests 是因为 committed index 领先 applied index 过多,即 apply 队列积压严重。Leader 在 Propose 前会检查 committed index 与 applied index 的差距,如果超过 maxGapBetweenApplyAndCommitIndex,就会拒绝新的 proposal,以防止 apply 队列无限堆积。这与配额无关,属于 apply 滞后症状,应检查 backend batch、大事务或 snapshot,而不是先进行 defrag。
etcd 的默认后端配额是多少?如何计算配额使用量?
etcd 的默认后端配额是 2 GiB(DefaultQuotaBytes),当 --quota-backend-bytes=0 时使用。官方建议的上限是 8 GiB(MaxQuotaBytes),超过可能降低性能。配额度量的是 Backend().Size(),即 bbolt 文件的物理分配字节数,包含未回收的空洞,而不是逻辑 key 个数。写入操作的成本估算为:Put 为 256 + len(key) + len(value),Txn 为 success/failure 分支中较大一侧的成本之和,LeaseGrant 为 64 字节。
etcd 触发 NOSPACE alarm 后,哪些操作会被拒绝?如何恢复?
当 etcd 后端大小超过配额时,会触发 NOSPACE alarm。激活后,写操作(Put、Txn、LeaseGrant)会在 RPC 层被拒绝,但读操作(Range)、删除和压缩(compaction)仍可进行。恢复步骤是:先执行 etcdctl alarm disarm 解除警报,然后进行 defrag 回收空间,并确保 Size() 低于配额。只 disarm 不 defrag 会导致再次触顶。
K8s 控制面中 apiserver 写 etcd 失败可能有哪些原因?
K8s 控制面中 apiserver 写 etcd 失败的错误链可能包括:1) ErrTooManyRequests,表示 etcd apply 积压,常伴随 apiserver 超时;2) mvcc: database space exceeded,表示元数据盘满,需要 defrag、扩容配额或清理事件;3) Raft propose 超时,这属于 Raft 提交问题,与配额无关。