Skip to content

降低 ops_error_logs 与 scheduler_outbox 的数据库写放大#936

Merged
Wei-Shaw merged 2 commits intoWei-Shaw:mainfrom
xvhuan:fix/ops-write-pressure-20260311
Mar 12, 2026
Merged

降低 ops_error_logs 与 scheduler_outbox 的数据库写放大#936
Wei-Shaw merged 2 commits intoWei-Shaw:mainfrom
xvhuan:fix/ops-write-pressure-20260311

Conversation

@xvhuan
Copy link
Contributor

@xvhuan xvhuan commented Mar 11, 2026

背景

线上排查里,数据库主要压力已经不只是业务主表,还包括两条高频写路径:

  1. ops_error_logs 在错误风暴时按条落库,虽然已经异步化,但仍然是逐条 INSERT
  2. scheduler_outbox 会被很多幂等状态变化反复写入,尤其同一个账号/分组在短时间内连续触发同类事件时,会放大 outbox 表和后续消费压力

这次 PR 的目标是先把这两条路径的写放大收下来,同时尽量不改业务语义、不改接口协议、不改调度逻辑口径。

改动内容

1. ops_error_logs 改为批量写入

  • OpsErrorLogger 的 worker 不再逐条调用 RecordError
  • 在 worker 内增加短时间窗口聚合,按 service 分组后批量调用 RecordErrorBatch
  • OpsService 增加批量准备和批量落库入口,复用单条日志原有的清洗逻辑
  • opsRepository 增加 BatchInsertErrorLogs,使用单事务批量写入
  • 如果批量写失败,会自动回退到逐条插入,避免为了削峰牺牲可用性

2. scheduler_outbox 对幂等事件做短窗口去重

只对这三类幂等事件启用 1 秒窗口内的去重:

  • account_changed
  • group_changed
  • full_rebuild

实现方式不是进程内缓存,而是数据库侧 INSERT ... SELECT ... WHERE NOT EXISTS (...),避免多实例或事务回滚时出现本地去重误判。

明确不去重的事件仍保持原样:

  • account_last_used
  • account_groups_changed
  • account_bulk_changed

也就是只收紧“重复写但最终效果等价”的事件,不碰带 payload 语义的事件。

为什么这样做

  • ops_error_logs 批量写入可以减少事务数和 WAL/fsync 压力,但不减少日志条数,不影响错误排查和统计语义。
  • scheduler_outbox 的幂等事件本来就是“通知调度器去读最新状态并重建快照”,同一账号/分组在 1 秒窗口内反复写同类事件,保留第一条就足够让消费器拿到最终状态。
  • 这次没有直接改 accounts.extra 的业务规则,也没有去动 usage_logs 主链路,是为了控制回归面。

风险控制

  • 不改数据库 schema。
  • 不改接口协议和返回结构。
  • 不改变 ops_error_logs 的单条清洗规则。
  • 批量写入失败时保留逐条插入回退。
  • 只对明确幂等的 outbox 事件做去重,保留带 payload 语义的事件原行为。

测试

本地静态与单测

  • docker run --rm -v /home/ius/sub2api/backend:/src -w /src golangci/golangci-lint:v2.9.0 golangci-lint run --timeout=30m
  • docker run --rm -v /home/ius/sub2api/backend:/src -w /src golang:1.26.1 /usr/local/go/bin/go test -tags=unit ./...

integration

  • docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /home/ius/sub2api/backend:/src -w /src golang:1.26.1 /usr/local/go/bin/go test -tags=integration ./internal/repository -count=1

新增覆盖:

  • ops_error_logs 批量插入
  • scheduler_outbox 幂等事件去重
  • account_last_used 不应被去重
  • RecordErrorBatch 的清洗与失败回退

本地 Docker 烟测

  • docker compose -f /tmp/sub2api-prtest-compose.yml up -d --build
  • GET /health 返回 200
  • GET /api/v1/settings/public 返回 200
  • POST /api/v1/auth/login 返回 200
  • GET /api/v1/admin/dashboard/snapshot-v2 返回 200
  • GET /api/v1/admin/dashboard/users-trend 返回 200

本地小并发验证

  • settings/public 并发 30 请求:全部 200
  • snapshot-v2 并发 20 请求:全部 200

fork CI

  • CI:通过
  • Security Scan:通过

@Wei-Shaw Wei-Shaw merged commit 30995b5 into Wei-Shaw:main Mar 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants