Files
OpenFlare/.agents/skills/logstore/SKILL.md
T
ryan b66cf3ae9c feat(log): PG 分区清理与 logstore import-lint
CleanupExpired 先按月 DROP 过期分区,再删边界行并清理空分区;apps 禁止直连 analytics。
2026-08-16 16:48:15 +08:00

5.1 KiB
Raw Blame History

name, description
name description
logstore Wavelet 项目专用:当新增或修改日志/分析用途表(访问日志、审计流水、可观测时序)、接入 internal/repository/logstore、切换日志主库、实现 PG/SQLite 回落,或判断一张表该走业务主库还是日志库时必须使用。

日志用途表开发

开始前阅读根目录 AGENTS.md。DDL 用 database-migration;高频写入队列用 clickhouse-batchwriter;切换任务用 new-async-task。本技能只回答:这张表是不是日志表,以及如何接入可切换的日志主库。

分层与切换协议见 日志用途表。

先判定

日志表同时满足:

  • 追加写入、几乎不更新单行
  • 按时间查询/聚合,允许按保留天数删除
  • 关闭 ClickHouse 后仍要能写、能查
  • 不参与用户/配置/任务等事务一致性

不要做成日志表:用户、配置、任务执行、上传元数据、需要事务或强一致的业务实体。这些走主库 repository,不要进 logstore。

当前框架已接入的日志表:w_user_access_logs(管理端 API 访问审计)。

分层

层级 路径 职责
抽象 internal/repository/logstore 接口 + Active/BuildForMigration;apps 只面向这里
CH 实现 logstore 委托 internal/repository/analytics 原生 PrepareBatch / ChDB 查询
主库实现 logstore GORM PG(按月分区)与 SQLite(普通表)
Model internal/model/analytics 实体、TableName、InsertColumns、BatchInsertSQL,无 IO
入队 internal/apps/<domain> + batchwriter FlushFunc 调 logstore.Active().….BatchInsert
切换 internal/apps/admin/logs 的 logs:db_switch 冻结写入 → 排空 → 复制 → 翻转 log_database
清理 logstore.CleanupExpired,由 system:cleanup 调用 按库读取保留天数后 DeleteBefore

log_database ∈ {postgres,sqlite,clickhouse},且只能是「随主库」或 ClickHouse:主库为 PG 时日志不能是 SQLite,反之亦然。log_database / log_db_migration 受保护,禁止管理端手动改。

新增一张日志表

按顺序做,列名三库必须一致。

  1. Model
    在 internal/model/analytics/ 定义 struct;实现 TableName();批量写再提供 InsertColumns() / BatchInsertSQL()。

  2. 三套 DDL(database-migration)

    • ClickHouse:goose/clickhouse/,MergeTree,PARTITION BY toYYYYMM(时间列)。
    • PostgreSQL:goose/postgres/,高频表用 PARTITION BY RANGE (时间列),复合主键必须包含分区键。
    • SQLite:goose/sqlite/,普通表 + 时间/过滤列索引。
      不要在 PG/SQLite 上复制 CH 物化视图;聚合在查询时实时算。
  3. logstore 接口
    在对应 Store(现有 UserAccessLogStore,或新域自建接口并挂到 Store)补齐至少:

    • 写入:BatchInsert(flush 目标;内调 ensureWritable)
    • 查询:业务需要的 List/Count/聚合
    • 迁移:ListForMigration(afterID, limit)、MigrationRange、DeleteAll、EnsurePartitions(PG 按月预建,CH/SQLite no-op)
    • 清理:DeleteBefore(cutoff)、DropEmptyPartitions、DropExpiredPartitions(仅 PG;CH/SQLite no-op)
  4. 双实现

    • CH:委托 analyticsrepo,零额外查询路径。
    • GORM:PG/SQLite 共用一套;方言 SQL 只放小函数(如按日 to_char / strftime)。零值 id 落库前用 idgen.NextUint64ID()。
  5. buildStore
    在 provider.go 的 CH / GORM 分支同时挂上新域。

  6. 写入
    apps 用独立 batchwriter 实例;FlushFunc → logstore.Active(ctx) → BatchInsert。禁止 analyticsrepo.BatchInsert、禁止 db.ChConn。迁移任务调用域的 Drain(等队列空一个 flush 周期,不要 Stop writer)。

  7. 切换任务
    在 copy* 流程增加该表:DeleteAll 目标 → MigrationRange + EnsurePartitions → 按 id 分页复制。不要改切换协议(仍冻结写入、源数据不删、成功才翻转)。

  8. 清理
    CleanupExpired:PG 先 DropExpiredPartitions(整月过期分区),再 DeleteBefore(边界月),最后 DropEmptyPartitions。保留天数用已有 log_retention_days_*。apps 禁止 import repository/analytics(imports_test.go)。

禁止

  • apps 直接 import internal/repository/analytics 或 db.ChConn / db.ChDB 做日志读写
  • 只建 CH 表、不建 PG/SQLite 回落
  • 在 Handler 里逐条 PrepareBatch + Send
  • 把业务表「顺便」放进 logstore 以便关 CH
  • 管理端 API 改 log_database / log_db_migration

验证

go test ./internal/repository/logstore ./internal/repository/analytics
go test ./internal/apps/admin/logs ./internal/apps/risk_control ./internal/platform/bootstrap
make swagger   # 若改了状态/查询 API
make code-check

对照:w_user_access_logs 的 model、三库 goose、logstore GORM/CH、risk_control.InitLogWriter、logs.LogDBSwitchHandler、system:cleanup。