很多团队第一次治理小文件,会加一个凌晨两点的 Spark 脚本:扫描昨天的分区,把文件合到 512 MB。刚上线时效果明显,过一阵又会遇到任务跑不完、与实时写入冲突、刚合完第二天又碎了,最后脚本变成一个没人敢停的定时黑盒。
我更愿意把 Compaction 看成表的持续维护服务。它不是“每天执行一次重写”,而是一条有输入指标、候选选择、预算、提交证据和效果验收的控制循环。表什么时候需要整理、整理哪部分、允许花多少资源、失败后如何继续,都应该可计算。
邓明瑞 / 纯粹
很多团队第一次治理小文件,会加一个凌晨两点的 Spark 脚本:扫描昨天的分区,把文件合到 512 MB。刚上线时效果明显,过一阵又会遇到任务跑不完、与实时写入冲突、刚合完第二天又碎了,最后脚本变成一个没人敢停的定时黑盒。
我更愿意把 Compaction 看成表的持续维护服务。它不是“每天执行一次重写”,而是一条有输入指标、候选选择、预算、提交证据和效果验收的控制循环。表什么时候需要整理、整理哪部分、允许花多少资源、失败后如何继续,都应该可计算。
把数据任务从 YARN 或固定机器迁到 Kubernetes,最容易做的一步是把命令装进镜像,创建一个 Job。最容易出问题的一步,是把原来调度系统的实例语义原样套在 Pod 上。
数据平台关心的是某个业务日期、某次补数、某次人工重跑有没有完成,并且输出是否能被下游使用。Kubernetes 关心的是控制器怎样让指定数量的 Pod 成功结束。两者有交集,但不是同一件事。Pod Succeeded、Job Complete 和“这批数据已经正确提交”之间,必须有一层明确映射。
Flink SQL 作业变慢后,最常见的动作是加并行度、调 managed memory、扩大 checkpoint timeout。这样有时能缓解问题,但也容易把一份错误的物理计划跑得更贵。
我接手一条 SQL 时,第一份材料不是 Web UI 截图,而是当时 Catalog、配置和 SQL 共同生成的 EXPLAIN。SQL 只是意图,真正消耗网络、状态和 CPU 的是优化后的物理节点。Join 选了广播还是 shuffle、聚合有没有两阶段、哪条边产生 Exchange、输出是 append 还是 retract,这些决定比一个笼统的“数据量大”更接近根因。
做统一 Catalog 时,团队通常先讨论要接哪些数据源、搜索框怎么做、血缘图画到字段还是表。我更愿意先追问一个看起来很基础的问题:prod 和 test 里都叫 sales.orders 的两张表,到底是不是同一个对象?
这个问题没答清楚,后面所有治理功能都会漂。采集任务重复跑一次可能多出一张表;集群迁移后,原来的标签和负责人找不到了;表重命名既可能把历史血缘切断,也可能错误地把两个对象合并。Catalog 不是把各种名称塞进一个搜索索引,它首先是一套对象身份系统。
在数据库里执行一条 UPDATE orders SET status='PAID' WHERE id=...,我们很容易把它想成“找到这一行,改掉几个字段”。把同样的语句放到湖仓表上,这个直觉会出问题。Parquet 数据文件是不可变的,Iceberg 管的是文件和元数据,不会钻进一个已经提交的 Parquet 文件里原地改几个字节。
于是,更新一行到底有多贵,不能只看 affected rows。它可能触发整个数据文件重写,也可能先增加 delete file,把合并成本推迟到查询和维护阶段。真正要算的是:命中了多少 data file、每个文件多大、产生多少 delete、读端要合并多少层,以及这些文件何时能被重新整理掉。
数据质量平台发出一条告警:“订单表行数校验失败,实际 980 万,期望大于 1000 万。”收到的人通常还要追问:检查的是哪个分区,数据任务跑完了吗,规则什么时候改过,这次是补数还是正常调度?
如果这些信息不在结果里,一条 FAIL 只是现象。甚至同一条规则重跑后变成 PASS,也无法判断数据被修复了,还是第二次读取了另一批数据。
Great Expectations 0.14.2 把一次验证拆成 Batch、Expectation Suite、RunIdentifier、Validation Result 和后续 Action。这个结构很值得数据平台参考:规则不是悬空执行,结果也不能只保存布尔值。一次质量结论必须能回答“用哪版规则,在什么时候,对哪批数据,算出了什么”。
给数据团队分 YARN 配额时,一个常见算法是统计上个月平均使用率,再按比例切队列。A 部门平均用了 40%,就给 40% capacity;B 部门平均 20%,就给 20%。报表看起来有数据依据,到了每天凌晨,两个部门的关键任务一起跑,队列还是排满。
平均值描述长期资源消耗,不描述任务在 SLA 窗口内是否碰到一起。一个队列每天只忙两小时,平均使用率很低;这两小时如果正好承担日结、报表和下游出数,它需要的保证容量可能比全天平滑运行的队列更高。
CapacityScheduler 的 capacity 本来就不是一堵静态资源墙。它给队列保证一部分容量,空闲资源可以被其他队列借用,maximum-capacity 再限制弹性上界。配额设计的重点应该是关键时间窗口内的并发需求、可等待时间和资源回收速度,而不是把集群按月均比例永久切开。
血缘项目最容易交付的是一张很大的图:点开一张表,左边几十张上游表,右边几十张下游表,连线铺满屏幕。演示时很直观,真正遇到问题却常常答不出来。
“改这个字段会影响谁”需要字段与表达式;“昨晚这批数据为什么错”需要运行实例、代码版本和输入输出水位;“这张表有哪些上游”用表级关系就够。把三种问题都塞进同一张表级拓扑,图再完整也只能回答资产发现。
Apache Atlas 2.1 的模型本身已经给出几种层次:基础 Process 通过 inputs/outputs 连接 DataSet;Hive 还有 hive_column_lineage,保存字段依赖和 expression;模型里也有 ProcessExecution 与 hive_process_execution 表达具体执行。关键不在于有没有这些类型,而是采集和查询时是否保留了问题需要的证据。