程序锅

  • 首页
  • 分类
  • 标签
  • 归档
  • 关于

  • 搜索
基础知识 Etcd LeetCode 计算机体系结构 Kubernetes Containerd Docker 容器 云原生 Serverless 项目开发维护 ELF 深入理解程序 Tmux Vim Linux Kernel Linux numpy matplotlib 机器学习 MQTT 网络基础 Thrift RPC OS 操作系统 Clang 研途 数据结构和算法 Java 编程语言 Golang Python 个人网站搭建 Nginx 计算机通用技术 Git

Etcd Compact

发表于 2023-02-03 | 分类于 Etcd | 0 | 阅读次数 2470

整体架构

etcd 中每一次更新、删除 key 的操作,treeIndex 的 keyIndex 索引中都会追加一个版本号,boltdb 中也都会生成一个新版本的 boltdb key 和 value。随着不停更新、删除,etcd 进程内存占用和 db 文件会越来越大,最终导致 etcd OOM 和 db 大小增长到最大 db 配额,最终不可写。

etcd 主要通过压缩(compact)机制来回收历史版本数据,控制内存占用和大小。目前支持两类方式进行压缩,一种是 client 发起人工压缩操作,另一种配置自动压缩策略(时间周期和保留版本号数量)。

下图是整个压缩模块的架构图,

  1. API 发起一个 Compact 请求的时候,KV Server 会收到 Compact 请求,并提交给 Raft 模块处理。如果配置了相应的自动压缩策略,那么自动压缩模块(Periodic Compactor/Revision Compactor)会根据配置的策略发起 Compact 请求。

  2. 在 Raft 模块中提交后,Apply 模块会继续处理该请求。Apply 模块会调用 MVCC 模块的 Compact 接口执行压缩任务。

  3. MVCC Compact 接口会首先更新当前已压缩的版本号,然后将耗时昂贵的压缩任务保存到 FIFO 队列中异步执行。

    详细的过程为:

    • 首先会检查 Compact 请求的版本号 rev 是否已被压缩过,若是则返回 ErrCompacted 错误给 client。收到的错误是:("mvcc: required revision has been compacted") 。

    • 其次会检查 rev 是否大 于当前 etcd server 的最大版本号,若是则返回 ErrFutureRev 给 client。收到的错误是:("mvcc: required revision is a future revision")。

    • 上述检查都通过后。会通过 boltdb 的 API 在 meta bucket 中更新当前已调度的压缩版本号 (scheduledCompactedRev) 号,然后将压缩任务追加到 FIFO Scheduled 中,异步调度执行。

      保存 scheduledCompactedRev 的主要原因是 Compact 任务是异步执行的,如果在异步执行的过程中了发生了 crash,又没有持久化这个版本号,则重启之后各个节点的数据可能会不一致。如果持久化了 scheduledCompactedRev,节点 crash 重启后,可以重新向 FIFO Scheduled 中添加压缩任务,从而保证各个节点间的数据一致性。

  4. 压缩任务执行时,它会首先压缩 MVCC 中 treeIndex 模块的 keyIndex 索引,其次遍历 boltdb 中的 key,删除已废弃的 key。

    详细的过程是:

    • 首先是压缩 treeIndex 模块中的各 key 的历史版本、已删除的版本。

      为了避免压缩工作影响读写性能,会克隆一个 B-tree,然后在克隆后的 B-tree 上遍历每一个 keyIndex 对象。它会保留 keyIndex 中最大的版本号,移除小于等于 CompactedRev(压缩版本号)的版本号。保留 keyIndex 中最大的版本号是因为最大版本号是这个 key 的最新版本,移除了会导致 key 丢失,而 Compact 的目的是回收旧版本。但是如果 keyIndex 中的最大版本号被打了删除标记 (tombstone),那么小于等于该版本号的都是无效的,整个 keyIndex 都会被删除。

    • 压缩任务执行完索引压缩后,它通过遍历 B-tree、keyIndex 中的所有 generation 获得 treeIndex 中有效的版本号,并记录在 map 中返回给 boltdb 模块使用。

    • 之后就是删除 boltdb 中废弃的历史版本数据。

      它会通过一个名为 scheduleCompaction 任务来完成。scheduleCompaction 任务获取到 boltdb 中从 0 到 CompactedRev 的所有 key(boltdb 中的 key 是 revision),然后再通过 treeIndex 模块返回的有效索引信息,判断这个 key 是否有效,无效则调用 boltdb 的 delete 接口将 key-value 数据删除。

      由于 scheduleCompaction 任务遍历、删除 key 的过程可能会对 boltdb 造成压力,为了不影响正常读写请求,它在执行过程中会通过参数控制每次遍历、删除的 key 数(默认为 100,每批间隔 10ms),分批完成 boltdb key 的删除操作。

      这个过程中,scheduleCompaction 任务还会更新当前 etcd 已经完成的压缩版本号 (finishedCompactRev),将其保存到 boltdb 的 meta bucket 中。

