资讯动态

Flink 的 checkpoint 为什么越跑越慢

发布时间:2026/10/26 13:13:27 来源:尧图企业网站定制
很多 Flink 作业的 checkpoint 间隔设得很短单次却跑得很久。本地 RocksDB 刷盘是顺序写快得多剩下的大头都在把文件传到对象存储。这就是这篇要拆的事。checkpoint 越跑越慢慢的不是快照是上传。先把结论放前面。增量 checkpoint 传的也不是纯增量。compaction 会把旧数据重新写成新文件赶上合并的那一轮就得多传一遍。对 exactly-once 来说checkpoint 是刚需绕不开。所以问题不是要不要做是怎么把开销压下来。要讲清楚这件事得先看增量 checkpoint 到底存了什么。RocksDB 是 LSM 树memtable 刷盘变成 SSTSST 一旦写完就不再改。刷盘是本地顺序写跟网络没关系。慢的那一段发生在文件离开本地盘之后。checkpoint 触发时Flink 让 RocksDB 把这一轮新产生的 SST 挑出来只传这些再附一份 metadata。metadata 里不放数据只放指向这些 SST 的引用。目录结构不复杂一个 job 一个目录下面分 shared、taskowned 和 chk-N。shared 放可能被多个 checkpoint 共用的文件taskowned 放 JobManager 不能删、也不属于某一轮的文件chk-N 放只属于这一轮的。共用靠引用计数认的是 state handle 的物理 ID不是文件路径。文件一旦被合并重写物理 ID 就换了引用计数也就认不出它是老文件。所以增量 checkpoint 不是一份完整备份是一条链每一轮挂在前一轮后面。一条链意味着一件事快照做完不等于文件已经传完。增量 checkpoint 的快照阶段只决定这一轮要传哪些文件真正的落地在异步上传里。JobManager 收集每个 subtask 的 state handle全部到齐这一轮才算完成。任何一个 subtask 上传翻车整轮 checkpoint 就失败上传这关绕不开。这里还有个容易被忽略的前提。增量 checkpoint 默认不开得手动把execution.checkpointing.incremental设成 true这是 2.3 的正名。很多人以为用了 RocksDB 就自动增量其实没有。全量和增量的差别不只是传多传少。全量 checkpoint 写的是 Flink 自己的 key/value 快照格式一份快照自带全部状态换后端也能读。增量传的是 RocksDB 的 SST恢复的时候省掉了重建本地表那一步。代价是恢复要顺着链把多个 delta 拉下来链越长拉得越多。这也是为什么增量 checkpoint 的恢复时间可能比全量还长。savepoint 走的是另一条路。它默认写成 canonical 格式所以不能靠增量链。要保留换状态后端的能力就得接受全量写一遍。canonical 格式的好处是跨后端可读坏处是每次都要全量写。链本身没问题问题在 compaction。RocksDB 会把小 SST 合并成大 SST读得少文件也少。但合并出来的是新文件文件名变了身份也变了。在 Flink 眼里这就是一批从没见过的新 SST得重新传一遍。官方文档写得很直白大多数增量状态后端都会周期性 compaction结果就是把旧状态重新上传一次。集群一大几乎每个 checkpoint 里都有一个 task 在上传大量数据。所以状态越大、更新越频繁、compaction 越勤增量 checkpoint 就越像全量。还有一层放大效应来自数据分布。状态是按 key 分到 subtask 的key 一歪某个 subtask 的 SST 就特别多。compaction 也是按本地数据跑的数据多的节点合并得更勤重传得也更多。于是每个 checkpoint 都有一两个重活节点别的节点早就传完了。上传还不只是总量的问题是短板问题。一个 checkpoint 要所有 subtask 都传完才算完成。九十九个传得快没用第一百个慢整体就慢。更麻烦的是排队。文档里说得很清楚checkpoint 跑得比间隔久下一个不会提前触发等当前完成之后立刻开始。于是系统会一直处在 checkpoint 里资源一直绷着。execution.checkpointing.min-pause能拉开一点距离但只是把频率降下来没解决单次为什么慢。execution.checkpointing.max-concurrent-checkpoints也不是解药并发多了只会让上传互相抢带宽。上传是异步的但完成事件是同步等的。JobManager 要等所有 handle 到齐才宣布完成异步只说明它不挡算子处理数据不代表完成判定可以提前。把间隔拉长也不是免费。间隔越长两次 checkpoint 之间的 delta 越大恢复点越旧。RPO 和上传成本本来就是跷跷板的两头。指标也别只看一个。duration 看总时长size 看这一轮传了多少full size 看总状态。三个一起看才知道是状态大了还是这一轮改多了。真正要看的是单次 checkpoint 里时间花在哪一段。看两段就够了对应 UI 里的 sync 和 async。快照阶段长多半是对齐或反压上传阶段长才是这篇文章要说的事。还有一项开销很多人根本没注意。checkpoint 目录是会留下来的。保留几份由execution.checkpointing.num-retained控制每份增量都引用一批 SST。引用计数没降到零之前这些文件不会被删。所以只要还在产生新 SST共享目录就会一直长引用计数归零只回收没人用的旧文件。FLINK-30461 讲过一个极端场面上传到一半超时已经传上去的 SST 没被登记共享目录里堆到上百万个文件。这个 bug 在 1.15.4、1.16.2 和 1.17.0 都修了但它说明了一件事上传失败不是重来一次就能抹平的是会留下垃圾的。本地备份能救恢复但救不了上传。execution.checkpointing.local-backup.enabled打开之后TaskManager 的本地盘还会留一份状态副本恢复时可以少拉远端。但 checkpoint 该传的还得传这份开销不会消失。还有一个容易看错的数字。开了增量之后Web UI 里的 Checkpointed Data Size 就是刚才说的 size只是这一轮的 delta不是全量状态。看到它很小就以为没事其实总状态可能已经很大只是这一轮刚好没怎么改。真要看清得看上传耗时、checkpoint 目录的增长速度、以及恢复时要拉多少数据。恢复这事也有反直觉的地方。网络带宽是瓶颈时增量 checkpoint 恢复可能比全量更慢因为要按链把多个 delta 都拉下来。CPU 或本地 IO 是瓶颈时增量反而快因为不用按 Flink 的 key/value 格式重建本地表。所以增量和全量之间不是单纯的新比旧好得看瓶颈在哪。那怎么办。我不太喜欢一上来就调间隔。间隔被吃满是上传慢的结果不是原因。先看三处。第一处是重写。compaction 越勤重传越多。能少更新、能提前把 key 打散、能让分布更稳都是在减少重写。第二处是上传。本地盘和对象存储之间的带宽、并发、单文件大小决定了这一轮要多久。对象存储按请求计费一个 SST 至少一次请求文件越小请求越多。同一个目录下对象太多list 也会变慢清理和恢复都受影响。小文件多了光请求开销就够肉疼让 SST 少而大往往更划算。大文件走分片上传吞吐能上去但并发和连接数又成了新的上限。小文件太碎在状态大的作业里很常见调参折腾半天到头来问题还在文件上。第三处是换路。ForSt 在 Flink 2.3 里还是实验特性SST 直接放远端天生就是增量。好处是 checkpoint 时不用再整批搬一次。代价是状态访问要过网络延迟敏感的场景不一定合适。但文档明确说不适合生产只能当方向看。三条路里前两条是今天能动的第三条是明年再看的事。说实话这套机制不算复杂坑都在细节里。传的是什么为什么重传谁在等谁哪个数字会骗人。这四个问题里踩坑最多的是第一个很多人根本不知道增量传的是 SST。把这四件事拆开checkpoint 慢在哪就清楚了。checkpoint 的时间最终都花在上传这一步。本地快照再快传不出去这一轮就不算完。Flink 2.3 的状态后端文档增量那一节我翻了几遍最要紧的就两句。一句是默认不开得手动打开另一句是 compaction 会把旧状态重新上传。这两句读懂了剩下的都能自己推。祝大家的 checkpoint别卡在末尾那个 subtask 上。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