Commit Graph
3 Commits
Author SHA1 Message Date
yml2213 26066858b6 docs: 记一个部署坑 —— 重建镜像后 up -d 不一定换容器
2026-10-18 部署礼包修复时实际踩到:`docker compose build` 出了新镜像
(ID 变了),`docker compose up -d douyu-login` 只打了句 Started,
容器仍是上一版(ps 显示 Up 11 hours,容器里搜不到新函数),差点把
「没生效」报成「部署成功」。

- AGENTS 坑 #10:重建过镜像就 `up -d --force-recreate <服务名>`
  (必须带服务名,不带会把 mysql 一起重建),部署后核对镜像 ID 或 grep 容器内新代码。
- 部署清单同步改命令(顺带给 `docker compose run` 补上 -T 与 </dev/null,
  同 AGENTS 坑 #5)。
2026-10-18 11:02:09 +08:00
yml2213 4732c02c09 feat(ops): 本地生产镜像同步脚本 + 上线清单补充说明
生产已部署完成(main@2f610a5,迁移 20261004_0046)。为了让后续测试能在本地跑,
把生产库同步成本地镜像,并把同步流程固化成脚本。

## 本次建立的本地镜像

`douyu_login_prod` —— 与生产逐项一致:15,182 账号 / 14,323 CK / 15,169 登录记录 /
**253,424 条 douyu_tasks(含 837MB JSON,可实测保留策略)** / 迁移 0046 / 907MB。

**已验证本地 `.env` 的 `APP_ENCRYPTION_KEY` 与生产一致** ——
抽样解密正常(CK 前缀 `dy_accounts_main`,长度 1659–1702),
所以镜像里的 CK 可用,CK 相关测试能跑。这一点不成立的话镜像基本没用。

## 脚本里规避的两个坑

1. **不要把 `mysqldump` 串进 SSH 管道**:实测 ~49KB/s(MySQL 读 + gzip + 网络互相拖),
   改成「服务器本地 dump 成文件 + rsync」后 ~8MB/s,**快两个数量级**。
   下载那步用 **rsync 而非 scp**:可断点续传,网断了不用重来。
2. 恢复前先 `DROP DATABASE`,避免旧数据残留造成「看起来同步了其实没有」。

## 文档

`docs/上线清单-S1-S7部署.md` 追加「把生产库同步到本地」一节:用法、
镜像内容、两个前提(密钥必须一致;**不要在镜像库上跑测试套件** ——
测试用独立的 `douyu_login_test`),以及上面两个坑。
2026-10-04 18:46:26 +08:00
yml2213 2f610a53e3 feat(ops): S1–S7 上线清单 + 回滚前置脚本(已在生产克隆库完整演练)
生产还停在 `20261003_0040`,S1–S7 一条都没上。本次在**生产克隆库**上把
「迁移 → 校验 → 回滚」完整走了一遍,并把可执行的清单落成文档。

## 演练结果(克隆库,与生产逐项同构)

| 步骤 | 结果 |
| --- | --- |
| 克隆生产库 | 15,182 账号 / 14,324 CK / 版本 `0040` / 27 张表,与生产一致 |
| **迁移全链 0041→0046** | 6 个迁移,**9 秒**,无错误(含 S1 的 fail-closed 自校验) |
| 账号数 | 15,182 ✓ 不变 |
| **CK 搬运** | 账号侧 14,323 = 登录记录侧**去重账号数 14,323**(精确相等)|
| 精英/电竞映射 | bound 410 / pending 75 / unbound 14;电竞 **unbound 26**(修复生效)|
| `esports_opened` | **4** ✓(抢救生效)|
| `source_ref` 可空 | `YES` ✓ |
| **回滚降级** | 6 个迁移,**2 秒**;`bind_status` 原值完好、两列回到 `varchar(32)` |

## 🔴 演练查出的关键风险(不演练绝对发现不了)

S2 之后 CK **只写 `accounts.cookie`、刻意不双写** `login_tasks.cookie`。
而降级会 **drop 掉 `accounts.cookie`** —— 也就是:

> **部署期间新拿到的 CK,会在回滚时全部丢失。**

CK 丢失是本项目唯一不能承受的损失(重登成功率≈0、全量重登≈2.7 天)。

于是新增 `scripts/backfill_login_tasks_from_accounts.py`(**回滚前置**):
把账号侧 CK / 健康 / 重登进度搬回旧列;没有历史行的账号补一条合成行
(否则旧代码靠 `login_tasks` 找不到它的 CK);没有 CK 的账号清掉旧行残留
(否则旧代码以为它还持有 CK)。自带 `--dry-run` 与**自检**
(账号侧 ≤ 登录记录侧去重数,不通过就别降级)。

**同样在克隆库上验证过**:模拟「部署期间新拿到一个 CK」→ 跑回填 → 降级 →
**该 CK 完整幸存**(task#100 / 账号 168)。不跑回填的话它就没了。

## 补齐了之前做不到的那项验证

本地没有生产的加密密钥,所以之前只验证了 CK「搬过去了」、没验证「能解密」。
这次**在生产上只读实测**:抽样 5 条 `login_tasks.cookie`,**5/5 解密正常**
(长度 1613–1660)。这是清单里第 1 节的部署前基线。

## 交付物

- `docs/上线清单-S1-S7部署.md`:部署前检查 / 执行顺序(**先停应用→迁移→再起代码**)/
  七项逐条校验 / 回滚预案(含正确顺序)/ 已知限制 / 演练证据
- `scripts/backfill_login_tasks_from_accounts.py` + 2 条用例
  (搬回已有行、无历史行时补合成行)

## 验证

`TZ=Asia/Shanghai`:**552 passed / 6 skipped**;ruff 全绿。

⚠️ 清单里写明:**S8(删旧列)本次不做** —— 要等稳定运行 + 逻辑备份之后。
2026-10-04 18:18:30 +08:00