跳转至

Ch 50 排障与可观测性实战

面包屑

本书主页Part VIII 治理与复盘 › Ch 50

项目第 3 年 · 成熟与治理期——排障实战


本章你将学到

  • 状态机卡住、ETL 失败、慢查询的排查路径
  • 常见问题索引与排障决策树
  • 跨账号同步的已知边界排障
  • 真实排障 walkthrough(fact_prescription 数据为空,含 CloudWatch 日志摘录与时间线)
  • 性能退化排障(查询延迟 P95 涨 10× 的排查:STL_QUERY/WLM/EXPLAIN/分布倾斜)
  • 预防性排障(健康检查 + 容量预警 + 漂移检测,在事故前发现隐患)

50.1 状态机卡住、ETL 失败、慢查询的排查路径

排障决策树

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TD
 ISSUE[问题报告] --> Q1{问题类型?}

 Q1 -->|状态机卡住|SF[Step Functions 排查]
 Q1 -->|ETL 失败|GLUE[Glue 排查]
 Q1 -->|慢查询|RS[Redshift 排查]
 Q1 -->|数据缺失|DATA[数据排查]
 Q1 -->|AI 回答错误|AI[Agentic BI 排查]

 SF --> SF1[查 SF 执行历史<br/>哪一步 Failed/TimedOut]
 SF1 --> SF2[查对应 Lambda/Glue 日志]
 SF2 --> SF3[定位根因→修复→重跑]

 GLUE --> G1[查 CloudWatch Logs<br/>错误堆栈]
 G1 --> G2{错误类型}
 G2 -->|OOM|G3[增加 DPU/优化分区]
 G2 -->|超时|G4[增加超时/优化逻辑]
 G2 -->|数据错误|G5[查源数据/质量校验]

 RS --> R1[查 STL_QUERY<br/>慢查询 SQL]
 R1 --> R2[查 STL_EXPLAIN<br/>执行计划]
 R2 --> R3{瓶颈}
 R3 -->|全表扫描|R4[优化分布键/排序键]
 R3 -->|join 慢|R5[优化 join 顺序/方式]
 R3 -->|锁等待|R6[查锁冲突→终止长事务]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class ISSUE,SF,GLUE,RS,DATA,AI,SF1,SF2,SF3,G1,G3,G4,G5,R1,R2,R4,R5,R6 bpProcess
class Q1,G2,R3 bpDecision
linkStyle default stroke:#697077,stroke-width:2px

图 50-1 排障决策树


50.2 常见问题索引与排障决策树

高频问题速查表

问题现象 首先检查 根因可能性 修复方向
状态机停在某步不走 SF 执行历史 + Lambda 日志 Lambda 超时/权限不足/配置缺失 增加超时/修复 IAM/补配置
Glue Job OOM CloudWatch 内存指标 数据量过大/分区不合理 增加 DPU/优化分区/减少 shuffle
Glue Job 超时 运行时长 vs 配置超时 数据量过大/死循环/源系统慢 增加超时/优化逻辑/检查源系统
Redshift 查询慢 STL_QUERY + STL_EXPLAIN 全表扫描/join 顺序差/锁等待 优化分布键排序键/重写 SQL/终止锁
数据行数不对 行数对账日志 源数据变了/过滤条件错/脱敏删除 查源数据/检查配置/查脱敏规则
AI 生成 SQL 错误 Langfuse 链路追踪 术语歧义/检索不准/护栏遗漏 检查语义资产/优化检索/补充护栏

表 50-1 高频问题速查表

数据缺失的排查流程

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
 MISS[数据缺失] --> Q1{哪个环节缺?}
 Q1 -->|Landing 没有|SRC[查源系统<br/>数据是否推送/信号文件是否到达]
 Q1 -->|Raw 没有|LANDING[查 Landing→Raw 步骤<br/>标准化是否失败]
 Q1 -->|Enriched 没有|RAW[查 Raw→Enriched 步骤<br/>质量校验是否阻断]
 Q1 -->|Redshift 没有|ENR[查 Enriched→Redshift 步骤<br/>COPY 是否失败]
 Q1 -->|Redshift 有但查不到|RLS[查 RLS 策略<br/>是否被行级过滤]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class MISS,SRC,LANDING,RAW,ENR,RLS bpProcess
