Files
junhong_cmp_fiber/docs/feature-504-multi-view-audit-center/归档灰度操作手册.md
break c64f3d8b80
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m31s
全局审计完成
2026-08-07 11:02:52 +08:00

11 KiB
Raw Blame History

审计归档灰度操作手册

灰度开关

灰度期必须保持:

JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED=false

该配置使每月任务和人工投递的 audit:monthly:retention 只执行只读门禁演练,不写清理断点、不删除在线数据。每日 Audit/Integration 归档和 Integration 月度最终复核不受影响。

完整自然月可以在显式确认的隔离测试数据库中按真实日界构造,无需等待现实时间流逝。测试环境的完整月 dry-run 全部通过后,才可临时启用清理验证删除范围;生产环境仍须由发布决策将开关改为 true。关闭开关并重启 Worker 即可回滚;不得手工删除在线日志或对象存储归档。

隔离测试环境完整月仿真

仓库提供 cmd/audit-retention-simulate,固定使用 2001-02 和专属实例标识。命令仅允许数据库名包含 test,且确认值必须与当前数据库名完全一致;目标月已有任何 Audit、Integration 或归档账本时立即拒绝,避免误清理已有数据。

source .env.local
JUNHONG_AUDIT_RETENTION_SIMULATION_CONFIRM="$JUNHONG_DATABASE_DBNAME" \
  go run ./cmd/audit-retention-simulate

命令为目标月每日构造一条 Audit Event、Event Resource 和 Integration Log并在月初前一秒与下月零点各放置一组边界哨兵。流程依次执行每日归档、对象和 manifest 回读、故障重试、Integration 最终 revision、清理关闭 dry-run、清理断点核对、测试库物理清理及边界复核。Redis 不参与该留存流程。

每日观测

以下查询只读取归档运行账本。将日期替换为待验收自然月的半开区间:

SELECT
    archive_date,
    source,
    status,
    attempt_count,
    revision,
    is_final,
    event_count,
    resource_count,
    record_count,
    uncompressed_bytes,
    compressed_bytes,
    CASE
        WHEN uncompressed_bytes = 0 THEN 1
        ELSE ROUND(compressed_bytes::numeric / uncompressed_bytes, 4)
    END AS compression_ratio,
    sha256,
    object_key,
    manifest_key,
    error_summary,
    completed_at,
    cleanup_started_at,
    cleaned_at
FROM tb_log_archive_run
WHERE archive_date >= DATE '2026-07-01'
  AND archive_date < DATE '2026-08-01'
  AND source IN ('audit', 'integration')
ORDER BY archive_date, source;

每日必须记录两类归档是否成功、重试次数、完成时间、对象大小、压缩率、SHA-256、Audit 事件/资源数量、Integration 记录数量及 revision。对象 metadata、manifest 和 gzip 实际 SHA-256/计数差异以归档任务自身复核结果及日志为准,不能只看账本字段。

月度汇总与积压

WITH expected AS (
    SELECT day::date AS archive_date, source
    FROM generate_series(DATE '2026-07-01', DATE '2026-07-31', INTERVAL '1 day') AS day
    CROSS JOIN (VALUES ('audit'), ('integration')) AS sources(source)
)
SELECT
    COUNT(*) AS expected_runs,
    COUNT(r.id) FILTER (WHERE r.status = 'success') AS successful_runs,
    ROUND(COUNT(r.id) FILTER (WHERE r.status = 'success')::numeric / COUNT(*), 4) AS success_rate,
    COUNT(*) FILTER (WHERE r.id IS NULL OR r.status <> 'success') AS backlog_runs,
    COALESCE(SUM(r.compressed_bytes), 0) AS object_bytes,
    COALESCE(SUM(r.uncompressed_bytes), 0) AS source_bytes,
    CASE
        WHEN COALESCE(SUM(r.uncompressed_bytes), 0) = 0 THEN 1
        ELSE ROUND(SUM(r.compressed_bytes)::numeric / SUM(r.uncompressed_bytes), 4)
    END AS compression_ratio,
    COALESCE(MAX(r.revision), 0) AS max_revision,
    COALESCE(SUM(r.attempt_count), 0) AS attempts
