1. etcd 存储架构概述:理解底层存储引擎、MVCC 机制与多版本数据保留策略

在深入 etcd 的数据压缩与碎片整理之前,必须首先理解其底层存储架构。etcd 的存储层设计直接决定了数据膨胀、版本堆积以及空间回收的机制。本节将拆解 etcd 的三大核心存储要素:BoltDB/Go-bbolt 存储引擎、MVCC 多版本并发控制以及多版本数据保留策略。

1.1 底层存储引擎:BoltDB / Go-bbolt

etcd v3 版本之后,底层存储引擎从 Google 的 LevelDB 切换为 BoltDB(后由社区维护为 Go-bbolt)。BoltDB 是一个纯 Go 实现的嵌入式键值存储引擎,其核心特点如下:

特性 说明
存储模型 基于 B+ 树,所有数据存储在单个内存映射文件中(默认 member/snap/db)
事务支持 支持 ACID 事务,提供读事务(快照隔离)和写事务(串行化)
并发控制 同一时刻只允许一个读写事务,允许多个只读事务并行
数据组织 数据按 Bucket(桶)组织,每个 Bucket 内存储键值对
文件结构 单文件存储,通过 mmap 映射到进程地址空间,读写效率高
关键认知:BoltDB 的 B+ 树结构天然支持范围查询和顺序遍历,这与 etcd 的 watch 机制和前缀查询需求高度匹配。但 B+ 树的页分裂和页合并行为,也是后续碎片产生的根本原因。

etcd 在 BoltDB 之上构建了多个核心 Bucket,用于存储不同类别的数据:

  • key:存储所有键值对的主数据,键为 revision(版本号),值为键值数据
  • meta:存储元数据,如当前集群的 term、committed index 等
  • members:存储集群成员信息
  • auth:存储认证相关的角色和用户信息

1.2 MVCC 机制:多版本并发控制

etcd 实现了完整的 MVCC(Multi-Version Concurrency Control) 机制。每次对键值对的写入(Put)或删除(Delete)操作,都会生成一个新的版本记录,而不会原地覆盖旧数据。这是 etcd 支持历史查询、watch 事件回溯以及事务一致性的基础。

MVCC 核心设计:每个键值对关联一个全局递增的 revision(主版本号)。revision 由两部分组成:main(事务 ID)和 sub(事务内操作序号)。etcd 通过 revision 来区分不同版本的数据。

MVCC 的工作流程如下:

  1. 写入操作:客户端发起 Put(key, value) 请求
  2. 分配 revision:etcd 分配一个新的全局 revision,作为该版本的唯一标识
  3. 存储新版本:将 revision → key, value 写入 BoltDB 的 key bucket
  4. 更新索引:在内存 BTree 索引中记录 key 到最新 revision 的映射
  5. 保留旧版本:旧版本数据仍然保留在 BoltDB 中,不会被立即删除

以下 SVG 图展示了 etcd 的 MVCC 多版本存储结构:

etcd MVCC 多版本存储结构 etcd MVCC 多版本存储结构 客户端写入 分配全局 revision 内存 BTree 索引 BoltDB 存储层(key bucket) revision=1001 key="foo" value="v1" revision=1005 key="foo" value="v2" revision=1010 key="foo" value="v3(最新) 旧版本 v1、v2 仍保留,未被覆盖 内存索引 key="foo" → revision=1010 (指向最新版本)

1.3 多版本数据保留策略

MVCC 机制虽然带来了强大的功能,但也导致了存储空间的持续增长。每次更新或删除操作都会产生新的版本,旧版本数据并不会自动清理。etcd 通过以下机制管理多版本数据的保留与回收:

机制 触发条件 行为 影响
自动压缩(Auto Compaction) 基于时间(如每 5 分钟)或版本数(如保留 10000 个 revision) 标记旧版本为“可删除”,但不会立即回收磁盘空间 减少逻辑版本数,但 BoltDB 文件大小不变
手动压缩(Compact) 运维人员执行 etcdctl compact 命令 指定一个 revision,删除该 revision 之前的所有历史版本 释放逻辑空间,但物理空间仍被占用
碎片整理(Defrag) 运维人员执行 etcdctl defrag 命令 重新整理 BoltDB 文件,回收空闲页 真正释放磁盘空间,缩小 db 文件
重要区分:压缩(Compact)和碎片整理(Defrag)是两个不同的操作。压缩只标记旧版本为可删除,不回收磁盘空间;碎片整理才真正回收物理空间。很多运维人员混淆这两个概念,导致压缩后磁盘空间并未减少。

以下 SVG 图展示了 etcd 数据从写入到压缩再到碎片整理的完整生命周期:

etcd 数据生命周期:写入 → 压缩 → 碎片整理 etcd 数据生命周期:写入 → 压缩 → 碎片整理 阶段1:数据写入 Put/Delete 操作 生成新版本 阶段2:版本堆积 旧版本保留 BoltDB 文件膨胀 阶段3:压缩 标记旧版本可删除 逻辑空间释放 阶段4:碎片整理 回收空闲页 物理空间释放 关键认知 • 压缩后:BoltDB 文件大小不变,但内部产生大量空闲页 • 碎片整理后:BoltDB 文件大小缩小,空闲页被回收 • 建议:压缩后必须执行碎片整理,才能真正降低存储占用

1.4 存储空间膨胀的根本原因

综合以上架构特点,etcd 存储空间膨胀主要源于以下三个因素:

  • MVCC 版本堆积:每次写入都生成新版本,旧版本未被及时清理,导致 BoltDB 中存储了大量冗余数据
  • B+ 树页分裂:频繁的插入和更新操作导致 B+ 树节点分裂,产生大量半满或几乎为空的页,形成内部碎片
  • 压缩不彻底:压缩操作仅标记旧版本为可删除,但 BoltDB 不会自动合并空闲页,空闲页仍占用磁盘空间
运维启示:理解 etcd 的存储架构是制定压缩和碎片整理策略的前提。只有掌握了 MVCC 的多版本特性、BoltDB 的页管理机制以及压缩与碎片整理的区别,才能在实际运维中有效控制 etcd 的存储空间,避免磁盘爆满导致集群不可用。