Files
insar-management-system-v2/docs/ISCE2_PRODUCTION_RELIABILITY_HARDENING_DESIGN_20260424.md
T

11 KiB
Raw Blame History

ISCE2 生产可靠性加固设计 2026-04-24

1. 背景

当前 ISCE2 生产链路已经具备:

  • 统一的 WSL runtime 入口
  • 共享 conda 环境 insar_wsl_v1
  • 结果发布到 D:\production_results
  • 统一 catalog / 自检 / 运维面板

但这条链路仍存在一个核心问题:

系统现在更像是“子进程执行器”,还不是“结果可靠性交付器”。

也就是说,当前成功判定主要依赖:

  • WSL runner 返回码是否为 0
  • 输出目录是否存在
  • 自动发布阶段是否“尝试过”

这会导致几类假成功:

  1. ISCE2 子进程返回 0,但没有形成可发布的主产物,任务仍显示完成。
  2. 历史运行只留下目录和 .dinsar_run.json,但主产物已损坏或缺失,“只跑未完成” 仍会跳过。
  3. 运维自检用的是一套路径规则,真实运行用的是另一套路径规则,面板和实际行为可能不一致。

本设计的目标不是重写 ISCE2 算法链,而是把 ISCE2 生产从“命令执行成功”收紧到“结果可验收、可发布、可重跑、可运维”。

2. 本轮范围

本轮只做可靠性加固,不做算法重写。

纳入本轮

  • ISCE2 原生产结果的验收契约
  • “只跑未完成” 判定收紧
  • 自动发布阶段收紧
  • 健康检查与真实运行入口对齐
  • 文档、自检、日志字段同步

暂不纳入本轮

  • stripmap/SNAPHU/PICKLE 恢复链的算法级改写
  • 与 ENVI/IDL 流水线逐步逐项数值对比
  • Gamma 生产实现细节
  • MintPy / 时序生产链的算法可靠性评估

3. 当前问题拆解

3.1 成功判定过宽

当前 backend/app/dinsar_engines/isce2_engine.py 中,单对任务成功主要由 rc == 0 决定。

现状后果:

  • 只要 runner 没报错,就写入 .dinsar_run.json
  • 只要 output_dir 被加入 output_dirs,后续任务层就进入自动发布
  • 自动发布即使 processed == 0,任务最终仍可标记为 COMPLETED

这不符合“生产完成”的系统定义。

3.2 未完成重跑判定过宽

当前 _has_completed_task_result() 只看:

  • 历史 run_dir
  • .dinsar_run.json
  • output_dir 目录存在

它不看:

  • 主产物是否存在
  • 主产物是否可读
  • 该结果是否满足发布器识别规则

因此历史半成品会被误判为“已完成”。

3.3 健康检查与真实运行入口不一致

当前存在两套 stripmapApp 路径逻辑:

  • 健康检查依赖 ISCE2_STRIPMAP_APP
  • 真正运行时在 WSL 内通过 import isce 动态定位

这会导致:

  • 面板报错但实际能跑
  • 面板通过但实际解释器/包位置已经漂移

3.4 手工恢复链缺少显式边界

当前 run_lt1_dinsar_pipeline.py 通过复制 PICKLE 中文件并改写 XML 来衔接:

  • filter -> filter_high_band
  • unwrap -> ionosphere

这条链路短期可用,但它对 ISCE2 内部文件结构有隐含依赖。

本轮不重写它,但必须把它从“隐式成功”改成“显式验收”。

4. 目标状态

ISCE2 生产链路调整后,系统对“完成”的定义变为:

  1. WSL runner 执行完成。
  2. 原生产物通过本地验收。
  3. 验收通过的产物可被统一发布器识别。
  4. 自动发布成功写入标准包。
  5. 自检面板能看到 runtime、native output、publish package 三层状态一致。

换句话说:

  • rc == 0 只表示“执行成功”
  • “任务完成” 必须是“执行成功 + 产物验收成功 + 发布链成功”

5. 具体改造方案

5.1 新增 ISCE2 原生产物验收层

新增一个独立验收模块,建议位置:

  • backend/app/services/isce2_result_validator.py

职责:

  • 不参与计算
  • 不参与数据库写入
  • 只负责判断一个 output_dir 是否是“可发布、可交付”的 ISCE2 原生结果

输入

  • output_dir
  • task_alias
  • engine_code
  • profile_code
  • run_key

核心检查项

必选检查:

  • output_dir 目录存在
  • 至少存在一个符合发布规则的主位移产物
  • 主产物文件大小大于 0
  • 主产物能被 GDAL 打开
  • 若存在配对的相干产品,则记录为辅助资产

建议主产物识别规则直接复用发布器当前的规则,避免两套规则分叉:

  • *_disp.tif
  • 可选 *_coh.tif

输出结构

返回统一 acceptance_result

{
  "accepted": true,
  "primary_file": "D:/.../Task_xxx_disp.tif",
  "asset_files": [
    "D:/.../Task_xxx_disp.tif",
    "D:/.../Task_xxx_coh.tif"
  ],
  "issues": [],
  "metrics": {
    "primary_exists": true,
    "primary_readable": true,
    "asset_count": 2
  }
}

运行时行为

isce2_engine.run() 中:

  • rc != 0 直接失败
  • rc == 0 后必须执行验收
  • 只有 accepted == true 才计入 pairs_processed
  • 只有验收通过的 output_dir 才进入 output_dirs

这样“runner 成功但没产物”的情况会被收口为失败。

5.2 收紧 .dinsar_run.json 语义

当前 .dinsar_run.json 只是运行元数据,需要补成“运行 + 验收”的统一侧写。

本轮不改数据库表结构,先改 sidecar 内容。

新增字段建议:

{
  "run_key": "run_xxx",
  "engine_code": "isce2",
  "profile_code": "lt1_stripmap",
  "runtime_id": "isce2_runtime_v1",
  "output_dir": "D:/.../native",
  "acceptance": {
    "accepted": true,
    "accepted_at": "2026-04-24T15:00:00Z",
    "primary_file": "D:/.../Task_xxx_disp.tif",
    "asset_files": [
      "D:/.../Task_xxx_disp.tif",
      "D:/.../Task_xxx_coh.tif"
    ],
    "issues": [],
    "metrics": {
      "primary_exists": true,
      "primary_readable": true,
      "asset_count": 2
    }
  }
}

原则:

  • .dinsar_run.json 不再只表示“跑过”
  • 它要能表达“验收通过 / 未通过”

5.3 重写“只跑未完成”判定

_has_completed_task_result() 调整为 _has_accepted_task_result()

新的跳过条件:

  1. 历史 run 的 .dinsar_run.json 存在
  2. engine_code / profile_code 匹配当前请求
  3. acceptance.accepted == true
  4. acceptance.primary_file 存在且可读

只有满足以上条件,才视为“已完成”。

不再采用的旧判断

  • 仅凭 output_dir 存在
  • 仅凭 .dinsar_run.json 存在

这样做的效果

  • 目录空壳不会再被跳过
  • 历史损坏结果会重新进入生产
  • “只跑未完成” 的含义变成“只跳过已验收成功的对”

5.4 自动发布阶段从“尽力而为”改为“交付闭环”

当前自动发布逻辑即使 processed == 0 也只记日志。

本轮改为:

  • 引擎层只把“已验收通过”的 output_dir 交给发布器
  • 任务层以“验收通过数量”作为发布期望值
  • 发布结果必须满足:
    • processed == accepted_output_count
    • failed == 0

若不满足,则任务整体失败。

原则

  • 自动发布不再是可有可无的附属步骤
  • 对托管生产来说,发布成功是交付定义的一部分

失败语义

若 native output 已验收通过,但标准包发布失败:

  • 任务状态标记为 FAILED
  • 保留 native output 目录
  • 日志明确写出:
    • accepted count
    • processed count
    • failed count

这样后续可以修发布器或手动重建 catalog,但不会再出现“任务完成却没有结果”的误导状态。

5.5 健康检查与真实运行入口统一

目标:只保留一套 runtime 事实来源。

调整方向

ISCE2_STRIPMAP_APP 不再作为运行事实来源,只保留为兼容字段。

健康检查改成:

  1. wsl_runtime_registry 读取共享 Python
  2. 在对应 Python 中执行:
import isce
from pathlib import Path
print(Path(isce.__file__).resolve().parent / "applications" / "stripmapApp.py")
  1. 检查该路径是否存在
  2. 将解析出的真实路径回显到自检面板

自检项调整

保留:

  • WSL 可用
  • 共享 Python 可执行
  • import isce 成功
  • stripmapApp 动态解析成功
  • runner 脚本存在

弱化:

  • 直接检查 .env 中的 ISCE2_STRIPMAP_APP

自检返回建议新增字段

  • resolved_stripmap_app
  • resolved_isce_package_path
  • python_matches_shared
  • runtime_id

5.6 数据库与自维护策略

本轮尽量不动数据库 schema。

原因:

  • 当前问题核心在文件系统验收语义,不在表结构
  • 用户已经明确要求兼容数据库自维护与自检
  • 现阶段没有必要为了可靠性问题引入一轮 schema 变更风险

本轮原则

  • 不新增非必要列
  • 先把验收信息写入 .dinsar_run.json
  • 数据库继续通过现有 result_products / result_assets / result_issues 表表达发布结果

若后续必须加列

只允许:

  • nullable
  • 可由 _add_missing_columns() 自动补齐
  • 不要求清库

但这不属于本轮必须项。

5.7 运维自检扩展

为避免“native 有结果但 publish 不一致”的灰区,本轮建议在健康面板新增两个聚合视角。

1. runtime 视角

  • 共享 distro / Python / stripmapApp 动态解析是否一致

2. product 视角

  • 已入库产品是否存在 native_output_dir
  • native_output_dir 是否仍存在
  • manifest_path 是否存在

3. 新增建议计数项

  • missing_native_output_count
  • missing_manifest_count
  • missing_runtime_count

其中前两项已有基础,重点是让 ISCE2 runtime 检查结果和产品检查结果在面板里串起来。

6. 文件落点

核心代码

  • backend/app/dinsar_engines/isce2_engine.py
  • backend/app/services/job_handlers.py
  • backend/app/services/result_catalog_service.py
  • backend/app/services/health_service.py
  • backend/app/services/wsl_service.py

新增模块

  • backend/app/services/isce2_result_validator.py

文档与运维

  • docs/ISCE2_PRODUCTION_RELIABILITY_HARDENING_DESIGN_20260424.md

7. 分阶段实施

Phase 1: 成功判定收口

  • 新增 isce2_result_validator
  • rc == 0 后执行验收
  • 未验收通过不写成功结果
  • .dinsar_run.jsonacceptance

Phase 2: 重跑语义收口

  • _has_completed_task_result() 改为验收驱动
  • “只跑未完成” 只跳过已验收成功的结果

Phase 3: 发布闭环收口

  • 自动发布必须与验收通过数量对齐
  • processed == 0 不再仅告警,改为任务失败

Phase 4: 自检对齐

  • stripmapApp 改为动态解析检查
  • 面板展示 runtime 实际解析结果

Phase 5: 后续科学性加固

不在本轮立即落地,但建议保留后续专题:

  • PIKCLE 恢复链版本敏感性评估
  • bbox / geoPosting 的科学性收紧
  • 与 ENVI/IDL 基线链的阶段性对比

8. 验收标准

改造完成后,至少满足以下场景:

  1. WSL runner 返回 0,但没有 *_disp.tif

    • 结果:任务失败,不得显示完成。
  2. 历史 run 目录仍在,但 *_disp.tif 已被删掉。

    • 结果:只跑未完成 不应跳过,应重新生产。
  3. 健康面板显示 stripmapApp 路径。

    • 结果:必须来自共享 Python 动态解析,不再依赖旧硬编码。
  4. native output 验收通过,但发布器没识别到包。

    • 结果:任务失败,日志明确指出 publish mismatch。
  5. 自检面板与实际 runtime 使用的 Python / runner / runtime_id 一致。

9. 本轮结论

本轮实现不应该再把 ISCE2 生产理解为“跑完脚本”,而应理解为:

“执行成功 -> 原生结果验收通过 -> 标准包发布成功 -> 系统完成交付”。

先把这个闭环收紧,再谈后续算法科学性优化,顺序才是健康的。