FROM expected e
LEFT JOIN tb_log_archive_run r
  ON r.archive_date = e.archive_date
 AND r.source = e.source
 AND r.instance_id = 'primary';

Integration 月度最终复核后,目标月所有 source='integration' 记录必须同时满足 status='success'is_final=true,且无 pending 阻断日志。灰度期开关关闭时,目标月所有 cleanup_started_at/cleaned_at 必须为 NULL

清理 dry-run 与耗时估算

dry-run 只执行现有月度门禁的只读核对:完整月每日 ledger/manifest、对象 metadata、对象大小、SHA-256、Audit 事件与资源计数、Integration 最终 revision。生产和共享灰度环境禁止调用物理清理方法验证 dry-run显式确认的隔离测试数据库必须在 dry-run 通过后执行一次物理清理,用于证明删除范围。

先统计预计删除行数和 1000 行批次数:

WITH counts AS (
    SELECT
        (SELECT COUNT(*) FROM tb_audit_event WHERE created_at >= TIMESTAMPTZ '2026-07-01 00:00:00+08' AND created_at < TIMESTAMPTZ '2026-08-01 00:00:00+08') AS audit_events,
        (SELECT COUNT(*) FROM tb_audit_event_resource r JOIN tb_audit_event e ON e.id = r.audit_event_id WHERE e.created_at >= TIMESTAMPTZ '2026-07-01 00:00:00+08' AND e.created_at < TIMESTAMPTZ '2026-08-01 00:00:00+08') AS audit_resources,
        (SELECT COUNT(*) FROM tb_integration_log WHERE created_at >= TIMESTAMPTZ '2026-07-01 00:00:00+08' AND created_at < TIMESTAMPTZ '2026-08-01 00:00:00+08') AS integration_logs
)
SELECT
    *,
    CEIL(audit_events / 1000.0) + CEIL(audit_resources / 1000.0) + CEIL(integration_logs / 1000.0) AS estimated_batches
FROM counts;

清理预估耗时为 estimated_batches × 灰度环境单批删除 P95,并额外预留 30%。未取得真实单批 P95 前不得启用清理。

告警与启用门禁

  • 每日 08:00 前任一前日 Audit/Integration 记录缺失、失败或仍为 runningcritical。
  • running 持续超过 3 小时、attempt_count > 1 或对象/manifest 复核失败critical。
  • 有数据时压缩率不在 (0, 1]、SHA-256 为空或对象大小为零critical。
  • 月度最终复核后任一 Integration 记录 is_final=false 或存在 pendingcritical。
  • 只归档模式出现任一 cleanup_started_atcleaned_atcritical立即停 Worker 并调查。
  • 完整月成功率必须为 100%积压、hash 差异、计数差异和未终结 Integration 数必须均为 0。
  • 故障重试、月度只读 dry-run、启停与关闭回滚均须保留时间、环境、操作者、日志位置和结果证据。

任何一项未通过时保持生产开关关闭,修复后重新执行完整自然月仿真;不得以补写验收记录替代真实归档、对象复核和删除范围核对。

全局审计切换监控阈值

contract 切换期按以下阈值观测;任一 critical 条件命中时停止扩大发布范围,保留已提交事实并前向修复。

