Files
new-api-key-usage/docs/design/同步引擎设计.md
T
skycurtain 45b312b357 docs: 设计文档体系落定(七篇定稿)
- docs/design:四张表字段设计 · 交互设计(方向层 + 细节层)· API 契约 · 同步引擎设计
- docs/upstream:sk-key 接口实证(430 行)· 接口范围(9 个接口)
- README(全景入口)· AGENTS.md(AI 协作者指引)
- 废弃旧稿已删除(不随仓库留存)
2026-09-14 23:54:52 +08:00

9.6 KiB
Raw Blame History

同步引擎设计

采集侧的引擎设计:调度、每轮动作、失败与状态机、可观测、部署初始化。它是「数据流动」的实现依据——写入者只有它(api_keys / usage_logs / qpu_history 三张表的写入时机见各表设计);查看面完全只读。

项目 内容
状态 定稿(2026-09-13:17 条议题全部落定——见 §2 结论索引与 §3 决策记录)
关联 docs/upstream/NewAPI-接口范围.md(上游调用与失败语义)· 四张表字段设计(写入时机)· docs/design/API契约.md(查看面消费的数据形状)· README.md(既定决策与关键事实)
范围 调度与执行、失败处理、可观测、部署初始化
不含 前端交互(交互设计)· HTTP 契约(API 契约,已定稿)· 多实例协调 / 历史回填 / 告警渠道(见 §4)

1. 前提(已由其他文档锁定,不在此重新讨论)

  • 统一 tick 方向已定(2026-09-13 作者确认):一轮 = 遍历全部 active key,逐个查日志 → 去重入库
  • 后端单进程(README 决策 7,2026-09-13 修订):API 与同步引擎同进程、单实例;前端静态产物独立部署(后端不托管前端产物);「定时器唯一」的前提就是这一个后端进程
  • 三个上游调用、失败互不影响(api_keys §3.3):日志(写 log_synced_at)· 额度(写 usage_synced_at)· /api/status(QPU 采样)
  • 物理约束:/api/log/token 固定返回最近 1000 条(实测 ≈ 3.2 天);同步停摆超约 3 天即永久丢数据(README 事实 4)
  • 失败语义(docs/upstream/NewAPI-接口范围.md §1):401 = key 不存在 / 显式禁用(唯一计入摘除的);403 = 账号封禁;500 = 数据库错误;过期 / 额度耗尽在宽松档仍可查
  • 登录 / 登记不阻塞:登记只做验证 + INSERT,首轮同步由引擎立即异步补一轮(API 契约 §2.2)
  • 启用需验证:管理面手动启用 = 上游验证通过(api_keys §3.6);启用成功清零 consecutive_401s

2. 议题清单(2026-09-13 范围确认;逐条推导,结论逐步回填)

2.1 运行形态与调度

# 议题 初步倾向 状态
1 载体:定时器跑在哪 已定(2026-09-13):随 Hono 进程内的定时器(后端单进程);不引入 worker 线程、不拆独立进程——影响评估见 §3 已定
2 周期值 已定(2026-09-13):5 分钟(固定间隔 tick + 防重入)——见 §3.2 已定
3 启动行为 已定(2026-09-13):进程启动立即跑一轮,随后进常规节奏——见 §3.2 已定
4 单轮内执行顺序 已定(2026-09-13):全部 active key 串行遍历——见 §3.2 已定

2.2 每轮的动作

# 议题 初步倾向 状态
5 日志同步 已定(2026-09-13):每 key 拉最近 1000 条 → 整批单事务入库(ON CONFLICT (dedupe_key) DO NOTHING;全成或全不写)→ 写 log_synced_at(取请求发起时刻)——见 §3.3 已定
6 额度采集 已定(2026-09-13):每 key 取上游字段全组 + usage_synced_at(同口径);与日志各自独立(先日志后额度)——见 §3.3 已定
7 QPU 采样 已定(2026-09-13):独立于 key 循环,每轮一次读 /api/status;值变化才追加——见 §3.3 已定

2.3 失败与状态机

# 议题 初步倾向 状态
8 401 摘除阈值 N 已定(2026-09-13):N = 3——见 §3.4 已定
9 失败状态区分 已定(2026-09-13):只有 401 计数;403 / 500 / 网络不动计数——见 §3.4 已定
10 单轮内重试 已定(2026-09-13):不重试,等下一轮——见 §3.4 已定
11 解密失败(KEYS_MASTER_KEY 变更) 已定(2026-09-13):记日志、跳过、不影响其他 key;不计数——见 §3.4 已定

2.4 可观测与完整性

# 议题 初步倾向 状态
12 错误日志 已定(2026-09-13):人类可读一行([sync] 前缀 + key_id / 阶段 / 上游状态 / 耗时);不引日志库、不输出 JSON——见 §3.5 已定
13 每轮摘要日志 已定(2026-09-13):一行「轮次完成:N key,成功 X,失败 Y,新增 Z 条,耗时 W 秒」——见 §3.5 已定
14 「3 天窗口」警示 已定(2026-09-13):落后 > 24 小时则每轮打 warning(留 2 天人工余量);不做告警渠道——见 §3.5 已定

