fix: verify shadow SQL package ownership
This commit is contained in:
@@ -16,8 +16,9 @@ origin/main = 916ac6bda32a5b102a3181cc8c5dbd5319c5656d
|
||||
|
||||
- 默认配置 `FORTUCKY_SHADOW_ENABLED=false`,未显式开启时不执行影子规划。
|
||||
- 启用时只允许 `FORTUCKY_ALGORITHM_MODE=SHADOW`;误设 `ACTIVE` 或 `LEGACY` 会导致应用拒绝启动。
|
||||
- 影子事务使用 `REQUIRES_NEW`、`READ_UNCOMMITTED` 和 3 秒超时。
|
||||
- 单个 JVM 同时最多执行一个影子规划;并发到达的其他观察会立即跳过,不排队、不等待,也不延迟 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`。
|
||||
@@ -105,7 +106,7 @@ WHERE object.is_ms_shipped = 0
|
||||
ORDER BY schema_name(object.schema_id), object.type_desc, object.name;
|
||||
```
|
||||
|
||||
预期 `alg_schema_id` 为 `NULL`。如果现场已经存在 `alg` schema 或同名对象,停止安装并先确认所有权;不要覆盖,也不要执行本分支卸载脚本。
|
||||
预期 `alg_schema_id` 为 `NULL`。安装脚本发现任何既有 `alg` schema 会以错误 51000 拒绝执行。如果现场已经存在 `alg` schema 或同名对象,停止安装并先确认所有权;不要覆盖,也不要执行本分支卸载脚本。
|
||||
|
||||
把两份查询结果连同执行时间保存为安装前基线。现场可能已有 Legacy 自身的触发器,安装后的全库 DML 触发器集合必须与安装前完全一致;用户对象清单只能新增本包定义的 `alg.*` 对象。
|
||||
|
||||
@@ -148,6 +149,15 @@ GROUP BY warehouse_id;
|
||||
|
||||
本次新增对象只能位于 `alg` schema;`lane_state` 应存在目标仓库的数据。如果安装部分失败,不要启动影子程序,也不要自动执行卸载脚本。保留完整输出和安装前后对象清单,由 DBA 决定从备份恢复,或在确认安装前 `alg` 不存在且失败期间无人创建其他 `alg` 对象后,才执行本分支卸载脚本。
|
||||
|
||||
确认包标记唯一且版本准确:
|
||||
|
||||
```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 中设置:
|
||||
@@ -173,20 +183,24 @@ FORTUCKY_ALGORITHM_LEGACY_FALLBACK_ON_TECHNICAL_ERROR=false
|
||||
4. 如果出现 `Shadow observation requires ...`,不要修改保护条件,直接回滚到稳定实例。
|
||||
5. 先做只读登录和查询冒烟检查,再由现场人员执行一笔受控入库和一笔受控出库。
|
||||
6. 确认 Legacy 返回码、库位和现场设备动作与稳定流程一致。
|
||||
7. 确认 `alg.decision_log` 出现 `INBOUND`/`OUTBOUND` 及相应的 `*_COMPARE` 记录。
|
||||
7. 确认 `alg.decision_log` 出现 `INBOUND`/`OUTBOUND` 记录,并至少有受控业务出现相应的 `*_COMPARE` 记录。
|
||||
|
||||
并发业务触发单 JVM 规划门时,被跳过的请求不会调用算法,也可能没有对应的计划/比较决策日志;Legacy 存储过程仍按原流程立即执行。这是保护现场数据库负载的预期行为,不要据此重放业务请求。
|
||||
影子任务在 Legacy 前提交,但 Legacy 不等待后台线程。线程调度和数据库时序可能导致影子读取与 Legacy 写入重叠,甚至在 Legacy 写入后读取;影子结果不是严格的“Legacy 前快照”,分析时必须结合时间和业务结果解释。
|
||||
|
||||
单线程零队列执行器忙时,被跳过的请求不会调用算法,也没有对应的计划/比较决策日志;如果规划或 Legacy 超过观察期限,也可能只有计划日志而没有比较日志。Legacy 存储过程仍按原流程立即执行。这是保护业务延迟和数据库负载的预期行为,不要据此重放业务请求。
|
||||
|
||||
## 8. 观察与停止条件
|
||||
|
||||
应用日志中的以下文本表示影子已失败开放,Legacy 仍继续执行:
|
||||
应用日志中的以下文本表示后台影子已失败开放,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
|
||||
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 节回滚:
|
||||
@@ -198,7 +212,7 @@ Shadow outbound comparison failed open
|
||||
- 同一 Legacy 存储过程疑似被重复调用;
|
||||
- WMS、WCS、PDA 或现场操作出现异常。
|
||||
|
||||
实际进入影子规划的请求每次都会按仓库全量刷新读模型。应用在单个 JVM 内将并发规划上限限制为 1,其他并发观察立即跳过;这能限制重叠刷新,但 `READ_UNCOMMITTED` 和单并发都无法消除全表扫描带来的 CPU 和 I/O,所以必须从少量受控业务开始,不能直接长时间全量开放。若部署多个影子 JVM,该上限分别生效,现场仍只允许启动一个 WMS 实例。
|
||||
实际进入影子规划的请求每次都会按仓库全量刷新读模型。应用在单个 JVM 内将观察周期上限限制为 1、队列容量为 0,其他并发观察立即跳过;这能限制重叠刷新,但 `READ_UNCOMMITTED` 和单并发都无法消除全表扫描带来的 CPU 和 I/O,所以必须从少量受控业务开始,不能直接长时间全量开放。若部署多个影子 JVM,该上限分别生效,现场仍只允许启动一个 WMS 实例。
|
||||
|
||||
## 9. 数据采集与交付
|
||||
|
||||
@@ -265,7 +279,7 @@ FORTUCKY_SHADOW_ENABLED=false
|
||||
fortucky-algorithm/src/main/resources/db/sqlserver/uninstall-shadow.sql
|
||||
```
|
||||
|
||||
卸载脚本只删除本分支拥有的 `alg` 过程、视图、表和 schema,不修改 Legacy 业务行。执行后验证:
|
||||
卸载脚本先校验包标记必须精确等于 `FT546WMS_SHADOW_ONLY` / `2026-08-12-1`,再校验 `alg` 顶层对象的名称和类型必须与本包白名单完全一致。发现未知对象、缺失对象、类型不符或标记不符时会在第一个 `DROP` 前拒绝执行,不删除任何对象。校验通过后才在事务内删除本分支拥有的 `alg` 过程、视图、表和 schema;失败会回滚,不修改 Legacy 业务行。执行后验证:
|
||||
|
||||
```sql
|
||||
SELECT SCHEMA_ID(N'alg') AS alg_schema_id;
|
||||
@@ -273,15 +287,16 @@ SELECT SCHEMA_ID(N'alg') AS alg_schema_id;
|
||||
|
||||
预期结果为 `NULL`。再次查询全库 DML 触发器集合,结果应与安装前保存的基线一致。
|
||||
|
||||
不要在影子应用运行时卸载。不要对包含未知对象的既有 `alg` schema 执行本卸载脚本。卸载后如需再次测试,必须重新执行完整安装和检查流程。
|
||||
不要在影子应用运行时卸载。如果脚本报错 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 内单并发门只避免同一实例的重叠刷新,不限制其他进程或数据库会话;禁止同时运行稳定版和影子版,也禁止运行多个影子实例。
|
||||
- JVM 内单线程零队列执行器只避免同一实例的重叠刷新,不限制其他进程或数据库会话;禁止同时运行稳定版和影子版,也禁止运行多个影子实例。
|
||||
- 应用级 fail-open 能保护 Legacy 控制流,但不能消除数据库、网络或主机资源争用。
|
||||
|
||||
这些残余风险是采用“零 Legacy 触发器、每次规划前全量刷新”的代价,也是现场必须先小流量验证、随时可切回稳定源码的原因。
|
||||
|
||||
Reference in New Issue
Block a user