观测项 阈值 证据来源与处置
关键成功审计写入 任一失败即 critical 业务与 Audit Event 同事务回滚;按 action/request/correlation 定位异常生产者,不得补写成功事件。
失败/拒绝审计二次写入 secondary_write_failure_count 增量必须为 0 监控“失败或拒绝审计二次写入失败” critical 日志;保留原业务错误。
未注册 action/resource/role 数量必须为 0 运行 go run ./cmd/audit-coverage;任一未注册项阻断发布。
安全凭据落库或响应命中 数量必须为 0 使用禁止键/凭据值受控抽样;发现后停止相关生产者并前向修复,不修改已提交审计事实。
批量根子计数差异 非法统计、缺少父事件或 correlation 数必须为 0 根事件 success_count/fail_count 与可查子事件不一致即 critical暂停该批量用例。
旧 Writer 调用 生产写调用必须为 0 覆盖门禁只允许旧资产历史查询和手动轮询运行 ledger 白名单;命中 Create/Update/裸 goroutine 立即阻断发布。
数据库审计查询 单条受控查询 < 50ms 使用在线数据的 EXPLAIN (ANALYZE, BUFFERS) 观测;超阈值先检查索引和无界时间范围,不直接增加 Redis 缓存或分区。
审计读接口 P95 < 200msP99 < 500ms 使用现有 Access Log 统计真实流量;本 Change 不新增或运行自动化测试。

contract 发布与回滚

  1. 发布前确认数据库迁移版本为 205dirty=false,清理开关保持关闭,覆盖门禁中的旧 Writer 生产调用为 0。
  2. 发布异常时先停止扩大流量,暂停明确异常的生产者或 Worker不删除当前在线 Audit Event、Integration Log、Domain Ledger、Outbox 或已校验对象存储归档。
  3. 应用回滚只能回到“旧 Writer 已停写”的 contract 基线版本。不得回到恢复 tb_account_operation_log/tb_asset_operation_log 生产写入或裸 goroutine 审计的版本;无合法基线时只做前向修复。
  4. 有任何 Audit Event 或归档 ledger 事实的环境不执行 000199/000205 down有 Outbox parent、长 correlation 或订单预占事实的环境不执行 000200/000201/000203 down。业务回滚保留版本 205 结构,不以 migration down 代替应用回滚。
  5. 需要暂停物理清理时,将 JUNHONG_WORKER_AUDIT_RETENTION_CLEANUP_ENABLED=false 并重启 Worker每日归档和新业务审计继续运行。
  6. 已清理月份不从对象存储回填 PostgreSQL不伪造在线历史查询仍按 retention 边界返回已归档错误。错误业务结论通过新的受控业务动作和新 Audit Event 前向修正,不改写原事实。
  7. 回滚后重新运行覆盖门禁、路由/OpenAPI 扫描、数据库凭据抽样和归档账本核对;任一旧 Writer 新增、在线事实减少或归档对象丢失都阻断结案。

迁移演练约束

  • 本 Change 的增量迁移范围为 000199~000205。共享 .env.local 测试库已有 Audit 和归档事实,不允许直接 down。
  • down 只能在无事实的隔离 schema/数据库执行;000200000203 会删除业务快照列,不得对有事实环境演练。
  • 000199_create_audit_event.down.sql 自带事务边界,不应依赖外层事务自动清理隔离 schema演练结束必须显式核对并删除精确的隔离对象。
  • 2026-08-06 在 .env.local 测试 PostgreSQL 中对无事实隔离 schema 完成 000199~000205 up/down上行后 Audit/归档表存在,逆序 down 后表和增量列均恢复;隔离 schema 已显式删除,public 保持版本 205dirty=false

2026-08-06 仿真记录

  • 环境:.env.localjunhong_cmp_test 测试数据库及其对象存储。
  • 月份:2001-02,共 28 个完整自然日;归档账本 56 条,成功 56 条。
  • Integration 最终 revision28/28故障重试后最大 revision=2、最大 attempt_count=2。
  • 压缩率0.5047dry-run 时目标月 Audit Event/Event Resource/Integration Log 各 28 条,预计 3 个 1000 行删除批次,且清理断点为 0。
  • 物理清理后目标月三表均为 0月前一秒和下月零点的 Audit Event、Event Resource、Integration Log 共 6 条全部保留。
  • 目标月 56 条账本均具有 cleanup_started_atcleaned_at;当月生成 1 条 evt_retention_2001_02 清理审计;对象归档及 manifest 未删除。