.memsearch\PINNED.md 铁律段)。另外 3 条属口头约定、没有落到文件。所以这一页只写这 10 条,一条不编、一条不凑。一、先说人话:红线是事故记录,不是公司规定
红线这个词容易误会。它听着像"公司规定",其实是事故记录的另一种写法。
规定是商量出来的,写的时候客客气气;红线是出完事补的,写的时候只剩一句"不许"。
所以读红线有个技巧:别读它禁了什么,读它在防什么。每一条的背面都写着一个具体的动作、一个具体的时间、一个具体坏掉的东西。
你把这层翻过来,它就不再是"限制",而是"有人替你试过了,此路不通"。
这一页的读法固定四行:原话、人话、为什么有这条、踩了会怎样。原话是能拿去跟人争的那一句,可以不好懂;人话是你自己记着用的那一句。
二、墙上这 10 条,一条一条看
先看图。十张纸条钉在墙上,其中三张是红的——红的那三张,就是第三部分要讲的三个事故现场。
它们的共同点后面会讲:没有一个报了错。这也是红线最不讲道理的地方——它防的常常不是崩溃,是"一切正常"。
编号沿用墙上原来的顺序,不是按严重程度排的。第 1 条最常被顺手违反,第 8 条最贵,第 7 条最会骗人——它骗人的方式正好是"一切正常"。
每条都按四行写。原话可以不好懂,但它是唯一能拿去跟人、跟审批、跟复盘会争的一句;人话是你自己记着用的。
原话:生产 SQL 禁 CREATE/DROP,禁读写 _sit/_uat/temp_db/default(import_db 只读)。
人话:生产库是给业务出货的,不是练习场。建表删表这种改结构的动作,走建表平台,别在 SQL 里手写。
_sit 是测试库、_uat 是验收库,temp_db 和 default 是谁都能写的公共堆放区。
这四个名字出现在生产连接里,就说明你把测试的东西接到了出货管线上。import_db 只准看,不准写。
为什么有这条:改结构是不可逆的,删错一次没有 Ctrl+Z;而测试库和生产库在同一个 Kyuubi 入口里,库名只差一个后缀,肉眼最容易看串。
踩了会怎样:脚本里一个 DROP 手滑,删掉的是生产表;反过来,数据全写进了 _sit,生产那张表一整天空着,页面数字停在昨天,没有任何人报错。
原话:Hudi 必须管理表,建表 SQL 不能带 Location;字段不能以 _ 开头;表和字段必须有备注。
人话:管理表 = 表和它的文件归同一个管家管,删表的时候文件跟着走。
带上 Location,等于把文件寄存在别人家:表是表、文件是文件,两本账。
字段不能以 _ 开头,因为下划线开头是框架留给自己的位置。备注不是客套话,是货架上的标签——没标签的货,没人敢动。
为什么有这条:湖里上千张表,靠人记文件在哪是不可能的。表和文件一旦分家,后面三件事全对不上:删表、清文件、对账。
踩了会怎样:删表只删了名册,文件还躺在盘上占地方,账上"已经删了"、盘上"还在";没备注的表等于匿名表,半年后没人知道那列是什么,也没人敢删。
拦在哪:建表平台 ddls 会逐条报——Location 检查、表备注、字段备注、字段名称检查,一条不过就不许发布。
原话:Kyuubi 必须带 subdomain tag,不许用 SQL 注释或 SET 冒充;SQL 里不 SET 引擎参数。
人话:Kyuubi 是所有人共用的一个总闸,谁连上来都要在门牌上写清自己是谁(subdomain tag)。
用注释假装、用 SET 冒充,等于把门牌摘了:出了事查不到人。
引擎参数(内存、并行度这些)是平台统一配的,个人不该在 SQL 里 SET。自己 SET 一下,等于偷偷把总闸的水压调高——你这一单快了,整栋楼的水都小了。
为什么有这条:一套 Kyuubi 后面挂的是共享的 Spark 资源。一个人 SET 大并行度,全项目一起排队;要定位是谁的会话,靠的就是那个 tag。
踩了会怎样:跑挂了不知道是谁的,跑慢了也不知道是谁把资源吃掉的。查不到人,最后只能整组一起背。
原话:同一张表不能拆并行分支——INSERT OVERWRITE 会互相覆盖。
人话:INSERT OVERWRITE 是"先清空、再写满":把这格子的货全铲出去,再倒新的进来。
同一张表拆两条分支一起跑,就是两把铲子同时伸进同一格:各铲各的、各倒各的,谁后倒完算谁的。
先倒的那批,直接没了。
为什么有这条:并行是为了快,但快的前提是两边写的地方不重叠。同一张表拆分支,省下来的时间是拿数据换的。
踩了会怎样:两个任务状态都是成功,一条错都没有,表里却少了一半行或者一半分区。这种错最难查,因为没有任何东西报错——见事故二。
原话:Doris 不用 Auto Bucket;副本 3;亿级表禁 select *。
人话:Auto Bucket 是让系统替你估"分几个桶"。桶估多了,一张表被切成一大堆 tablet,小文件一片,导入越跑越慢。
副本 3 是同一份货存三个仓库,坏一个还能卖。亿级表 select * 是把整仓货搬到门口,只为看其中两箱。
为什么有这条:Doris 是出货层,所有人都在上面查。桶分错、副本少、乱扫全表,坏的从来不是一个人的查询,是所有人的响应时间。
规范原话就一句:Auto Bucket 会造成 tablet 数量过多、大量小文件。
踩了会怎样:Auto Bucket 平时看不出来,等数据涨上来,导入开始一天比一天慢。副本不是 3 的表平时也看不出来;等一台机器挂了,页面不是慢,是没数。
select * 打到亿级表上,会把整个集群的查询队列堵住。
原话:23:00 后停 Hudi 写与查,不跨日续跑。
- 真名
Hudi(湖里的表)——写和查两个动作都归这条管
人话:湖里的数据按天分格子,格子的名字来自分区里的日期。
23 点以后还在写,很容易跨过零点;一跨零点再续跑,昨天的数据就倒进了今天的格子,或者今天的任务先占了明天的格子。
日子这东西,写错一天就是一辈子。
为什么有这条:零点前后正是分区换日的时候,也是最容易把"哪天"写错的时候。宁可停一小时,不要错一天。
踩了会怎样:分区错一天,当天的数就是错的,而且不报错。时间字段看着完全正常,只是对不上任何一天的账——对账那天才发现,已经错了半个月。
原话:依赖检查只认"上次成功",不认"今天跑没跑"。
真名:被检查的那条上游是 ads_supp_di_daily_update_limited(编码 182733687384832,定时 33028)。
原话后面还有半句现场记录:所以 08:00:05 就放行,上游 10-03 下线后仍天天搬旧数据。
人话:调度看上游,看到"上次成功"就放行,它不问这次是不是今天成功的。
上游 10 月 3 号之后再没跑过,那条"上次成功"的印章还盖在 10 月 3 日——灯一直绿着。
这条红线的重点不是"调度有 bug",是你得自己确认今天这次到底跑没跑。
为什么有这条:这条是事故之后补进去的,见事故一。它防的不是机器出错,是人把绿灯当证据。
踩了会怎样:42 家供应商天天拿到 10 月 3 日之前的旧数,日志一条错都没有。所有人都以为今天是正常的。
原话:大表分区删除:16–32 万分区用普通 DROP TABLE ≈ 23–26 分钟;PURGE 不可恢复。
人话:删一张 16–32 万分区的大表,正常删也要二十多分钟;这二十多分钟它不是卡住,是它真的在一个一个地干活。
PURGE 是"连回收站一起删":既没更快,又把后悔的余地一起删了。
为什么有这条:删数据最容易被当成"一秒钟的事",于是很少有人在按下去之前先问一句"删错了怎么办"。这条红线就是替你先问这一句。
踩了会怎样:见事故三。删的时候不疼,第二天要用数据的时候才疼——那时候没有后悔药。
原话:发布生产前必须 GET 回读整份定义,确认没有 _sit 残留——正式定义不得引用 SIT 运行对象。
人话:发布完要多做一步:把系统里真正生效的那份定义读回来,通读一遍,重点看有没有 _sit 字样。
注意读回来的不是你手里那份稿子——稿子是你以为的,回读才是它现在真正在跑的。
为什么有这条:复制粘贴改库名是最常见的开发动作,改漏一处,稿子里看不出来,因为你看的永远是稿子。
踩了会怎样:生产任务读着测试库的表。测试库一停,生产就停;测试库一改,生产跟着变。
而生产这边看不到任何异常——生产的定义本身"没有错",错在它引用了谁。
原话:临时管理员权限 1 小时,理由要写具体场景,不能只写"下线"。
- 真名
- 项目级临时管理员权限,一次 1 小时
人话:临时管理员权限是借来的钥匙,一次只借一小时。
申请理由要写清"哪个项目、为哪件事、改什么",比如"库存趋势场景 5 月 6 月数据异常调整重刷"。
只写"下线"两个字等于没写:审批的人不知道你要动什么,事后也没人知道这一小时里发生了什么。
为什么有这条:管理员权限能改的东西太多。写不清场景的申请,既是给自己留隐患,也是给别人留口子。
踩了会怎样:轻的理由含糊被卡住,活干不完;重的批下来一小时到期,改到一半的工作流停在半路,谁也不知道它缺了哪一步。
三、三个事故现场,加一条迟早要出的事
下面三个都是真事,表名、编码、日期、分钟数照记录抄的,一个数没改。它们的共同点很奇怪:没有一个是"报错"引起的。
这一点值得单独说。近 8 天里,这套体系跑出 45,571 次成功、296 次失败、529 次被杀,失败率 0.65%。
失败能看见,所以失败好办。而这三个事故全都落在"成功"那 45,571 次里——它们长得跟正常任务一模一样。
上游任务 ads_supp_di_daily_update_limited(编码 182733687384832,定时 33028)在 2026-10-03 10:46:50 下线。之后没有恢复。
- 10-03 10:46:50:上游下线。它的"最后一次成功"就停在这一刻。
- 之后每天 08:00:00:42 家供应商的下游任务照常调度,一分钟都没迟到。
- 08:00:05:依赖检查放行——只认"上次成功",而那个印章还盖在 10 月 3 日。
- 每天跑完:日志干净,零失败、零重试、零告警。
结果:42 家供应商每天搬的都是 10 月 3 日之前的旧数据,页面每天都能打开,数字每天都"对"。
教训:绿灯不等于对。绿灯只回答"上次成功过吗",不回答"今天跑的是哪天的数"。
一张大表跑得慢,有人把它拆成两条并行分支,两条分支写同一张表,都用 INSERT OVERWRITE。
- 分支 A:先清空这个分区,再往里写自己那批数。
- 分支 B:同一时间,也在清空同一个分区。
- 谁后写完:谁的数据留下,先写完的那批被后面的清空动作直接抹掉。
- 两个任务:状态都是成功,谁也没报错。
教训:分区是格子,只能一个人倒。要并行就并行到不同的格子里去;同一格上并行,省下的时间是拿数据换的。
PURGE 之后没有后悔药
大表 16–32 万分区。这种表正常删一遍要多久,实测了四个;同一批里还有一张,试过 PURGE:
| 对象 | 方式 | 用时 |
|---|---|---|
_p1 | 普通 DROP TABLE | 23.8 分钟 |
_p2 | 普通 DROP TABLE | 25.7 分钟 |
_p4 | 普通 DROP TABLE | 24.8 分钟 |
_p8 | 普通 DROP TABLE | 22.9 分钟 |
_p7 | PURGE | 48 分钟,不可恢复 |
23–26 分钟不是卡了,是它真的在干活。而 PURGE 那次花掉 48 分钟——时间没省下,后悔的余地先没了。
这不是"删得干净",这是"删得没法回头"。
教训:删数据之前先问一句"要不要能恢复"。答"要",就走普通删,老老实实等那二十多分钟。
单项目 756 个任务压在 10 台 worker 上。其中 10.160.10.26 一台扛 218 个,排队 3.8 分钟;其余 9 台基本空闲。
这不是故障,今天还跑得动。但一台机器扛了全项目将近三成的任务(218 / 756),它一咳嗽,全项目一起等。
负载不均不算错,算欠账——欠账不还会加息。
三个事故让墙上多了三张红条子;第四件事还没出事,只是账还没到期。这就是那面墙的真相:纸条不是一次贴完的,是一次一次贴上去的。
四、出事之后,先别改 SQL
页面数字不对,第一反应通常是"去改 SQL"。这是最贵的第一反应,因为十次里有六次根本不是 SQL 的事。
事故一里那条 SQL 一个字都没错,错在上游没人跑;事故二里两条 SQL 也都对,错在它们不该同时跑。
这时候去改 SQL,等于把对的改成错的,然后接着查。
- 先确认是"没跑"还是"跑错":看实例,不看页面。实例什么时候开始、什么时候结束、状态是什么、是不是今天这一次。这一步就能排掉一半问题。
- 再确认是"数据错"还是"同步错":湖(Hudi)里有没有数?仓(Doris)里有没有数?两边一比就知道断在哪一段——湖里就没有,往上游和入湖查;湖里有、仓里没有,是同步那一段;两边都有,才轮到 SQL 口径。
- 最后才改 SQL:前两步都排除了,再动代码。动之前先把现在生效的定义读回来存一份,改坏了还能对照。
这三步的顺序不是礼貌,是省钱。跳过第一步直接改 SQL,最常见的结局是改三天,最后发现上游压根没跑。
而那个结论,八十秒就能看出来。
五、一句话记住
换个说法:红线上写的是"别做",底下压着的是别人的一天、一周、或者一整张表。
你照着做,等于这些账已经有人替你付过了。
下次你在生产上准备敲下那条 DROP、或者准备把一张表拆成两条并行分支、或者看见一片绿灯准备收工的时候,停三秒,想想墙上那三张红的。