class Q1 bpDecision
linkStyle default stroke:#697077,stroke-width:2px

图 50-2 数据缺失的排查流程

引申

数据缺失排查的关键是"按链路逐层定位"——从 Landing 到 Raw 到 Enriched 到 Redshift,逐层检查行数。批次标识让这个定位极其精确——查"批次 20260618-001500 的数据在哪一层丢了"。


50.3 跨账号同步的已知边界排障

回顾 Ch 34 的六个已知边界,跨账号同步场景的排障:

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TD
 SYNC[跨账号同步问题] --> Q1{问题现象}

 Q1 -->|目标表为空|B1[① truncate→COPY 非原子<br/>COPY 失败但已 truncate]
 Q1 -->|数据不完整|B2[② 无 MANIFEST<br/>分片丢失]
 Q1 -->|历史数据被误删|B3[③ 清理删根前缀<br/>误删整表数据]
 Q1 -->|外键冲突|B4[④ 无 FK 排序<br/>加载顺序错误]
 Q1 -->|同步很慢|B5[⑤ 单线程调度<br/>并行度不足]
 Q1 -->|配置不生效|B6["⑥ bool 覆盖陷阱<br/>“false” 被当真值"]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class SYNC bpProcess
class Q1 bpDecision
class B1,B2,B3,B4,B5,B6 bpError
linkStyle default stroke:#697077,stroke-width:2px

图 50-3 跨账号同步的已知边界排障

已知边界 排障方式 临时缓解
① 表为空 查 COPY 日志是否失败 从 S3 重新 COPY
② 数据不完整 对比源/目标行数 启用 MANIFEST 重跑
③ 历史误删 查 S3 版本恢复 从源重新同步
④ 外键冲突 查 FK 依赖顺序 手动指定加载顺序
⑤ 同步慢 查调度器日志 拆分大表为小批次
⑥ 配置不生效 检查配置值类型 强制类型转换

表 50-2 跨账号同步的已知边界排障

Trade-off

已知边界的排障策略是"先识别是否命中已知边界"——80% 的跨账号同步问题在这六个边界内。识别后按临时缓解措施处理,长期通过改进设计消除边界(见 Ch 34)。


引申:双联章说明

本章与 Ch 49 日志、监控、审计与告警 合构"监控排障双联章"——Ch 49 讲"怎么看见",本章讲"怎么排查"。建议两章连读。


50.4 排障演练:fact_prescription 数据突然为空

理论讲多少都不如走一遍真实案例。这是数据平台里很有代表性的一个排障过程——某天早上,一条 P1 告警弹出来:"ma 域 fact_prescription 行数较基线下降 100%"。下面是当时完整的时间线和排查路径。

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8'}}}%%
timeline
    title fact_prescription 行数为空排障时间线
    section 08:00 告警
        SNS 告警 : fact_prescription 行数=0
    section 08:05-08:20 排查
        08:05 : 查 CloudWatch Logs,ETL 任务 status=success
        08:10 : 矛盾——任务成功但数据为空
        08:15 : 查源系统,确认源表有 12450 行
        08:20 : 查 ETL 日志,过滤条件 updated_at 大于 2026-06-17
    section 08:25-08:30 根因与修复
        08:25 : 根因——水位被错误覆盖为未来时间
        08:30 : 修复——回退水位,手动重跑
    section 08:50 验证
        08:50 : 验证——行数对账通过,事件关闭

图 50-4 排障演练:fact_prescription 数据突然为空

第一步:确认告警(08:00)。SNS 告警写的是 fact_prescription 行数较 7 天基线下降 100%。先确认是不是误报——打开 CloudWatch Logs Insights:

# CloudWatch Logs Insights 查询
filter entity="fact_prescription" and status="success"
| sort @timestamp desc
| limit 5
# 结果:最近一次任务 status=success,row_count=0

第二步:发现矛盾(08:10)。任务 status=success,但 row_count=0——这就是 Ch 49 提到的"ETL 成功但数据为空"。任务没挂,是"跑了但没拉到数据"。

第三步:查源系统(08:15)。连源库跑 SELECT COUNT(*) FROM prescription WHERE updated_at > '2026-06-17'——返回 12450 行,源数据没毛病。问题出在 ETL 侧。

第四步:定位根因(08:20)。查 ETL 日志的水位读取记录(Ch 14 水位机制):

# 从 DynamoDB 水位表查到的值
last_watermark = "2026-06-19T00:00:00"   # 核心意图:水位是未来时间!updated_at > 未来 = 0 行

水位被错写成了未来时间(6-19,实际当天是 6-18)。前一天有人手动重跑时误传了参数,把水位覆盖成了"明天"。于是 WHERE updated_at > '2026-06-19' 返回 0 行。

第五步:修复(08:30)。回退水位到正确值(2026-06-17),手动触发重跑。迟到数据回溯窗口(Ch 14)确保不漏数据。

第六步:复盘(08:50)。行数对账通过,事件关闭。改进措施:水位更新环节加了"不允许写未来时间"的校验。这也是 Ch 52 里"工程诚实"的具体落脚:把排障中吃到的教训固化为防护栏杆,下次同样操作会被拦住。

引申

这个案例的价值在于展示了"数据平台排障 ≠ 应用排障"——应用排障看"是否报错",数据平台排障要看"是否成功但结果异常"。如果只监控任务 status,这类"成功但为空"的问题要到业务方用数据时才被发现,滞后数天。行数对账 + 基线告警让它在 8 点就被发现——这是数据可观测性的价值。


50.5 性能退化排障:查询延迟从 3 秒涨到 30 秒

排障不只是"挂了"才算故障,"慢了"也是。这个案例很有代表性:用户反馈 Agentic BI 查询 P95 延迟从 3 秒涨到了 30 秒,但结果是对的。这种排查完全不走功能故障那条路:

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TD
 SLOW[用户反馈查询变慢<br/>P95 3s→30s] --> Q1{Redshift 侧?}
 Q1 -->|查 STL_QUERY|QSLOW[查询执行时间变长]
 QSLOW --> Q2{排队还是执行慢?}
 Q2 -->|查 STL_WLM_QUEUE|QUEUE[WLM 队列等待 20s]
 QUEUE --> Q3{为什么排队?}
 Q3 -->|查并发查询|BIGQUERY[一个全表扫描大查询占用队列]
 Q4 -->|查 STL_EXPLAIN|SCAN[大查询未走 sortkey<br/>全表扫描 100TB]
 BIGQUERY --> Q4
 SCAN --> FIX[修复:为大查询加时间过滤<br/>+ 调整 WLM 并发]
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class SLOW,QSLOW,QUEUE,BIGQUERY,SCAN bpProcess
class Q1,Q2,Q3,Q4 bpDecision
class FIX bpSuccess
linkStyle default stroke:#697077,stroke-width:2px

图 50-5 性能退化排障:查询延迟从 3 秒涨到 30 秒

排查步骤 用的工具/表 发现
确认慢在哪 Redshift STL_QUERY 查询执行时间从 2s 涨到 25s
区分排队 vs 执行 STL_WLM_QUEUE 队列等待 20s,执行 5s——是排队不是执行慢
找占用队列的查询 STL_QUERY + svl_qlog 一个未带时间过滤的 SELECT * FROM fact_prescription 全表扫描
确认未走索引 STL_EXPLAIN Seq Scan 而非 Sort Scan——未命中 sortkey
检查分布倾斜 SVL_QUERY_REPORT 分布键倾斜导致单节点瓶颈

表 50-3 性能退化排障:查询延迟从 3 秒涨到 30 秒