2.5 部署与初始化

# 议题 初步倾向 状态
15 QPU 基线 已定(2026-09-13):首次启动且 qpu_history 为空时插入当时取值——见 §3.6 已定
16 无 key 时 已定(2026-09-13):空转不报错;QPU 采样照跑(独立于 key 循环)——见 §3.6 已定
17 时间处理 已定(2026-09-13):上游 UTC 秒 → timestamptz;同步时刻取服务端时钟;输出统一 +08:00——见 §3.6 已定

3. 决策记录(逐条推导后回填)

1. 载体:后端进程内的定时器(2026-09-13 定)

  • 定时器跑在 API 进程内(后端单进程、单实例);不引入 worker 线程、不拆独立进程(2026-09-13 作者裁决:现在没必要)
  • 影响评估结论:一轮的 CPU 占用只在「JSON 解析 + 入库参数组装」两个小片段(各几十 ms 级、5 分钟级低频);且最重的活(SQL 聚合)在 PostgreSQL 进程里跑——对前端响应时间的影响可忽略
  • 升级路径(真需要时再走,现在不做):同进程 Worker 线程(先挪 parse / 组装)→ 独立进程

2. 调度节奏(2026-09-13 定,议题 #2–#4)

  • 周期 5 分钟:固定间隔 tick(与上一轮耗时无关);防重入——上一轮未结束时本轮跳过并记一条日志(避免轮次叠加)
  • 启动立即跑一轮:进程重启(含部署更新)后不等第一个 tick,尽快补齐窗口
  • 单轮内串行:全部 active key 逐个处理——对上游友好、日志按 key 顺序可读;数量增长到影响单轮时长时再评估并发

3. 每轮的动作(2026-09-13 定,议题 #5–#7)

  • 日志同步(#5):每 key 拉最近 1000 条(上游不支持参数、无法增量)→ 整批单事务入库(ON CONFLICT (dedupe_key) DO NOTHING)——全成或全不写:失败则 log_synced_at 不前移「数据截止」,下一轮幂等重来
  • 额度采集(#6):每 key 取上游字段全组 + usage_synced_at;与日志各自独立(先日志、后额度,两个独立 try——谁失败都不影响对方写自己的时间戳)
  • QPU 采样(#7):独立于 key 循环,每轮一次读 /api/status;值变化才追加一行(qpu_history §4)
  • 时间戳口径(#5/#6 共同):一律取该次请求的发起时刻——上游响应内容的上限是「处理请求那一刻」;请求发起至入库完成之间产生的日志不在本次响应内,不能许诺(与「数据截止取最慢」同一保守精神)

4. 失败与状态机(2026-09-13 定,议题 #8–#11)

  • 401 摘除阈值 N = 3:任一调用返回 401 → 本轮 consecutive_401s +1;成功即清零;其他失败不动计数。累计到 3 → status = disabled + disabled_source = auto_401(数据保留、不再轮询;复活走管理面启用 = 上游验证通过 + 计数清零)
  • 失败区分(#9):只有 401 是「key 失效」信号(上游实证:宽松档下过期 / 额度耗尽仍可查,唯一 401 = key 不存在 / 显式禁用);403 / 500 / 网络 = 其他故障,不计数
  • 不重试(#10):单轮内失败不立即重试,等下一轮(5 分钟后自然重试);3 天窗口余量下延迟无碍
  • 解密失败(#11):视为该 key 的故障——记服务器日志、跳过本轮、不影响其他 key;不计数、不摘除(非 401 语义),需人工重新登记

5. 可观测(2026-09-13 定,议题 #12–#14)

  • 错误日志(#12):人类可读一行——[sync] 前缀 + key_id / 阶段(日志·额度·QPU)/ 上游状态或错误 / 耗时;输出 stdout / stderr 由 systemd 收集。不引日志库、不输出 JSON(单人维护,journalctl 直读)
  • 每轮摘要(#13):一行——轮次完成:N key,成功 X,失败 Y,新增日志 Z 条,耗时 W 秒
  • 超期警示(#14):log_synced_at 落后超过 24 小时时每轮打一条 warning——距 3 天极限留 2 天人工介入余量;不做告警渠道(超期的可见性已由 UI 的 data_until 承担)

6. 部署与初始化(2026-09-13 定,议题 #15–#17)

  • QPU 基线(#15):首次启动时若 qpu_history 为空,插入当时 /api/status 取值(qpu_history §4)
  • 无 key 时(#16):空转不报错;QPU 采样照跑(它独立于 key 循环)
  • 时间处理(#17):上游 created_at(UTC Unix 秒)→ timestamptz;同步时刻类时间戳取服务端时钟;对外输出统一 +08:00(API 契约 约定 9)

4. 明确不做

  • 多实例协调(单进程部署,无并发实例)
  • 历史回填 / 补数(上游无此能力,1000 条窗口之外永久不可得)
  • 告警通知渠道(无通知对象与渠道;超期信息进服务器日志与 UI 的 data_until)