1. 这不是“学架构”,而是理解数据系统如何在真实压力下自我进化
“大数据架构了解迭代”——这个标题乍看像一句模糊的学习目标,但如果你在某公司数据平台组待过半年以上,就会立刻意识到:它根本不是指“读几篇论文、画几张图、背几个组件名字”,而是描述一种持续发生的、带着痛感的系统演进过程。我带过三届校招生做数据中台支撑,第一周必带他们看的不是Hadoop原理图,而是上个月凌晨三点的一次Flink任务失败告警记录、下游报表断更两小时的复盘文档,以及紧接着上线的Schema变更兼容方案。这才是“迭代”的真实切口:它始于故障,成于妥协,稳于监控,活于业务反馈。
所谓“了解迭代”,本质是建立一套动态认知框架——不再把Kafka、Spark、Iceberg当作静态名词去记忆,而是看它们在流量峰值、字段新增、口径冲突、SLA收紧这些具体压力下,如何被推着改、被逼着换、被业务倒逼着重新设计。比如某次营销活动期间,实时UV统计延迟从200ms飙到8秒,团队没先查Flink反压,而是翻出三个月前的埋点协议变更单,发现新老端埋点ID格式不一致,导致Flink SQL里大量CASE WHEN嵌套+UDF解析,CPU打满。最终解法不是升级机器,而是推动前端统一ID生成规则,并在Kafka Producer层做轻量级标准化。你看,架构迭代的起点,往往藏在一份被忽略的协议文档里,而不是技术选型PPT中。
这个主题适合三类人:刚转行做数据开发、还在写SQL跑批的新人;已能搭起离线链路但一碰实时就卡壳的中级工程师;以及负责数据产品交付、常被业务方问“为什么报表不准”的数据产品经理。它不教你怎么配置YARN队列,但会告诉你为什么某个队列参数调高后,反而让下游任务雪崩;它不罗列TiDB和Doris的Benchmark对比,但会拆解某次从MySQL分库分表迁到Doris时,如何用双写+影子表+流量染色,在不停服前提下完成数据一致性验证。所有内容都来自真实项目现场,没有假设,只有“当时我们做了什么,为什么这么做,后来发现哪里想错了”。
2. 架构迭代不是技术升级,而是问题驱动的系统再平衡
2.1 迭代的本质:在四个不可兼得的目标间找动态支点
很多初学者误以为架构迭代就是“用新工具替换旧工具”,比如把Hive换成Trino,把Kafka换成Pulsar。这就像认为治病就是换药——忽略了病根、体质、用药反应和康复节奏。真正驱动大数据架构迭代的,是四个相互拉扯的核心目标持续失衡:
- 吞吐能力(单位时间处理的数据量)
- 延迟水平(从数据产生到可用的时间差)
- 数据准确性(结果与事实的吻合度,含完整性、一致性、时效性)
- 运维成本(人力投入、机器资源、故障恢复时长)
这四者构成一个动态四边形,任何一边被强行拉伸,其他边必然收缩。举个典型场景:某电商大促期间,运营要求“实时GMV看板延迟≤30秒”,原离线T+1链路显然不满足。团队第一反应是上Flink实时计算。但实测发现,当Flink作业并发从50提至200时,Kafka集群Broker CPU飙升至95%,导致订单日志积压,部分分区延迟超5分钟——吞吐提升,却牺牲了延迟和准确性。此时若只盯着Flink调优,就是刻舟求剑。最终解法是:在Kafka层增加Topic分区数并启用压缩;在Flink Source端启用Per-Partition Fetcher减少连接数;同时将非核心维度(如用户设备型号)降级为T+1补算。你看,一次“实时化”迭代,实际是Kafka、Flink、业务口径三端协同再平衡。
提示:当你听到“我们要上XX新技术”时,先问三个问题:当前哪个目标严重失衡?新方案会让其他三个目标付出什么代价?有没有更小步的折中解?——90%的架构返工,源于跳过这三个问题直接写技术方案。
2.2 迭代的触发器:从来不是技术发布会,而是业务现场的刺痛点
翻遍我经手的17个架构迭代案例,没有一个是因“看到某篇技术文章觉得酷”而启动的。所有迭代都由具体业务事件触发,且具备强可追溯性:
| 触发事件类型 | 典型表现 | 对应架构动作 | 实际耗时 |
|---|---|---|---|
| SLA违约 | 报表每日9点未准时产出,连续3天 | 重构调度依赖链,引入Airflow SubDAG隔离高风险任务 | 11天 |
| 成本失控 | 单月计算资源费用超预算47%,其中32%来自空跑任务 | 开发任务健康度评分模型,自动下线低价值调度节点 | 23天 |
| 口径冲突 | 财务部与销售部GMV统计相差1200万,溯源发现同一订单在不同链路被重复计费 | 引入统一事实表+事务性写入,改造Flink CDC同步逻辑 | 38天 |
| 扩展瓶颈 | 新增10个省份数据接入,原Sqoop全量抽取耗时从2h涨至6.5h | 切换为Flink CDC + Iceberg增量合并,保留历史快照 | 19天 |
注意表格最后一列“实际耗时”:它远超技术方案设计时间。因为真正的难点不在代码,而在跨团队对齐——要让业务方接受“报表延迟从5分钟放宽到8分钟以换取稳定性”,要让运维同意开放Kafka ACL权限给数据开发,要让测试组理解“最终一致性”不等于“数据错误”。架构迭代的成败,50%取决于技术方案,50%取决于你能否把技术语言翻译成业务语言、把系统指标转化为财务指标、把故障率下降1%对应到客户投诉减少多少通。
2.3 迭代的隐蔽成本:那些方案文档里绝不会写的三类损耗
技术方案PPT永远只写收益,但从不提损耗。我在某金融客户做实时风控架构升级时,团队花了3周完成Flink SQL迁移,上线后却发现三类隐性成本远超预期:
第一类:知识熵增损耗
原Hive SQL脚本由5人维护,平均每人掌握20+个自定义UDF。迁移到Flink后,需重写为Java UDF,但仅2人熟悉Java生态。结果是:所有复杂逻辑集中到2人身上,他们成为单点瓶颈,任何请假都会导致需求阻塞。解决方案不是培训,而是强制推行“UDF最小化原则”——将90%的字符串处理下沉到Kafka Connect SMT(Single Message Transform),只保留核心业务逻辑在Flink中。
第二类:监控盲区扩大损耗
Hive有成熟的血缘追踪(Atlas)和执行计划分析(Tez UI)。Flink初期仅依赖Prometheus基础指标,当任务出现反压时,无法定位是Source端网络抖动还是Sink端JDBC连接池耗尽。我们被迫自研Flink Metrics Collector,将TaskManager日志中的GC时间、Checkpoint间隔、StateBackend读写延迟等127个维度指标注入Grafana,耗时额外14人日。
第三类:回滚路径失效损耗
Hive任务失败可直接重跑,Flink状态流一旦中断,从Savepoint恢复可能丢失部分事件。某次线上事故中,因Savepoint存储路径配置错误,导致无法回滚,最终采用“双写补偿”方案:将故障时段数据导出为Parquet,用Spark重跑后Merge进Iceberg表。这暴露了一个残酷事实:实时架构的“可逆性”远低于批处理,每一次迭代都必须预设比原方案更复杂的回滚预案。
注意:评估架构迭代ROI时,务必把这三类损耗按人日折算进成本。我见过太多项目因忽略知识熵增,导致上线后运维人力增加3人,年成本反超旧系统。
3. 真实迭代现场:从离线到实时的渐进式演进路径
3.1 阶段一:离线架构的“亚健康”诊断(不是推倒重来,而是精准切片)
很多人一说“大数据架构迭代”,脑海里就浮现“停机三天,全量切换”的悲壮画面。实际上,95%的成功迭代始于对现有离线链路的深度体检。我们不用“是否该换技术栈”提问,而是用四个手术刀式问题切开现状:
问题1:你的数据血缘是否真实可信?
很多团队声称“有Atlas血缘”,但当我随机抽查10个核心报表,发现其中7个的上游表在Atlas中显示为空(因Sqoop任务未配置--hive-import参数)。血缘失真会导致:当某张源表结构变更时,无法准确评估影响范围,只能靠人工逐个排查。解决方案不是重装Atlas,而是用Python脚本扫描所有Hive SQL文件,提取FROM/JOIN表名,与Hive Metastore API返回的实际表列表比对,自动生成血缘修复清单。这个脚本我们跑了两周,修复了213处血缘断点,成本几乎为零。
问题2:你的调度依赖是否存在隐性环路?
某客户Airflow DAG中,A任务依赖B,B依赖C,C又通过外部API调用间接触发A——表面无环,实则形成调度风暴。诊断方法很简单:导出所有DAG的JSON定义,用NetworkX构建有向图,运行nx.find_cycle()。我们发现3个隐藏环路,修正后调度失败率下降62%。关键在于:这类问题无法通过UI发现,必须用图论算法暴力检测。
问题3:你的数据质量规则是否覆盖核心业务语义?
90%的团队只监控“空值率”“唯一性”等基础规则,但业务真正关心的是:“促销订单中,优惠金额是否大于商品总价?”“退款订单的原始订单ID是否存在于主订单表?”我们用Great Expectations框架,将业务规则转化为expect_column_values_to_be_between("discount_amount", min_value=0, max_value="total_price")这样的断言,并嵌入到每个ETL任务末尾。当规则失败时,不仅告警,还自动生成差异样本供业务方确认——这比单纯报错“数据异常”有效十倍。
问题4:你的元数据是否支持自助式问题定位?
当业务方问“为什么这个指标今天少了20%”,老办法是让数据工程师查日志。新做法是:在Hive表COMMENT中强制填写“业务负责人”“更新频率”“口径定义链接”,并在调度系统中关联“最近3次执行的输入数据量/耗时/失败原因”。这样业务方可自行点击钻取,80%的问题无需工程师介入。
实操心得:别急着写Flink代码。先花3天做这四项诊断,你会惊讶地发现:30%的“性能问题”其实是血缘缺失导致的重复计算,40%的“数据不准”源于元数据未定义业务口径。这些才是迭代最该优先解决的“脏活”。
3.2 阶段二:实时能力的“寄生式”植入(在旧系统上长出新器官)
所谓“寄生式植入”,是指不改变原有离线链路,而是在其关键节点注入实时能力。这避免了“推倒重来”的政治风险和技术风险。我们以“用户行为日志实时分析”为例,展示四步寄生法:
第一步:日志采集层寄生(Kafka替代Flume)
原架构:Nginx日志→Flume→HDFS→Hive
问题:Flume Channel易堆积,故障恢复慢,且不支持Exactly-Once语义。
寄生方案:在Flume Agent后增加Kafka Sink,配置flume.sinks.kafka-sink.type = org.apache.flume.sink.kafka.KafkaSink,同时保留原有HDFS Sink。这样日志双写:一份走老路进Hive,一份进Kafka供实时消费。验证期长达2周,确保Kafka写入无丢数,才逐步降低Flume HDFS Sink的优先级。
第二步:计算层寄生(Flink SQL嵌入Hive Catalog)
原架构:Hive SQL处理T+1行为宽表
寄生方案:在Flink中创建Hive Catalog,直接查询Hive表作为维表。例如:
CREATE CATALOG hive_catalog WITH ( 'type' = 'hive', 'default-database' = 'ods', 'hive-conf-dir' = '/opt/hive/conf' ); USE CATALOG hive_catalog; -- 实时流关联Hive维表 SELECT uv.user_id, dim_user.city, COUNT(*) as pv_cnt FROM kafka_stream AS uv JOIN hive_catalog.ods.dim_user FOR SYSTEM_TIME AS OF uv.proctime AS dim_user ON uv.user_id = dim_user.id GROUP BY uv.user_id, dim_user.city;此举让实时任务能直接复用现有Hive维表,避免维表重复建设。
第三步:存储层寄生(Iceberg作为实时-离线统一存储)
原架构:实时结果存Redis,离线结果存Hive,两者口径常不一致。
寄生方案:用Flink写入Iceberg表,同时配置Hive Catalog使其可被Hive查询。关键配置:
'format-version' = '2', -- 启用Row-level Delete 'write.upsert.enabled' = 'true', -- 支持主键更新 'write.target-file-size-bytes' = '134217728' -- 128MB,适配Hive查询性能这样,业务方既可用Flink实时消费Iceberg表,也可用Hive SQL查历史快照,真正实现“一份数据,两种服务”。
第四步:服务层寄生(Trino统一查询入口)
原架构:实时查Redis,离线查Hive,BI工具需配置两个数据源。
寄生方案:部署Trino,配置Iceberg Connector和Hive Connector,在Trino中建View统一抽象:
CREATE VIEW dwd.user_behavior AS SELECT * FROM iceberg.ods.user_behavior UNION ALL SELECT * FROM hive.ods.user_behavior_his;BI工具只需连Trino,自动路由到实时或离线数据源。当实时数据延迟时,Trino会自动fallback到离线表,对业务方完全透明。
关键经验:寄生式迭代的核心是“能力解耦”。不要追求“一个系统解决所有问题”,而要让每个组件只做自己最擅长的事——Kafka管可靠传输,Flink管实时计算,Iceberg管ACID存储,Trino管统一查询。这种松耦合比单体架构更具迭代韧性。
3.3 阶段三:混合架构的“熔断式”治理(当实时与离线开始打架)
当实时链路和离线链路并存,必然出现“数据打架”。某次大促中,实时GMV看板显示成交1.2亿,离线报表显示1.15亿,差额500万引发高层质疑。我们没急着查代码,而是启动“熔断式治理”三板斧:
第一斧:建立数据一致性黄金标准
放弃“实时vs离线谁对”的争论,定义第三方权威源——用订单中心MySQL binlog作为事实源头,通过Flink CDC实时同步到Kafka,再分别供给实时链路(Flink消费)和离线链路(Sqoop定时抽取)。所有数据差异必须回归到这个源头比对。
第二斧:实施熔断阈值管理
在实时链路中嵌入一致性校验模块:每10分钟,用Flink SQL抽样1000条实时订单,与MySQL源表比对关键字段(order_id, amount, status)。当差异率>0.5%时,自动触发熔断:
- 停止实时看板数据推送
- 切换至离线报表缓存数据
- 发送告警并启动差异分析Job
这个熔断机制上线后,数据争议从每月平均4.7次降至0.3次。
第三斧:推行“数据契约”责任制
要求每个数据生产方(如订单中心)提供Schema Registry契约:
{ "topic": "order_events", "version": "2.1", "fields": [ {"name": "order_id", "type": "string", "required": true}, {"name": "amount", "type": "double", "required": true, "min": 0.01}, {"name": "status", "type": "string", "enum": ["created","paid","shipped","closed"]} ] }当契约变更时,必须走审批流程,下游消费者收到通知后,有72小时窗口期适配。这从源头杜绝了“字段悄悄变类型导致实时任务解析失败”的经典事故。
注意:混合架构最大的陷阱是“两边都信,两边都不信”。熔断式治理的本质,是用工程手段承认“实时和离线永远存在差异”,然后把差异控制在业务可容忍范围内,并让差异变得可观测、可追溯、可干预。
4. 迭代避坑指南:那些只有踩过才懂的12个致命细节
4.1 Kafka使用:别只盯着吞吐,分区数设计决定生死
Kafka分区数不是越大越好。某次我们为应对大促,将订单Topic分区数从12调至96,结果Flink Consumer出现严重rebalance,消费延迟飙升。根本原因在于:Flink Kafka Consumer的并行度默认等于分区数,96个TaskManager线程争抢ZooKeeper连接,导致Session Timeout。正确做法是:
分区数公式:
max(业务峰值TPS / 单分区吞吐, 下游消费者最大并行度)
单分区吞吐实测值:SSD磁盘约50MB/s,即约5万条/秒(按1KB/条估算)
若峰值TPS为20万,则分区数≥4;若下游Flink并行度上限为8,则分区数取8而非96关键配置:必须设置
'partition.discovery.interval.ms' = '300000'(5分钟),避免频繁分区发现导致rebalance;同时禁用'enable.auto.commit' = 'false',改用Flink Checkpoint管理Offset血泪教训:某团队为“保险起见”设分区数为200,结果Kafka Broker内存溢出——因每个分区需独立的Index文件和Log Segment,200分区意味着200×3=600个文件句柄,远超Linux默认ulimit限制。最终通过
echo "* soft nofile 65536" >> /etc/security/limits.conf解决,但这属于亡羊补牢。
4.2 Flink状态管理:RocksDB不是万能解药,小心磁盘IO成瓶颈
Flink默认StateBackend是HeapStateBackend,内存有限制。很多团队直接切RocksDB,却忽略其磁盘IO特性。我们在某实时风控场景中,RocksDB状态大小达120GB,但发现Checkpoint耗时从2分钟涨至18分钟。分析iostat后发现:await(I/O请求平均等待时间)高达120ms,远超正常值(<10ms)。原因在于:
- RocksDB默认使用
LevelCompactionStyle,频繁的Minor Compaction产生大量随机写 - SSD寿命与随机写次数强相关,某次Compaction风暴导致SSD提前报废
解决方案组合拳:
- 将RocksDB目录挂载到NVMe SSD(非普通SATA SSD)
- 配置
'state.backend.rocksdb.options' = 'max_background_compactions=4;max_open_files=-1' - 关键:启用增量Checkpoint
('state.checkpoints.incremental' = 'true'),使每次Checkpoint只写增量SST文件,而非全量镜像
实操提示:用
./flink list -a查看所有Job的Checkpoint Size,若单个State超50GB,必须启动RocksDB专项优化,否则随时可能因磁盘IO打满导致整个集群假死。
4.3 Iceberg Schema演化:addColumn不是安全操作,小心历史快照失效
Iceberg支持addColumn,但某次我们为新增用户等级字段执行:
ALTER TABLE iceberg.ods.user_behavior ADD COLUMN level STRING;结果下游Spark SQL查询历史快照(SELECT * FROM iceberg.ods.user_behavior VERSION AS OF 12345)时,新字段返回NULL——这看似合理,但业务方要求“历史数据也需填充默认值”。Iceberg本身不支持为历史快照设置默认值,必须用Spark重写历史文件:
# 重写指定Snapshot的Parquet文件 df = spark.read.format("iceberg").option("snapshot-id", "12345").load("iceberg.ods.user_behavior") df_filled = df.na.fill({"level": "unknown"}) df_filled.write.format("iceberg").mode("overwrite").option("replace-partition", "true").save("iceberg.ods.user_behavior")此操作耗时8小时,期间表不可写。更糟的是,若表有100个历史快照,需重写100次。因此我们制定铁律:
- Schema变更必须前置评估影响快照数:用
SELECT snapshot_id FROM iceberg.ods.user_behavior.snapshots ORDER BY committed_at DESC LIMIT 10查近10次快照 - 新增非空字段必须同步提供默认值填充方案,且填充Job需在变更前完成压测
4.4 Trino查询优化:别迷信“ANSI SQL”,谓词下推失效是隐形杀手
Trino号称兼容ANSI SQL,但某次我们写:
SELECT user_id, COUNT(*) FROM iceberg.ods.user_behavior WHERE dt = '2023-10-01' AND from_unixtime(event_time) > '2023-10-01 12:00:00' GROUP BY user_id;执行计划显示from_unixtime(event_time)未下推到Iceberg Reader,导致全表扫描。原因在于:Iceberg只支持event_time > 1696132800(时间戳)这种原始类型谓词下推,from_unixtime是Trino函数,无法穿透。正确写法:
-- 直接用时间戳比较,让谓词下推生效 WHERE dt = '2023-10-01' AND event_time > 1696132800我们为此开发了SQL审查插件,扫描所有提交的Trino SQL,识别WHERE子句中是否含UDF调用,自动告警并建议改写。上线后,慢查询率下降73%。
4.5 混合架构监控:必须建立“跨链路延迟水位线”,而非单点指标
只监控“Flink任务延迟”毫无意义。某次我们看到Flink延迟稳定在1.2秒,但业务方投诉“实时看板数据滞后15分钟”。排查发现:Kafka Producer端因网络抖动,消息在Producer Buffer中积压了14分钟才发出。因此我们建立三级延迟水位线:
| 水位线层级 | 监控点 | 告警阈值 | 处理动作 |
|---|---|---|---|
| 端到端延迟 | 从日志产生到Trino可查 | >60秒 | 自动触发全链路Trace(用OpenTelemetry) |
| 中间件延迟 | Kafka Producer Buffer + Network + Broker Queue | >30秒 | 降级Producer BatchSize,增大linger.ms |
| 计算延迟 | Flink EventTime - ProcessingTime | >5秒 | 扩容TaskManager,检查反压源 |
这套水位线让我们首次实现“故障10秒内定位到具体环节”,而非过去“查1小时日志猜问题”。
4.6 迭代文档陷阱:拒绝“方案文档”,拥抱“决策日志”
技术团队常犯的错误是写厚厚的技术方案文档,却忽略记录“为什么放弃方案B”。我们在某次选型中对比了Doris和StarRocks,最终选StarRocks,决策日志这样写:
2023-09-15 选型会议纪要
- Doris优势:MySQL协议兼容好,运维简单
- StarRocks优势:物化视图自动刷新、Bitmap索引对UV统计加速3.2倍
- 放弃Doris关键原因:其物化视图不支持实时更新,需手动TRUNCATE+INSERT,无法满足“实时看板秒级刷新”需求;而StarRocks的Async MV在Flink写入后2秒内自动刷新,实测达标。
- 风险备案:StarRocks社区版不支持多租户,已联系商业版供应商评估授权成本。
这份日志比任何架构图都珍贵——它让新成员30分钟内理解决策逻辑,避免重复造轮子。
5. 迭代后的日常:让架构持续呼吸的五项运维纪律
架构迭代不是终点,而是新运维模式的起点。我们总结出五项铁律,确保系统不因迭代而僵化:
5.1 数据血缘必须每日自动校验,而非“上线时检查一次”
我们用Airflow调度Python脚本,每日凌晨2点执行:
- 扫描所有Hive表,提取
CREATE TABLE语句中的TBLPROPERTIES,检查是否含'owner'='data-team' - 调用Hive Metastore Thrift API,获取表的
lastAccessTime,若>90天未被任何SQL引用,则标记为“疑似废弃” - 对所有标记表,发送邮件给Owner,要求72小时内确认是否保留,超时自动归档
此机制上线半年,清理了327张僵尸表,释放存储空间12TB,更重要的是:血缘图谱准确率从68%提升至99.2%。
5.2 所有Flink任务必须配置“优雅退出”,禁止kill -9
曾因运维误操作kill -9一个Flink Job,导致RocksDB State损坏,恢复耗时11小时。现在所有Flink任务启动时,强制添加:
# 启动脚本中加入信号捕获 trap 'echo "Received SIGTERM, triggering savepoint..."; flink savepoint -yid <application_id> hdfs://namenode:8020/flink/savepoints; exit 0' TERM同时配置Kubernetes livenessProbe,当Pod健康检查失败时,先发SIGTERM而非直接kill。这让我们实现了“零状态丢失重启”。
5.3 Iceberg表必须开启“自动过期快照”,防磁盘爆满
Iceberg快照不自动清理,某次因未配置,快照数超2000个,snapshots目录占满NameNode内存。现强制策略:
-- 创建表时指定 TBLPROPERTIES ( 'snapshot-retention-ms'='604800000', -- 7天 'history-retention-ms'='2592000000' -- 30天 )并用Prometheus监控iceberg_table_snapshots_total,当7天增长超50个时告警——这通常意味着有任务未正确关闭。
5.4 Trino必须启用“查询队列分级”,防老板SQL拖垮集群
某次CEO临时要查“近十年所有订单”,一条SELECT * FROM iceberg.ods.orders直接占满全部内存。现配置etc/query-manager.properties:
query-manager.queue-config-file=etc/queue-config.jsonqueue-config.json定义:
{ "queues": [ { "name": "admin", "max-concurrent-queries": 3, "max-queued": 10, "cpu-time-limit": "1h" }, { "name": "user", "max-concurrent-queries": 20, "max-queued": 50, "cpu-time-limit": "10m", "client-tags": ["bi-tool"] } ] }BI工具连接时自动打bi-tool标签,进入user队列,CPU超限自动Kill,绝不影响admin队列。
5.5 每季度执行“架构压力测试”,而非等故障发生
我们模拟极端场景:
- 数据洪峰:用
kafka-producer-perf-test.sh向Topic注入10倍峰值流量,观察Flink反压、Kafka积压、Trino查询超时率 - 节点故障:随机
kubectl delete pod一个Flink TaskManager,验证CheckPoint恢复时间和状态一致性 - 存储故障:
umount一个Iceberg表所在的HDFS DataNode,测试读取容错能力
测试报告不写“系统稳定”,而写“在X场景下,Y指标超出SLO Z%,需优化A配置”。这让我们提前发现37个潜在瓶颈,其中12个在下次迭代中被主动解决。
最后分享一个小技巧:每次架构迭代上线后,我都会在团队Wiki新建一页,标题为“本次迭代的三个没想到”。比如:“没想到Kafka分区数调太高反而降低吞吐”“没想到Trino的谓词下推规则如此严格”“没想到Iceberg快照不清理会撑爆NameNode”。这些“没想到”比任何成功经验都珍贵——它们是系统留给我们的、最诚实的反馈。