根因:某个分析师(非 AI)直接连 Redshift 跑了一个无过滤的大查询,占满了 WLM 队列,导致 Agentic BI 的查询排队等待。修复:① 为大查询强制加时间过滤(DaaS 层 Ch 37 的 LIMIT + 超时);② 调整 WLM 队列优先级,AI 查询走独立高优先级队列。

Trade-off

性能退化排障的难点在于"没有明确错误"——任务没失败、数据没错,只是慢了。如果没有 P95 延迟基线监控,退化可能累积数周才被业务感知,届时已难以定位是哪次变更引入的。平台的做法是:P95 延迟与 7 天基线对比,偏差超 2× 即告警——在业务感知前就触发排查。


50.6 预防性排障:在事故前发现问题

最好的排障是不让故障发生。预防性排障靠"主动健康检查 + 容量预警 + 漂移检测",在问题还没炸出来之前就看到隐患:

预防手段 做法 发现的典型隐患
数据新鲜度 SLA 监控 每个实体的最新批次时间与 SLA 对比,临近违约即告警 上游断流、调度器卡住
容量预警 监控 S3 存储增长、Redshift 磁盘使用、DynamoDB 容量 存储即将耗尽、需要扩容
Drift Detection 周级 terraform plan -detailed-exitcode 检测基础设施漂移 有人手动改了 AWS 资源配置
数据质量趋势 质检通过率趋势监控,下降即告警 上游数据质量退化
依赖健康检查 监控源系统可达性、API 可用性 源系统即将下线

表 50-4 预防性排障:在事故前发现问题

# 示意:预防性健康检查脚本(定时运行)
def health_check():
    issues = []
    # ① 数据新鲜度:各域最新批次是否在 SLA 内
    for entity in all_entities:
        freshness = hours_since_last_batch(entity)
        if freshness > entity.sla_hours * 0.8:        # 核心意图:达 80% SLA 即预警,不等违约
            issues.append(f"{entity.name} 新鲜度 {freshness}h 接近 SLA {entity.sla_hours}h")
    # ② 容量:S3 存储增长趋势
    if s3_growth_rate() > threshold:
        issues.append(f"S3 存储月增 {s3_growth_rate()}TB,超阈值")
    # ③ 漂移:Terraform 检测
    if terraform_plan_has_diff():
        issues.append("检测到基础设施漂移,有资源被手动修改")
    notify(issues) if issues else log("all healthy")

引申

预防性排障的哲学是"把排障从被动变为主动"——与其等业务方反馈"数据不对/查询慢",不如让平台自己先发现。这需要投入建设监控基线(7 天/30 天基线、SLA 定义、容量阈值),初期看似"多做事",但每预防一次事故省下的排障时间远超建设成本。这是 Ch 52 "可观测优先"原则的落地。


本章小结

  • 排障决策树:按问题类型(状态机/ETL/慢查询/数据缺失/AI 错误) 分支排查
  • 高频问题速查表:6 类高频问题的首先检查点、根因可能性、修复方向
  • 数据缺失按链路逐层定位:Landing→Raw→Enriched→Redshift,用批次标识精确定位丢失环节
  • 跨账号同步排障:先识别是否命中 6 个已知边界,命中则按临时缓解处理,长期通过设计改进消除
  • 真实排障 walkthrough:fact_prescription 数据为空——任务成功但行数 0,根因是水位被错误覆盖为未来时间,修复+复盘固化为校验护栏
  • 性能退化排障:P95 延迟 3s→30s,区分排队 vs 执行(STL_WLM_QUEUE),定位全表扫描大查询占满队列,修复靠时间过滤+WLM 优先级
  • 预防性排障:健康检查脚本(新鲜度 SLA/容量/Drift Detection/质量趋势/依赖可达性)——把排障从被动变主动

下一章

Ch 51 价值度量与案例复盘 —— 排障讲完了,接下来看平台创造了什么价值——量化度量与案例复盘。

评论