# 灰度发布与回滚判据（设计稿）

本项目目前是本地优先的单实例参考实现，没有真实用户流量或生产 SLO。本文定义的是上线前应执行的发布门槛，**不是已完成真实灰度的声明**。所有阈值应先在目标硬件、目标数据规模和预期并发下，用 `tests/run_performance.py` 复测并记录基线后再批准。

## 发布前置条件

1. 在干净工作区运行 `python -m pytest -q`、`tests/e2e_verify.py`、`tests/ws_verify.py` 和 `tests/runtime_probe.py`。
2. 用临时数据库运行性能脚本，保存原始输出；重点核对单条写入与 `POST /api/entries/batch` 的 `entries/s`，不再沿用网站中未经复测的“20 entries/s”估算。
3. 配置 `BLACKBOARD_API_KEY`、具体的 `CORS_ORIGINS`、持久化卷和数据库备份。详见 目标仓库 docs/operations.md。
4. 确认监控能采集 `/metrics`，并让日志/事件接收端保存 `X-Request-ID` 或 WebSocket `trace_id`。

## 分阶段策略

| 阶段 | 范围 | 观察项 | 放行条件 | 回滚判据 |
| --- | --- | --- | --- | --- |
| 内部验证 | 单实例、受控测试账户 | HTTP 错误率、请求延迟、事件总线丢弃数、乐观锁冲突 | 冒烟全绿；没有未解释的错误或事件丢弃 | 任一正确性错误、事件丢弃，或延迟相对已批准基线上升超过 25% |
| 受限发布 | 目标并发的 10% 或不超过 10 个受控客户端 | 同上，加 SQLite 写入吞吐、进程内存、WebSocket 重连 | 写入和 WebSocket 结果均达到本环境的批准基线 | 连续两个观察窗口低于写入基线 20%，或 WebSocket 出现未定位的长尾超时 |
| 扩大发布 | 目标并发的 50% | 同上，加数据库文件增长与备份恢复抽查 | 一个完整观察周期无触发项 | HTTP 5xx 上升、内存持续增长、备份恢复失败，或任何数据一致性异常 |
| 全量 | 目标范围 | 与扩大发布一致 | 运行手册与回退版本均已演练 | 命中任一回滚判据立即停止扩量并回退 |

“观察窗口”和各环境的批准基线由发布负责人在性能复测后记录；这里刻意不伪造 P99、内存或吞吐的绝对生产数值。

## 回滚步骤

1. 停止继续扩量，保留触发时间、`X-Request-ID` / `trace_id`、`/metrics` 快照和应用日志。
2. 切回上一个已验证镜像或 Git tag；不对 SQLite 数据库执行破坏性覆盖。
3. 若问题涉及数据写入，先把数据库副本置为只读备份，再依据迁移/恢复预案处理。
4. 在隔离环境复现：使用相同请求关联 ID、事件类型和最小数据集，补充回归测试后才重新进入内部验证阶段。

## 已知边界与替代方案

- SQLite 是单文件、单写入者存储，适合本地和小规模受控使用；批量条目接口通过单次提交减少提交次数，但不是横向扩展方案。
- 当写入容量或多实例需求成为瓶颈时，优先实现 PostgreSQL 存储后端与外部事件总线，再进行真实多副本灰度；在此之前不应将本地压测结果宣传为生产容量。
- 本项目当前关联的是应用内请求与领域事件，不是 OpenTelemetry 跨服务追踪。若需要跨服务采样、trace exporter 或告警联动，应在部署架构确定后引入 OpenTelemetry。


---
网站随附副本：2026-10-06 读取目标仓库 docs/rollout.md；本轮未执行演练。原文件 SHA-256：`704a07b489801da879307e8313797592cc48d86459519c0e0374bfb6a0ec31a2`。
