Files
orbit/docs/api/retention.md

135 lines
9.7 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.
# 数据保留与归档策略
Orbit 将保留策略作为命名空间级数据库配置保存。控制台保持单管理员模型,登录后打开“保留策略”即可查看和修改窗口;每次修改都要求上一版本号、幂等键和变更原因,前后值写入 `orbit_audit_events`。多个 Orbit 节点读取同一个 MySQL 版本,因此不会因为节点环境变量不同而产生不同清理边界。
首次安装和数据库策略尚未保存时使用部署默认值:幂等记录 90 天、任务历史 365 天、流程历史 365 天、事件日志 365 天、已确认交付 90 天、已处理 Inbox 90 天、管理审计 2555 天。默认 `enabled=false`,保存策略也不会立即删除数据。
环境变量只负责首次展示/未保存策略的默认值:
```text
ORBIT_RETENTION_IDEMPOTENCY_DAYS=90
ORBIT_RETENTION_TASK_DAYS=365
ORBIT_RETENTION_WORKFLOW_DAYS=365
ORBIT_RETENTION_EVENT_DAYS=365
ORBIT_RETENTION_DELIVERY_DAYS=90
ORBIT_RETENTION_INBOX_DAYS=90
ORBIT_RETENTION_AUDIT_DAYS=2555
```
窗口范围为 1–3650 天,幂等记录不得短于 90 天;所有节点应使用同一组默认值。数据库中已有策略后,环境变量不覆盖它。管理端的“允许导出只读归档”控制导出权限,导出仍须核对固定策略版本。
归档分两步:先用“预览”或 `GET /console/retention/preview` 查看各类候选,再按固定类别调用 `POST /console/retention/export` 分批生成只读 JSON 快照。每个快照包含冻结的策略版本、数据库时间、截止时间、根记录及关联历史,并返回 SHA-256;源表仍然保留,导出成功只记录摘要和数量审计,不代表可以删除。后续删除执行器必须在独立发布和备份恢复验收后接入,不能把导出接口当成删除接口。
候选筛选由服务端固定目录实现,浏览器不能提交表名或 SQL。终态任务必须没有未知效果、租约和流程所有权;流程必须终态且没有未结任务或未处理 Inbox;事件必须没有未确认交付或未处理 Inbox;交付只允许已确认记录;Inbox 只允许已处理且流程已终态记录;幂等记录需同时超过过期时间和窗口。审计单独使用审计窗口,普通清理不处理审计记录。未确认交付、未处理 Inbox、未知效果、进行中的任务/流程和有效幂等身份始终受保护。
控制台接口示例:
```sh
# 已登录控制台会话;浏览器通常直接使用页面,不需要手工传令牌
curl -b orbit-console.cookie http://127.0.0.1:8080/console/retention
curl -b orbit-console.cookie -X PUT http://127.0.0.1:8080/console/retention \
-H 'Content-Type: application/json' -H 'Idempotency-Key: retention-2026-q3' \
--data '{"expected_revision":"0","enabled":false,"idempotency_days":90,"task_history_days":365,"workflow_history_days":365,"event_log_days":365,"delivery_history_days":90,"inbox_history_days":90,"audit_log_days":2555,"reason":"初始生产保留评审"}'
```
## 管理页操作
1. 登录后打开 `/console/#/retention`,调整窗口、填写单行原因后保存;原因最多 1024 UTF-8 字节。幂等记录最少 90 天。
2. 点击“预览候选”:每类最多展示 100 条,`100+` 表示还有更多,并非全库总数。修改未保存时不能预览或导出,避免使用错误版本。
3. 需要导出时开启“允许导出只读归档”并保存,选择历史类型,点击“导出首批”。每批最多 20 条根记录,关联历史一并导出。浏览器需要 HTTPS 或 localhost 安全上下文来执行 SHA-256 校验。
4. 页面显示本批、累计条数和完整摘要;有后续记录时显示“导出下一批”,到末批后停止。可“重新下载本批”,切换历史类型或保存新策略会重置进度。“从首批开始”重新扫描,不会清理已下载文件。
下载文件保存 `format_version`、`sha256`、原始字符串 `content` 三个字段。页面关闭或刷新后内存中的分页进度会丢失;文件中的 `content.next_cursor` 可用于接口继续导出。服务端提交后网络中断可能多记录一次导出审计,但不改变任何源业务记录;页面未成功校验时不推进游标。修改策略的网络重试在当前页面复用原幂等键。
## 导出接口与分页
这些接口仅允许已登录控制台会话;业务 Bearer 凭据不能代替管理员会话。
| 接口 | 请求/结果 |
| --- | --- |
| `GET /console/retention` | `data` 为当前策略,`revision` 是字符串;未保存版本为 `"0"` |
| `PUT /console/retention` | 必填全部窗口、`enabled`、字符串 `expected_revision`、单行 `reason`,请求头必填 `Idempotency-Key`;幂等返回同一版本 |
| `GET /console/retention/preview` | `data.policy` 为所用策略,`sample_limit=100`;各类别包含 `kind/cutoff/count/more` |
| `POST /console/retention/export` | `kind`、字符串 `expected_revision` 必填;`limit` 默认 20、最大 100;`cursor` 默认空 |
响应采用标准 `{"data": ...}` 信封。导出请求示例:
```sh
curl -b orbit-console.cookie http://127.0.0.1:8080/console/retention/export \
-H 'Content-Type: application/json' \
--data '{"kind":"tasks","expected_revision":"1","limit":20}' \
--output export-response.json
```
`data.content` 是原始 JSON 文本,解析后包括:
| 字段 | 含义 |
| --- | --- |
| `policy` / `kind` | 本批实际策略与根记录类别 |
| `snapshot_at` / `cutoff` | 数据库读取时间与严格早于的候选截止时间,UTC |
| `source_retained` | 始终为 `true`,源数据保留 |
| `root_count` | 本批根记录数量,不含关联历史计数 |
| `tables` | 固定目录下的表与行;二进制为十六进制,整数和 JSON 正文按文本保留精度 |
| `next_cursor` | 非空表示可以继续;下一次请求原样传入相同 `kind` 和 `expected_revision` |
游标绑定命名空间、历史类型、策略版本与截止时间;不接受客户端表名或 SQL。策略变化后必须重新核对并从首批开始。每批使用独立数据库一致性快照,多个批次不是同一个全库时间点;期间从受保护状态转为候选的记录可能需要重新扫描。归档不是灾难恢复备份,不能直接把这些 JSON 导入生产库。
每个导出事务最多 10 秒、10000 条关联记录、8 MiB 正文;超限整体失败,不返回截断快照。减少根记录 `limit` 后可重新尝试;单个根记录本身过大时需要另行处理,不能忽略其历史。每次成功导出记录类别、策略版本、数量和摘要审计。
| 情况 | 处理 |
| --- | --- |
| 未登录/会话已失效 | 401,重新登录 |
| 归档关闭或策略版本不匹配 | 409,读取策略并重新核对 |
| 不支持的类别、游标不匹配、数量越界、快照超限 | 400,修正参数;超限时减小批量 |
| 修改策略结果不明 | 使用完全相同的内容和 `Idempotency-Key` 重试 |
离线核验及生成下一批请求(兼容控制台下载信封和上述 curl 响应):
```python
import hashlib
import json
from pathlib import Path
# 控制台下载文件也可替换这里的文件路径。
response = json.loads(Path("export-response.json").read_text(encoding="utf-8"))
snapshot = response.get("data", response)
assert snapshot["format_version"] == 1
content = snapshot["content"].encode("utf-8")
assert hashlib.sha256(content).hexdigest() == snapshot["sha256"], "快照校验失败"
page = json.loads(content)
assert page["source_retained"] is True
print("本批根记录:", page["root_count"])
if page["next_cursor"]:
Path("next-request.json").write_text(json.dumps({
"kind": page["kind"], "expected_revision": page["policy"]["revision"],
"limit": 20, "cursor": page["next_cursor"],
}), encoding="utf-8")
# 下一批:curl ... --data-binary @next-request.json --output export-response-2.json
```
摘要针对 `content` 的原始 UTF-8 字节,不对解析后重新格式化的 JSON 计算。SHA-256 用于发现内容损坏;摘要与正文一起被篡改时无法证明真实性,需要把审计摘要或外部签名另行保存在可信介质中。
## 自动分批与持久归档
需要在页面之外连续处理并断点续跑时,使用 [`orbit-admin retention archive`](retention-jobs.md)。目录、批量、总字节及执行时限可配置,已发布文件就是恢复检查点,支持离线核验;源业务数据仍保留。周期触发和清理执行器尚未接入。
## 只读盘点
`orbit-admin retention inventory` 用于在制定保留窗口、归档和备份恢复方案前盘点历史数据。它只执行带截止时间的统计查询,不删除、搬迁、改写数据,也不会自动判断某条记录可以安全归档。
```sh
ORBIT_MYSQL_DSN='user:password@tcp(127.0.0.1:3306)/orbit' \
go run ./cmd/orbit-admin retention inventory \
--before 2026-09-18T00:00:00Z
```
示例输出:
```json
{"cutoff":"2026-09-18T00:00:00Z","expired_idempotency":0,"terminal_tasks":0,"uncertain_tasks":0,"active_tasks":0,"terminal_workflows":0,"active_workflows":0,"acknowledged_deliveries":0,"unconfirmed_deliveries":0,"processed_inbox":0,"unprocessed_inbox":0,"events":0,"audit_events":0}
```
所有时间按 UTC 解析,严格早于 `--before` 的记录才计入。截止时间不能晚于数据库当前时间,避免把未来记录误认为历史。`terminal_tasks` 只统计确定终态且 `effect_uncertain=false` 的任务;`uncertain_tasks`、`active_tasks`、`active_workflows`、`unconfirmed_deliveries` 和 `unprocessed_inbox` 是需要先处理或保留的阻塞项。
结果是盘点快照,不是归档授权。真正的清理或归档还必须完成保留周期评审、未确认副作用核对、事件投递和流程推进核对、备份恢复演练、权限隔离及审计;当前命令没有 `apply` 或删除路径。