Skip to content

perf: 规划统一缓存层保护 dashboard / usage 重查询 #7

Description

@is7Qin

背景

线上性能与日志审计报告显示,当前卡顿的主因不是 Redis 被打穿,也不是单纯 Postgres 连接数不足,而是多个重接口在请求链路上直接聚合 usage_logs 明细大表(线上约 2,000 万行 / 19GB),再叠加日志表高写入和缓存覆盖不完整,导致 OLTP Postgres 同时承担明细库、报表库、日志库三种角色。

报告里的关键现象:

  • usage_logs 约 20.4M 行,dashboard / usage / billing 聚合慢查询等待 DataFileReadAioIoCompletionBufferMapping
  • /api/v1/auth/me 平均约 1.2s,用户感知像“登录状态掉了/页面卡住”。
  • 当前已有短 TTL 缓存和预聚合表,但覆盖碎片化:handler 级 snapshot cache、部分 Redis cache、部分 rollup 表各自为政;很多 miss 后仍回源扫大表。
  • 用户侧 dashboard 一次打开会并发调用 /auth/me/usage/dashboard/stats/usage/dashboard/trend/usage/dashboard/models/usage
  • 管理端批量用户/API Key 用量、组用量汇总、模型/组/用户/endpoint breakdown 等路径仍大量直接 FROM usage_logs 聚合。

提议:建设统一缓存层,而不是继续零散加缓存

建议引入一个项目内的统一缓存抽象,集中治理 dashboard / usage / billing / auth 这类高频读路径的缓存策略。

这不是要替代 rollup 表,也不是简单在每个 handler 手写 map + TTL。目标是把“怎么取缓存、怎么防击穿、怎么失效、怎么观测、怎么按部署规模选择后端”统一起来,让业务代码只声明 key、TTL、版本、回源函数和失效语义。

是否直接用现成库?

建议:底层可复用成熟库,但不要把业务直接绑死到某个缓存库 API。

原因:

  • 项目已有 Redis(github.com/redis/go-redis/v9),适合跨进程共享热数据和部署多实例场景。
  • 单进程短 TTL 缓存可以用现成 in-memory 库(如 Ristretto / ttlcache / expirable LRU),但 handler 直接依赖这些库会继续形成碎片化缓存。
  • 重查询需要 singleflight 防击穿、stale-while-revalidate、负缓存、按 tag 失效、metrics,这些通常需要项目级封装。
  • 用量统计最终应走 rollup / materialized view / Redis hot counter;统一缓存层只是在线读路径的保护层,不应掩盖明细表直接聚合的问题。

因此建议实现项目内 internal/cache 统一接口,底层 adapter 使用:

  • L1:进程内 TTL/LRU(可选 Ristretto 或 ttlcache)
  • L2:Redis(优先复用现有 go-redis client)
  • 防击穿:singleflight
  • 序列化:JSON/msgpack,按 value 类型封装
  • 指标:hit/miss/fill/error/stale/evict

目标

  1. 统一缓存 API:避免 handler/repository/service 各自实现 TTL map。
  2. 对重查询提供标准保护:TTL、jitter、singleflight、stale-while-revalidate、负缓存。
  3. 支持多级缓存:本地 L1 + Redis L2;单实例也可只启用 L1。
  4. 支持显式失效:按 key、prefix、tag/version 失效,避免“只能等 TTL”。
  5. 支持观测:每个 namespace 暴露命中率、回源耗时、错误率、stale 命中次数。
  6. 为 usage/dashboard/billing 接口迁移到 rollup/cache 提供统一落点。

非目标

  • 不在这个 issue 内完成所有 rollup 表设计。
  • 不把 usage_logs 大范围聚合永久包一层缓存后继续使用;重查询仍需要迁移到 rollup/materialized view。
  • 不引入外部缓存服务替代 Redis。
  • 不把 access/error 日志治理混入缓存层;日志采样和分区应另开 issue。

建议接口形态

type Cache interface {
    Get(ctx context.Context, key string, dest any) (bool, error)
    Set(ctx context.Context, key string, value any, ttl time.Duration, opts ...SetOption) error
    Delete(ctx context.Context, keys ...string) error
    DeletePrefix(ctx context.Context, prefix string) error
    GetOrLoad(ctx context.Context, key string, dest any, ttl time.Duration, load func(context.Context) (any, error), opts ...LoadOption) error
}

建议补充 namespace 封装:

type Namespace struct {
    Name       string
    Version    string
    DefaultTTL time.Duration
    Jitter     time.Duration
    Tags       []string
}

Key 约定:

sub2api:{env}:{namespace}:v{version}:{hash(args)}

第一阶段落地范围

优先迁移高频但语义清晰的读路径:

  • /api/v1/auth/me:短 TTL + 用户变更/登出失效。
  • /api/v1/usage/dashboard/stats
  • /api/v1/usage/dashboard/trend
  • /api/v1/usage/dashboard/models
  • /api/v1/usage/dashboard/api-keys-usage
  • /api/v1/admin/dashboard/users-usage
  • /api/v1/admin/groups/usage-summary
  • /api/v1/dashboard/billing/usage
  • /api/v1/dashboard/billing/subscription

建议每个 namespace 明确:

  • key 输入字段
  • TTL / jitter
  • 是否允许 stale
  • 回源超时
  • 失效触发点
  • 是否允许负缓存

验收标准

  • 新增 internal/cache 或等价包,提供统一接口和至少一个 Redis adapter / memory adapter。
  • 所有新缓存接入点通过统一层,不再新增散落的 handler 级 snapshot cache。
  • GetOrLoad 对同 key 并发请求只触发一次回源。
  • 缓存 key 有版本号,支持灰度修改 key schema。
  • 关键 namespace 有 metrics / 日志字段可观测 hit/miss/fill duration。
  • 至少迁移 2 个线上热点接口作为示范,并保留单元测试覆盖 hit/miss/singleflight/TTL/失效。
  • 文档说明:哪些接口仍会回源扫 usage_logs,哪些已受统一缓存保护,后续 rollup 迁移计划是什么。

风险与注意事项

  • 缓存不能成为数据正确性的唯一来源;账单结算仍应以持久化 rollup / 明细事实为准。
  • TTL 不能替代失效策略,用户权限、API key、账号状态、价格配置变化需要主动 bump version 或 delete tag/prefix。
  • 避免缓存巨型响应;对大列表和高维过滤组合应优先改 rollup/query shape。
  • 对错误结果只做短负缓存,避免把临时 DB/Redis 错误放大成持续不可用。
  • 多实例部署下不要只依赖本地内存缓存。

关联报告摘录

审计报告结论:当前项目不是完全没有缓存,而是缓存做得碎:

  • dashboard 主统计 Redis cache TTL 只有 30s。
  • handler 级 snapshot cache 多为进程内 30s。
  • 预聚合表维度不够,过滤场景大量回落 usage_logs
  • 线上高频 usage/dashboard/billing 接口持续触发 Postgres IO 压力。

统一缓存层应作为后续 usage rollup、日志降噪、分区生命周期治理的配套基础设施。

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestperformancePerformance, scalability, and database load improvements

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions