docs: add IDEA shadow deployment and rollback guide

This commit is contained in:
zgw
2026-08-11 19:38:26 +08:00
parent 8b050ac9d6
commit 2fbb20c5b1
5 changed files with 365 additions and 9 deletions

View File

@@ -0,0 +1,283 @@
# FT546WMS 现场影子测试操作手册
## 1. 适用范围
本手册用于分支 `test/ft546wms-shadow-from-main-20260811` 的现场测试。该分支基于远端稳定版本:
```text
origin/main = 916ac6bda32a5b102a3181cc8c5dbd5319c5656d
```
影子算法只提出候选结果并记录比较数据。原有 Legacy 存储过程仍执行一次且仅执行一次,Legacy 的库位分配、返回码和响应内容始终是最终结果。
影子模式仍会读取 Legacy 表,并会在 `alg` schema 中刷新读模型和写入决策日志。因此“只观察”表示不改变 Legacy 业务数据和决策权,不表示完全没有数据库 CPU、I/O 或执行时间开销。
## 2. 安全边界
- 默认配置 `FORTUCKY_SHADOW_ENABLED=false`,未显式开启时不执行影子规划。
- 启用时只允许 `FORTUCKY_ALGORITHM_MODE=SHADOW`;误设 `ACTIVE` 或 `LEGACY` 会导致应用拒绝启动。
- 影子事务使用 `REQUIRES_NEW`、`READ_UNCOMMITTED` 和 3 秒超时。
- 影子异常、超时、输入缺失和比较日志写入失败都会 fail-open,不跳过 Legacy 流程。
- 安装包不在 `dbo.bas_cargo_space`、`dbo.bas_stock`、`dbo.mydo_job` 或其他 Legacy 表上创建触发器。
- 安装包不对 Legacy 业务表执行 `INSERT`、`UPDATE`、`DELETE` 或 `MERGE`。
- 影子模式不调用预留、深位锁、理库、回放、移库完成、WCS 完成或 PDA 回调写入过程。
- `alg.reservation` 和 `alg.deep_workflow_lock` 是规划器读取兼容表;影子包不安装修改它们的过程,SHADOW 模式也禁止进入这些写入口。
## 3. 交付文件
```text
fortucky-algorithm/src/main/resources/db/sqlserver/install-shadow.sql
fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics-shadow.sql
fortucky-algorithm/src/main/resources/db/sqlserver/uninstall-shadow.sql
```
只使用名称带 `-shadow` 的脚本。该分支已移除原算法通用的 `install.sql`、`diagnostics.sql` 和 `uninstall.sql`,避免现场误装 ACTIVE 数据库对象。
## 4. 上现场前必须完成的演练
1. 在现场数据库的最近备份副本上启动远端 `origin/main`,完成一次入库和一次出库基线测试。
2. 在备份副本执行 `install-shadow.sql`,确认脚本全部成功。
3. 用影子分支完成同样的入库和出库,确认业务结果仍来自 Legacy。
4. 导出 `alg.decision_log`,然后停止影子程序并执行 `uninstall-shadow.sql`。
5. 再次启动远端 `origin/main`,重复基线测试。
未完成数据库副本演练,不要直接在现场生产库安装。
## 5. 准备两个独立 IDEA 工作区
当前开发机的本地 `main` 已与 `origin/main` 分叉,不能作为稳定版回滚源。先执行 `git fetch origin`,确认 `origin/main` 的提交号,再从远端提交准备只读稳定工作区。
在仓库主目录执行。目标目录必须不存在;如果已存在,先人工确认它的来源,不要覆盖或删除:
```powershell
git fetch origin
$ExpectedStableCommit = "916ac6bda32a5b102a3181cc8c5dbd5319c5656d"
$RemoteMainCommit = git rev-parse origin/main
if ($RemoteMainCommit -ne $ExpectedStableCommit) { throw "origin/main 已变化,停止部署并重新合并验证" }
$StableWorktree = "D:\workSpace\PROJECT_SPACE\FT546\FT546MWS\worktrees\stable-origin-main-20260811"
if (Test-Path -LiteralPath $StableWorktree) { throw "稳定版 worktree 目标目录已存在,请人工核对" }
git worktree add --detach $StableWorktree origin/main
$CheckedOutCommit = git -C $StableWorktree rev-parse HEAD
if ($CheckedOutCommit -ne $ExpectedStableCommit) { throw "稳定版 worktree 提交校验失败" }
if (git -C $StableWorktree status --porcelain) { throw "稳定版 worktree 不是干净状态" }
```
两个 IDEA 窗口分别打开:
```text
稳定版:D:\workSpace\PROJECT_SPACE\FT546\FT546MWS\worktrees\stable-origin-main-20260811
影子版:D:\workSpace\PROJECT_SPACE\FT546\FT546MWS\worktrees\shadow-from-main-20260811
```
为两个窗口分别创建 Run Configuration:
- `FT546WMS-STABLE-origin-main`:完全沿用当前稳定现场参数,不增加任何 `FORTUCKY_*` 环境变量。
- `FT546WMS-SHADOW`:复制稳定配置的 JDK、profile、端口、数据库和 Redis 参数,只增加下一节列出的环境变量。
禁止同时运行两个实例。即使使用不同 HTTP 端口,两个 WMS 实例也可能重复执行定时任务、消费任务或发送外部回调。切换时必须先停止当前实例,确认进程退出和端口释放,再启动另一个实例。
## 6. 安装影子数据库包
### 6.1 安装前检查
由 DBA 对现场数据库做可恢复的完整备份,并记录备份文件、时间和恢复负责人。随后执行:
```sql
SELECT SCHEMA_ID(N'alg') AS alg_schema_id;
SELECT trigger_object.name AS trigger_name,
schema_name(trigger_object.schema_id) AS trigger_schema,
schema_name(parent_object.schema_id) AS parent_schema,
parent_object.name AS parent_table
FROM sys.triggers trigger_object
JOIN sys.objects parent_object ON parent_object.object_id = trigger_object.parent_id
WHERE trigger_object.is_ms_shipped = 0
ORDER BY parent_schema, parent_object.name, trigger_schema, trigger_object.name;
SELECT schema_name(object.schema_id) AS schema_name,
object.name AS object_name,
object.type_desc
FROM sys.objects object
WHERE object.is_ms_shipped = 0
ORDER BY schema_name(object.schema_id), object.type_desc, object.name;
```
预期 `alg_schema_id` 为 `NULL`。如果现场已经存在 `alg` schema 或同名对象,停止安装并先确认所有权;不要覆盖,也不要执行本分支卸载脚本。
把两份查询结果连同执行时间保存为安装前基线。现场可能已有 Legacy 自身的触发器,安装后的全库 DML 触发器集合必须与安装前完全一致;用户对象清单只能新增本包定义的 `alg.*` 对象。
### 6.2 执行安装
在低业务量时段使用 SQL Server Management Studio 打开并执行:
```text
fortucky-algorithm/src/main/resources/db/sqlserver/install-shadow.sql
```
脚本使用 `GO` 分批执行并在当前会话设置 `READ_UNCOMMITTED`。首次安装会全量建立读模型,可能扫描较多现场数据;如果副本演练时间超出可接受窗口,现场不要继续。
也可以使用已配置 Windows 集成认证的 `sqlcmd`:
```powershell
sqlcmd -S "<SQL Server>" -d "<数据库>" -E -b -r1 -i "<仓库>\fortucky-algorithm\src\main\resources\db\sqlserver\install-shadow.sql"
```
不要把数据库密码写进脚本、IDEA Run Configuration 截图或命令历史。
### 6.3 安装后检查
重新执行安装前的两份元数据查询。全库 DML 触发器结果必须完全一致,用户对象清单只能新增预期的 `alg.*` 对象。保存安装后结果和执行时间,再执行:
```sql
SELECT schema_name(object.schema_id) AS schema_name,
object.name,
object.type_desc
FROM sys.objects object
WHERE schema_name(object.schema_id) = N'alg'
ORDER BY object.type_desc, object.name;
SELECT warehouse_id,
COUNT(*) AS lane_count,
MAX(rebuilt_at) AS latest_rebuild_at
FROM alg.lane_state
GROUP BY warehouse_id;
```
本次新增对象只能位于 `alg` schema;`lane_state` 应存在目标仓库的数据。如果安装部分失败,不要启动影子程序,也不要自动执行卸载脚本。保留完整输出和安装前后对象清单,由 DBA 决定从备份恢复,或在确认安装前 `alg` 不存在且失败期间无人创建其他 `alg` 对象后,才执行本分支卸载脚本。
## 7. IDEA 启用影子模式
在 `FT546WMS-SHADOW` 的 Environment variables 中设置:
```text
FORTUCKY_SHADOW_ENABLED=true
FORTUCKY_ALGORITHM_MODE=SHADOW
FORTUCKY_ALGORITHM_DATABASE_WRITES_ENABLED=true
FORTUCKY_ALGORITHM_WAREHOUSE_ID=01
FORTUCKY_SHADOW_TIMEOUT_SECONDS=3
FORTUCKY_ALGORITHM_LEGACY_FALLBACK_ON_TECHNICAL_ERROR=false
```
将 `01` 替换为现场实际仓库号。保持 `FORTUCKY_SHADOW_WAREHOUSE_ID` 未设置,使影子入口统一回退到 `FORTUCKY_ALGORITHM_WAREHOUSE_ID`。不要设置 replay、tidy、ACTIVE 或 WCS callback 相关变量。
`FORTUCKY_ALGORITHM_DATABASE_WRITES_ENABLED=true` 是影子读模型与 `alg.decision_log` 所必需的应用开关;当前影子代码路径只执行已安装的 `alg.*` 写入,它不把 Legacy 决策权交给算法。该开关不是数据库权限隔离,安全性还依赖本分支的 SHADOW 启动保护、删减后的 SQL 包和现场变更审计。
切换步骤:
1. 记录稳定实例停止时间,停止 `FT546WMS-STABLE-origin-main`。
2. 确认 Java 进程退出、端口释放,且没有第二个 WMS 实例运行。
3. 启动 `FT546WMS-SHADOW`。
4. 如果出现 `Shadow observation requires ...`,不要修改保护条件,直接回滚到稳定实例。
5. 先做只读登录和查询冒烟检查,再由现场人员执行一笔受控入库和一笔受控出库。
6. 确认 Legacy 返回码、库位和现场设备动作与稳定流程一致。
7. 确认 `alg.decision_log` 出现 `INBOUND`/`OUTBOUND` 及相应的 `*_COMPARE` 记录。
## 8. 观察与停止条件
应用日志中的以下文本表示影子已失败开放,Legacy 仍继续执行:
```text
Shadow inbound planning failed open
Shadow outbound planning failed open
Shadow exact-stock planning failed open
Shadow inbound comparison failed open
Shadow outbound comparison failed open
```
出现单条告警不等于 Legacy 失败,但应记录时间和业务单号。测试前由现场负责人和 DBA 根据稳定时段基线写下响应延迟、SQL CPU/I/O、锁等待、连接数和 Shadow 告警频率阈值;未约定阈值时,只允许少量受控业务,不进入连续观察。满足任一条件立即停止影子测试并执行第 10 节回滚:
- Legacy 入库或出库结果、返回码、消息或设备动作与稳定版不同;
- 正常接口达到预先约定的延迟阈值,或同类 3 秒超时告警重复出现;
- SQL Server CPU、I/O、锁等待或连接数出现不可接受增长;
- `alg` 之外出现任何由本次测试新增的表、过程、触发器或数据修改;
- 同一 Legacy 存储过程疑似被重复调用;
- WMS、WCS、PDA 或现场操作出现异常。
影子规划每次都会按仓库全量刷新读模型。`READ_UNCOMMITTED` 降低了对 Legacy 写事务的锁干扰,但无法消除全表扫描带来的 CPU 和 I/O,所以必须从少量受控业务开始,不能直接长时间全量开放。
## 9. 数据采集与交付
测试开始和结束时各执行一次:
```text
fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics-shadow.sql
```
在 SSMS 中选择“结果输出到文件”,保留完整结果。该脚本只读,包含对象清单、读模型统计、Legacy 表行数/校验和、当前锁和决策日志。Legacy 表校验和会扫描数据,只在低业务量时执行,不要高频轮询。
测试结束、卸载之前单独导出原始决策记录:
```sql
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT decision_id, warehouse_id, decision_type, input_version,
result_code, reason, created_at
FROM alg.decision_log
WHERE created_at >= '<测试开始时间>'
AND created_at < '<测试结束时间>'
ORDER BY created_at, decision_id;
```
交付分析的数据包至少包括:
- 决策日志导出文件,保持原始列名和时间字段;
- `diagnostics-shadow.sql` 开始和结束两份结果;
- 应用日志中测试时间段的 Shadow 告警及其前后异常栈;
- 测试开始/结束时间、时区、仓库号、入库次数、自动出库次数、手动出库次数;
- 现场人员确认的 Legacy 最终库位、返回码及异常说明;
- 当前分支提交号和构建 JAR 的 SHA-256。
不要提供数据库密码、访问令牌或与分析无关的个人信息。
## 10. 快速回滚
### 10.1 首选:切回远端稳定源码
1. 停止 `FT546WMS-SHADOW`,记录停止时间。
2. 确认进程退出和端口释放。
3. 在独立稳定工作区启动 `FT546WMS-STABLE-origin-main`。
4. 执行登录、查询、受控入库和受控出库冒烟检查。
稳定版不引用 `alg.*`,因此应用回滚时无需立即卸载数据库对象。先恢复生产服务,再在维护窗口导出数据并卸载。
### 10.2 备用:仅关闭影子开关
如果稳定工作区暂时无法启动,可在影子 Run Configuration 中设置:
```text
FORTUCKY_SHADOW_ENABLED=false
```
然后完整重启应用。该方式保留影子分支代码,但所有影子入口会直接返回空观察结果。它不替代稳定版回滚,只用于临时恢复。
## 11. 完整卸载
仅在影子应用已停止、`alg.decision_log` 已导出且稳定版已恢复后执行。
使用 SSMS 打开并执行:
```text
fortucky-algorithm/src/main/resources/db/sqlserver/uninstall-shadow.sql
```
卸载脚本只删除本分支拥有的 `alg` 过程、视图、表和 schema,不修改 Legacy 业务行。执行后验证:
```sql
SELECT SCHEMA_ID(N'alg') AS alg_schema_id;
```
预期结果为 `NULL`。再次查询全库 DML 触发器集合,结果应与安装前保存的基线一致。
不要在影子应用运行时卸载。不要对包含未知对象的既有 `alg` schema 执行本卸载脚本。卸载后如需再次测试,必须重新执行完整安装和检查流程。
## 12. 已知残余风险
- 当前自动化验证是 Java 单元测试和 SQL 静态审计,不能替代真实 SQL Server 2016 副本演练。
- `READ_UNCOMMITTED` 可能读到业务事务的中间状态;该结果只用于影子比较,不参与 Legacy 决策。
- 3 秒事务超时会触发 fail-open,但 JDBC 取消和 SQL Server 回滚完成时间不保证严格等于 3 秒。
- 每次影子规划前的全量刷新可能增加数据库 CPU 和 I/O;现场必须监控并设置短测试窗口。
- 应用级 fail-open 能保护 Legacy 控制流,但不能消除数据库、网络或主机资源争用。
这些残余风险是采用“零 Legacy 触发器、每次规划前全量刷新”的代价,也是现场必须先小流量验证、随时可切回稳定源码的原因。