Paimon 主键表在 S3 上越写越碎:先把 compaction 的默认 5 看懂
某制造企业的订单明细实时入湖:Flink 从业务库接 CDC 数据,写进 Paimon 主键表,数据存在 S3 上,按天分区。上线头两周一切正常,第三周开始,下游的日报查询从几分钟拖到几十分钟,单个分区在 S3 上的文件数从几百涨到了上万。第一反应往往是“S3 读得慢”,但这次问题不在存储,在写入侧:文件碎了。
定性的方法很直接:拿同一个表的新分区和老分区对比。先数文件,再跑同一条查询——如果新分区文件少、查询快,老分区文件多、查询慢,基本可以确定是文件碎了,而不是 S3 或下游查询本身的问题。
文件是怎么碎掉的
Paimon 主键表是 LSM 结构。Flink 每完成一次检查点提交,就会产生一批新文件,落在 Level 0;每个 Level 0 文件单独算一个 sorted run。查询时,Paimon 要把同一个主键散落在各个 sorted run 里的版本合并起来,才能返回最新值——sorted run 越多,读放大越严重,查询就越慢。
关键是,compaction 不是每次提交都做。默认要攒够 5 个 sorted run 才触发一次合并(num-sorted-run.compaction-trigger 默认 5)。如果检查点间隔是 1 分钟,写入又持续不断,Level 0 文件的产生速度很容易跑赢合并速度。文件数就这样一次提交一次提交地涨上去,查询一次比一次慢。
更隐蔽的一点在分桶。主键表默认 bucket = -1,也就是动态分桶模式:新主键先落进已有桶,桶满 200 万行(dynamic-bucket.target-row-num 默认 2000000)才开新桶。主键基数大、数据量又估不准的表,桶数会在不知不觉中膨胀,每个桶又各自攒 Level 0 文件,碎上加碎。
先把三个默认值对上号
动手调之前,先把三个起决定作用的默认值对上号:
‘bucket’ = ‘-1’:动态分桶,不用提前算桶数。代价是不支持多作业并发写同一分区,而且桶数随数据到达顺序变化,事前不好预测。
‘num-sorted-run.compaction-trigger’ = ‘5’:攒够 5 个 sorted run 触发合并。调大能减少合并开销、提升写入,但查询时要合并的 run 更多,读放大变大;调小则反过来。
‘num-sorted-run.stop-trigger’:不填时默认是 trigger + 3,也就是 8。sorted run 攒到 8 个还没合完,写入会停下来等 compaction——写入侧先变慢,严重时还会把 Flink 检查点拖超时。
这三个数拼在一起,故障链就很清楚了:高频提交产生 Level 0 文件的速度超过了 5 个 run 才触发一次的合并速度,run 数一路涨到 8,写入被迫减速,查询还要在成堆的 run 里找最新值。
可以直接抄的四步做法
第一步,建表就把桶数定下来。 数据量能估出来的表,别用默认的动态分桶,按分区数据量定固定桶数:
CREATE TABLE dwd.order_detail (
dt STRING,
order_id BIGINT,
shop_id BIGINT,
user_id BIGINT,
amount DECIMAL(12, 2),
update_time TIMESTAMP(3),
PRIMARY KEY (dt, order_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH (
‘bucket’ = ‘8’,
‘bucket-key’ = ‘order_id’,
‘num-sorted-run.compaction-trigger’ = ‘5’
);
桶数怎么定,给一个经验值:单个分区的数据量除以 2GB 左右,再向上取整。比如一个分区一天大约 16GB,8 个桶就比较合适。桶太少限制写入并行,桶太多又回到小文件老路。固定分桶之后,桶数只能通过离线方式调整,所以建表时一次定准,比事后返工划算。
第二步,放宽检查点间隔。 检查点间隔从 1 分钟放到 3 分钟,Level 0 文件的产生速度直接降到三分之一,给合并留出追赶的时间:
execution.checkpointing.interval: 3min
实时性要求没那么高的明细表,3 分钟的延迟通常可以接受;换来的是一天少产生三分之二的 Level 0 文件。
第三步,历史分区定期全量合并。 当天分区还在写,合并交给触发器;已经不再写入的历史分区,用存储过程定期做一次全量合并,只处理 1 天没新数据的分区:
CALL sys.compact(
table => ‘dwd.order_detail’,
partition_idle_time => ‘1 d’
);
也可以精确到某一个分区:
CALL sys.compact(table => ‘dwd.order_detail’, partitions => ‘dt=20261106’);
第四步,写入和合并分开跑。 写入量大的表,在表属性里设置 ‘write-only’ = ‘true’,让写入作业跳过 compaction 和快照过期,再单独起一个 compaction 作业定时跑。官方文档明确提醒过:默认的同步 compaction 可能拖慢检查点、导致检查点超时,写入和合并分开是最稳妥的做法。
做完这四步,用一条命令验证效果:
aws s3 ls s3://datalake/dwd.db/order_detail/dt=20261106/ --recursive | wc -l
同一个分区,合并前后的文件数对比是最直接的验收标准。文件数下来了,下游查询耗时一般会跟着下来。
小结
Paimon 主键表文件越写越碎,不是 S3 的问题,而是 LSM 表“写得多、合得慢”的必然结果:每次检查点提交都产生 Level 0 文件,攒够 5 个 sorted run 才触发一次合并,攒到 8 个写入还要停下来等。治理的顺序是:建表时按分区数据量定死桶数,别留着默认的动态分桶;检查点间隔放到 3 分钟,减少 Level 0 文件的产生速度;历史分区定期全量合并;写入量大的表把写入和合并分开跑。先把三个默认值看懂,小文件才治得住。
- 点赞
- 收藏
- 关注作者
评论(0)