整个过程其实需要注意以下几点:

  • 压缩的本质是回收历史版本,目标对象是历史版本,不包括一个 key-value 数据的最新版本,因此可以放心执行压缩命令,不会删除你的最新版本数据。
  • Watch 机制中的历史版本数据同步依赖于 MVCC 中是否还保存了相关数据,因此在调用压缩 API 或者配置自动压缩策略的时候需要考虑这点。

自动压缩策略

自动压缩策略支持两种模式,分别是按时间周期性压缩和保留版本号数量压缩。配置相应策略后,etcd 节点会自动周期性地发起 Compact 操作。etcd 中通过以下两个参数进行配置,

  • auto-compaction-mode 为 periodic 时,表示启用时间周期性压缩,auto- compaction-retention 为保留的时间的周期,比如 1h。
  • auto-compaction-mode 为 revision 时,表示启用版本号压缩模式;auto- compaction-retention 为要保留的历史版本号数量,比如 10000。
  • auto-compaction-retention 为'0'时,将关闭自动压缩策略。
// Interpret 'auto-Compaction-retention' one of: periodic|revision.
--auto-compaction-mode
// Auto compaction retention length. 0 means disable auto Compaction.
--auto-compaction-retention

时间周期性

etcd server 启动后,如果配置的模式是 periodic,则会创建 periodic Compactor。periodic Compactor 会异步定时获取并记录过去一段时间的版本号。如果当前时间减去上一次成功执行 Compact 操作的时间大于一个小时,则会从记录中取出版本号,然后发起压缩操作。

如果压缩周期为 1h,periodic Compactor 会将其划分成 10 个区间,每个区间 6 分钟。每隔 6 分钟,它会通过 etcd MVCC 模块的接口获取当前的版本号,追加到 rev 数组中。如果当前时间减去上一次成功执行 Compact 操作的时间大于一个小时,它会取出 rev 数组首元素,调用 etcd 的 Compact 接口,发起压缩操作。

保留版本号

etcd server 启动后,如果配置的模式是 revision,则会创建 revision Compactor。revision Compactor 会根据设置的保留版本号数量,每隔 5 分钟定时获取当前的最大版本号,减去配置的要保留的历史版本数量,然后通过 etcd server 的 Compact 接口发起压缩操作即可。

保留版本号适合于这样的场景:当写请求比较多,可能产生比较多的历史版本导致 db 增长时,或者不确定配置 periodic 周期为多少才是最佳的时候。此时,可以通过设置压缩模式为 revision,指定保留的历史版本号数。

手动执行压缩

如果 etcd server 的自带压缩机制无法满足诉求,想要更精细化的控制 etcd 保留的历史版本记录。此时可以基于 etcd 的 Compact API,在自己的业务逻辑代码中主动触发压缩操作。

但是,此时需要确保发起 Compact 操作的程序高可用。压缩的频率、保留的历史版本都合理范围内,并最终能使 etcd 的 db 大小保持平稳,否则会导致 db 大小不断增长,直至 db 配额满,无法写入。

下面是通过 etcdctl 命令手动执行压缩的示例,

$ etcdctl compact 9

附录

为什么压缩后 db 文件大小不减少?

boltdb 删除大量的 key,在事务提交后 B+ tree 经过分裂、平衡,会释放出若干 branch/leaf page 页面,但是 boltdb 并不会将其释放给磁盘,因为释放和后续重新申请页的操作是昂贵的,对性能有较大的影响。因此为了实现高性能读写,boltdb 会通过 freelist page 记录这些释放的空闲页的位置,在收到新的写请求后,直接从空闲页数组中申请若干连续页使用,避免释放和申请导致的性能损耗,实现了高性能的读写。只有当连续空闲页申请无法得到满足的时候,boltdb 才会通过增大 db 大小来补充空闲页。

一般情况下,压缩操作释放的空闲页就能满足后续新增写请求的空闲页需求,db 大小会趋于整体稳定。

卷死我
dawnguo 微信支付

微信支付

dawnguo 支付宝

支付宝

  • 本文作者: dawnguo
  • 本文链接: /archives/269
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# Kubernetes # Etcd
Etcd DB Quota
Kubernetes CSI-基本原理
  • 文章目录
  • 站点概览
dawnguo

dawnguo

215 日志
24 分类
37 标签
RSS
Creative Commons
© 2018 — 2025 程序锅
0%