Trino 从 4xx 版本起提供catalog.management=dynamic:catalog 不再写死在配置文件里,可以用 SQL DDLCREATE CATALOG/DROP CATALOG运行时增删。对数据平台这是刚需——用户注册一个新的湖 catalog,应该立刻能查,而不是等运维改 configmap、重启 Trino。
但这个特性官方仍标注 experimental,并且有个极易被忽略的属性:默认的 catalog store 是 memory 的,不落盘。「我的数据空间」(datastudiohappy.cn)在生产里被它上了一课,这篇讲事故本身,和事后收敛出的「三路对账」自愈设计。
一、事故:OOMKilled 之后,catalog 无声蒸发
平台后端在启动时会把注册表里的 catalog 全量创建进 Trino,之后靠事件增量维护——注册就 CREATE,删除就 DROP。看起来闭环了,直到某天 Trino 容器因内存超限被 K8sOOMKilled自动重启:
- memory catalog store 清零,所有动态 catalog 消失;
- 但后端没有重启,启动时的全量创建不会再来一次;事件侧也安静——没有任何 catalog 被注册或删除;
- 于是缺口一开就是几个小时:所有经 Trino 的查询报
Catalog 'xxx' does not exist,直到下一次后端重启才恢复。
根因不是哪行代码写错,是架构上少了一路:状态的权威在平台数据库,副本在 Trino 内存里,而「副本丢了」这件事,启动时机 + 事件驱动两路都感知不到。
二、修法:三路触发,一个对账函数
把「创建 catalog」的逻辑收敛成一个幂等的全量对账:拿权威(平台注册表)与现实(SHOW CATALOGS)做差集,缺的建、多的摘。三个触发时机共用它:
| 触发路 | 兜住什么 |
|---|---|
| catalog 注册/编辑/删除事件(经 MQ,事务性 outbox 投递) | 正常增量,秒级生效 |
| 后端应用就绪时全量对账 | 后端自己重启、以及消息漏投 |
| 周期全量对账(默认 60s) | Trino 自身重启丢 memory store——事故缺的就是这路 |
对账的三条纪律,比触发时机更重要:
幂等且不打扰:已在目标集合里的 catalog 不重建——重建会打断正在跑的查询。空转一轮的成本只是一次SHOW CATALOGS,60 秒一次可以忽略。
单点失败不拖垮整轮:某个 catalog 创建失败(属性错、多实例 CREATE 撞车)只记 warn 跳过,其余照常对齐,下一轮自然重试。对账循环里一个 throw 中断全场,等于把一个 catalog 的问题放大成全部 catalog 的问题。
日志安静:周期空转不刷 info。60 秒一条「对账完成,无变化」,一天 1440 条噪音,真出事时反而没人看日志了。
实测:手动删掉 Trino pod,80 秒内 5 个动态 catalog 全部自动补回,期间无人工介入。
三、顺带治本:OOMKilled 本身也要修
自愈解决「重启后恢复」,但 ~7 小时一次的 OOMKilled 循环本身是个内存配置问题:-Xmx几乎贴着容器 memory limit,JVM 的堆外开销(metaspace、线程栈、direct buffer、JIT)没有余量,堆越满、native 越挤,最终被 cgroup 杀掉。把 Xmx 从 limit 的 2/3 档再往下压一档,给 native 留足空间,循环消失。JVM 容器化的老规矩:limit 减去 Xmx 的差值才是你真正给 native 的预算。
对用户而言,这一切的意义是:新注册的 catalog 即刻可查,跨 catalog 联邦查询始终可用:
四、三句话总结
- 凡是「运行时注入到别的组件里」的状态,都要回答三个问题:权威在哪、副本丢了怎么发现、重建会不会打扰在跑的活——事件驱动只回答第一问;
- 启动对账 + 事件增量只兜「自己重启」,对方重启只有周期对账兜得住,这一路最容易被省略;
- 对账要幂等、单点失败降级、空转安静——以及别忘了把触发对账的那个 OOM 本身修掉。
这套多引擎查询(Spark/Trino/Doris 自动选路)是「我的数据空间」的一部分——一套可私有化部署的数据平台,支持 OEM 合作。产品介绍:https://datastudiohappy.cn/。