Files
FT546MWS/doc/algorithm-shadow/README.md

303 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 秒超时,并且只在名为 `ft546-shadow-observer` 的 daemon 工作线程执行。
- 专用执行器只有一个线程、队列容量为 0,拒绝策略不会回落到 Legacy 请求线程;忙时其他观察立即跳过。
- Legacy 线程仅提交任务和通知内存结果,不等待影子规划或比较,也不执行任何影子数据库操作。
- 影子异常、超时、输入缺失和比较日志写入失败都会 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 会以错误 51000 拒绝执行。如果现场已经存在 `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` 应存在目标仓库的数据。如果安装部分失败,不要启动影子程序,也不要执行本分支卸载脚本:拒绝式卸载会因对象缺失而报错 51014。保留完整输出和安装前后对象清单,由 DBA 决定从备份恢复,或依据清单人工清理本次未完成安装。不要补写 manifest、补建空对象或修改卸载脚本来绕过保护;清理完成且 `SCHEMA_ID(N'alg')` 再次为 `NULL` 后,才能重新完整安装。
确认包标记唯一且版本准确:
```sql
SELECT package_id, package_version, installed_at
FROM alg.shadow_package_manifest;
```
必须只有一行:`FT546WMS_SHADOW_ONLY` / `2026-08-12-1`。不一致时不要启动影子程序,也不要手工修改标记。
## 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` 记录。
影子任务在 Legacy 前提交,但 Legacy 不等待后台线程。线程调度和数据库时序可能导致影子读取与 Legacy 写入重叠,甚至在 Legacy 写入后读取;影子结果不是严格的“Legacy 前快照”,分析时必须结合时间和业务结果解释。
单线程零队列执行器忙时,被跳过的请求不会调用算法,也没有对应的计划/比较决策日志;如果规划或 Legacy 超过观察期限,也可能只有计划日志而没有比较日志。Legacy 存储过程仍按原流程立即执行。这是保护业务延迟和数据库负载的预期行为,不要据此重放业务请求。
## 8. 观察与停止条件
应用日志中的以下文本表示后台影子已失败开放,Legacy 仍继续执行:
```text
Shadow inbound observation failed open
Shadow outbound observation failed open
Shadow inbound observation interrupted
Shadow outbound observation interrupted
Inbound shadow submission failed open
Project outbound shadow submission failed open
Exact-stock shadow submission failed open
```
出现单条告警不等于 Legacy 失败,但应记录时间和业务单号。测试前由现场负责人和 DBA 根据稳定时段基线写下响应延迟、SQL CPU/I/O、锁等待、连接数和 Shadow 告警频率阈值;未约定阈值时,只允许少量受控业务,不进入连续观察。满足任一条件立即停止影子测试并执行第 10 节回滚:
- Legacy 入库或出库结果、返回码、消息或设备动作与稳定版不同;
- 正常接口达到预先约定的延迟阈值,或同类 3 秒超时告警重复出现;
- SQL Server CPU、I/O、锁等待或连接数出现不可接受增长;
- `alg` 之外出现任何由本次测试新增的表、过程、触发器或数据修改;
- 同一 Legacy 存储过程疑似被重复调用;
- WMS、WCS、PDA 或现场操作出现异常。
实际进入影子规划的请求每次都会按仓库全量刷新读模型。应用在单个 JVM 内将观察周期上限限制为 1、队列容量为 0,其他并发观察立即跳过;这能限制重叠刷新,但 `READ_UNCOMMITTED` 和单并发都无法消除全表扫描带来的 CPU 和 I/O,所以必须从少量受控业务开始,不能直接长时间全量开放。若部署多个影子 JVM,该上限分别生效,现场仍只允许启动一个 WMS 实例。
## 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
```
卸载脚本先校验包标记必须精确等于 `FT546WMS_SHADOW_ONLY` / `2026-08-12-1`,再校验 `alg` 顶层对象的名称和类型必须与本包白名单完全一致。发现未知对象、缺失对象、类型不符或标记不符时会在第一个 `DROP` 前拒绝执行,不删除任何对象。校验通过后才在事务内删除本分支拥有的 `alg` 过程、视图、表和 schema;失败会回滚,不修改 Legacy 业务行。执行后验证:
```sql
SELECT SCHEMA_ID(N'alg') AS alg_schema_id;
```
预期结果为 `NULL`。再次查询全库 DML 触发器集合,结果应与安装前保存的基线一致。
不要在影子应用运行时卸载。如果脚本报错 51011-51014,保存错误和对象清单,停止手工删除,由 DBA 判断恢复或清理方案。不要修改 manifest 来绕过保护。卸载后如需再次测试,必须重新执行完整安装和检查流程。
## 12. 已知残余风险
- 当前自动化验证是 Java 单元测试和 SQL 静态审计,不能替代真实 SQL Server 2016 副本演练。
- `READ_UNCOMMITTED` 可能读到业务事务的中间状态;该结果只用于影子比较,不参与 Legacy 决策。
- 零等待异步提交不保证严格的 Legacy 前置快照;影子读取可能与 Legacy 写入重叠或发生在其后。
- 3 秒事务超时会触发 fail-open,但 JDBC 取消和 SQL Server 回滚完成时间不保证严格等于 3 秒。
- 每次影子规划前的全量刷新可能增加数据库 CPU 和 I/O;现场必须监控并设置短测试窗口。
- JVM 内单线程零队列执行器只避免同一实例的重叠刷新,不限制其他进程或数据库会话;禁止同时运行稳定版和影子版,也禁止运行多个影子实例。
- 应用级 fail-open 能保护 Legacy 控制流,但不能消除数据库、网络或主机资源争用。
这些残余风险是采用“零 Legacy 触发器、每次规划前全量刷新”的代价,也是现场必须先小流量验证、随时可切回稳定源码的原因。