Files
cpa-plugin/docs/modules/statistics.md
T

15 KiB
Raw Blame History

统计分析模块

1. 定位

统计分析模块是 cpa-ext 的数据处理层。它把已经持久化的请求、执行、用量、账单和账本事实,转换为可查询、可重建、适合 UI 展示的统计结果。

它回答以下问题:

  • 一段时间内消费了多少钱、发生了多少请求;
  • 金额、请求、Token 和性能随时间如何变化;
  • 哪些 Key、用户、模型和上游账户产生了消费;
  • 成功率、错误、TTFT、Latency 和缓存情况如何;
  • 原始事实是否已被聚合、统计是否滞后、是否存在无法核算的数据。

它不负责:

  • 从 CPA 回调取得事实;
  • 决定请求是否放行;
  • 选择上游账户;
  • 计算一笔请求应该扣多少钱;
  • 修改账单、余额或账本。

最重要的边界是:统计模块只能汇总计费模块已经确定的金额,不能根据 Token 和当前价格重新计算历史费用。

2. 输入、处理与输出

Request / Execution / Usage / Billing / Ledger / Identity facts
                         │
                         ▼
        增量读取 → 维度规范化 → 时间分桶 → 聚合计算
                         │
                         ▼
          Rollup rows + independent checkpoints
                         │
                         ▼
        Query services → permission projection → UI/API DTO

2.1 输入事实

输入 用途 权威来源
RequestRecord 请求数量、终态、入口、下游身份、请求时间 采集与 Core
ExecutionRecord 重试、上游账户、实际模型、性能、失败 采集与 Core
UsageRecord Token 分项、数据质量、generate 状态 采集模块
BillingRecord 请求对应的已确认金额、价格快照引用、核算状态 计费模块
LedgerEntry 实际扣款、补记、退款、充值和管理员调整 计费模块
IdentityRecord Key、账户、Auth、Provider 的稳定展示维度 数据/目录模块
QuotaSnapshot 上游配额与订阅的时间点分析 采集模块

所有输入必须已经落入持久化层。进程内通知只负责唤醒处理器,不能作为唯一事实来源。

2.2 处理结果

统计处理产生两类数据:

  • AggregateRecord:小时、日、活动度、延迟等可重建聚合;
  • 查询投影:把事实、聚合、目录和权限组合成 API/UI 需要的只读结果。

聚合数据不是账本真相。它可以删除、重建和升级;任何重建结果都必须与原始事实和账本一致。

3. 统一统计口径

3.1 请求、执行和账单不能混为一层

  • 用户请求数按 RequestRecord 计数;
  • 上游尝试数按 ExecutionRecord 计数;
  • 上游失败率可以按 Execution 计算;
  • 用户看到的请求结果按 Request 最终终态计算;
  • 金额按属于该 Request 的有效 LedgerEntry 净额汇总。

一次请求发生三次重试时,用户请求数是 1,上游执行数是 3;如果三个执行都产生了可可靠关联的上游消费,管理员审计必须能看到三次成本事实。当前 CPA 无 RequestID 的 attempt usage 无法安全归属时,执行仍计数,但金额覆盖率通过 unmeasured 单独表达。

3.2 金额口径

金额统计统一使用 int64 amount_micros

请求最终消费 = Σ usage-linked `spend_delta_micros`

其中 charge/late settlement 为正、Usage refund 为负;账户充值和普通余额 adjustment 的 spend delta 为 0。余额则单独由 balance_delta_micros 推导:扣费为负、充值/退款为正。二者都以金额表示,但不能混为同一个统计口径。

禁止:

  • 使用 float64 累计余额;
  • 查询时按当前价格重算历史 Usage
  • 因价格目录更新而改变已经结算的趋势;
  • 把未定价或未取得 Usage 自动视为确认的免费请求。

用户视图只返回金额;管理员视图可以同时返回 Token、单价、倍率、核算质量和价格版本。

3.3 取消、失败和迟到结算

请求终态与金额是两个正交维度:

  • outcome=canceled/failed 但存在可靠 Usage:金额进入正常统计;
  • outcome=canceled/failed 且无可靠 Usage:当前金额为 0,同时计入 unmeasured_count
  • 迟到 Usage 产生 late_settlement:用差额账本更新原请求所属的业务时间桶;
  • 账本的 booked_at 同时保留,用于管理员审计“何时发现并补扣”。

因此查询可以同时提供:

  • occurred_amount:按原请求时间归属的消费趋势;
  • booked_amount:按实际入账时间归属的账本变化。

用户消费趋势默认使用 occurred_amount;账本审计默认按 booked_at 排序。

3.4 时间和区间

  • 事实时间保存 UTC
  • 所有查询使用半开区间 [start, end)
  • 小时桶以配置时区的钟面整点为边界;
  • 日桶以配置时区的本地自然日为边界;
  • DST 日期允许出现 23 或 25 小时,不能固定假设一天等于 24 小时;
  • 修改统计时区需要显式重建依赖本地边界的聚合。

4. 目标聚合集合

4.1 money_overview_hourly / money_overview_daily

这是用户和管理员金额页面的核心聚合。

维度按实际查询需求控制:

  • downstream account / credential
  • model / model alias
  • provider / upstream auth
  • effective service tier
  • outcome
  • endpoint。

指标:

  • request count、success/failure/canceled/rejected count
  • charge、late-settled、Usage refund/correction 的 spend delta micros
  • final charged amount micros
  • measured/partial/unmeasured count
  • input/output/cache/reasoning Token(仅管理员)。

不要把所有维度机械组合到一张极宽聚合表。MVP 先覆盖 UI 已经确定的筛选;低频组合可以查日级聚合或受限事实明细。

账户充值和非 Usage 人工余额调整只进入 balance/ledger 投影,不具有 model/Auth/endpoint,不能强行归属到本表这些请求维度。

4.2 request_health

按时间桶和必要维度保存:

  • 用户请求数与上游执行数;
  • 成功、失败、取消、拒绝;
  • HTTP/标准错误类别;
  • RPM
  • 取消率、无核算率、价格不可用率。

RPM 是所选窗口请求数除以窗口分钟数。空窗口返回 0;部分窗口必须使用真实覆盖分钟数,不能按完整日除。

4.3 usage_activity

参考 cpa-usage-keeper 的 Activity 设计,提供:

  • 日内细粒度活动;
  • 7 天和 30 天活动;
  • 长期自然日活动;
  • 成功/失败以及 Token 分项。

第一版可以简化为小时和日两级;但边界函数必须只有一套实现,并同时被聚合、查询和测试使用。

4.4 latency_stats

只把满足条件的真实生成请求作为性能样本:

  • 成功执行;
  • generate=true
  • TTFT、总延迟为有效正值。

保存:

  • sample count
  • TTFT/Latency 可合并分位 sketch
  • P50/P95/P99
  • 精确最大值;
  • 有界、稳定、按 EventID 去重的真实散点样本。

不要只保存平均值,也不要为图表永久保存无限散点。

4.5 identity_stats

按下游账户、Key、模型、上游 Auth 和 Provider 提供:

  • 请求与执行数量;
  • 金额;
  • 最近使用时间;
  • 成功率和取消率;
  • 管理员 Token 与性能摘要。

目录名称变化只改变展示解析,不应重写历史事实中的稳定 ID。

5. 增量处理与 checkpoint

所有领域事务向统一 projection_events 追加变化。该表包含 event_seq INTEGER PRIMARY KEY AUTOINCREMENTevent_id UNIQUE、kind、fact ID/revision、occurred/booked time、可选 supersedes ID 和确定性 delta payload。外部 UUID/EventID 用于业务幂等,event_seq 用于稳定分页和 checkpoint;不能拿时间戳、随机 UUID 或各事实表互不相关的自增 ID 充当全局聚合游标。

状态修订必须发布逆向/正向差量。例如 unmeasured 在迟到 Usage 后变为 measured 时,同一 projection event 表达 unmeasured_count=-1measured_count=+1 和新增 spend_delta_micros,不能只新增 measured 行让请求被计两次。领域事实与 projection event 必须在同一 SQLite 事务提交。

每种聚合拥有独立 checkpoint,例如:

money_overview
request_health
activity
latency
identity

处理规则:

  1. 新事实事务提交后,只发送非阻塞唤醒和最大 event_seq
  2. runner 冻结本轮目标上界,按固定页大小读取事实;
  3. 纯函数在内存中生成确定性的增量行;
  4. 聚合 upsert 与 checkpoint 推进处于同一短事务;
  5. 单类失败只阻止自己的 checkpoint,不冻结其他聚合;
  6. 启动时从数据库最大事实序号和各 checkpoint 自动追平;
  7. SQLite 有前台事实等待写入时,聚合主动让出唯一 writer。

同一输入页重复执行必须得到相同结果。推荐使用 (aggregate_kind, source_event_seq) inbox/应用记录或等价事务约束,确保崩溃发生在 upsert 与推进之间时仍可安全重试。

动态库内后台 goroutine 在目标 CPA 的双 Go runtime 下尚未证明安全。runtime soak gate 通过前,runner 由安全的 host callback/管理查询按时间和页数预算增量驱动,或运行在 sidecar;统计算法与 checkpoint 不依赖“常驻 goroutine 一定存在”。

6. 实时查询

实时视图由两部分组成:

已聚合到 checkpoint 的稳定数据
  + checkpoint 之后的近期事实右边界补偿

近期缓存只是性能优化:

  • 缓存丢失时回退到 SQLite
  • 缓存项必须来自已提交事务;
  • 查询以 checkpoint/event_seq 切开两段,不能重复累计;
  • 页面不可见时停止轮询;
  • 实时窗口默认 5~15 分钟,刷新间隔由 UI 控制。

每个 widget 返回自身 projector 的 as_of_seq 和 lag。一个组合响应若要求强一致,使用所有相关 checkpoint 的最小值作为共同 as_of_seq,并分别从该位置做右边界补偿;不能拿 money checkpoint 切 latency/identity 数据。

7. 查询服务

统计模块向 Core/管理 API 提供只读服务,不直接处理 HTTP:

type StatisticsService interface {
    Overview(ctx context.Context, scope QueryScope, filter OverviewFilter) (Overview, error)
    Realtime(ctx context.Context, scope QueryScope, filter RealtimeFilter) (Realtime, error)
    Analysis(ctx context.Context, scope QueryScope, filter AnalysisFilter) (Analysis, error)
    Activity(ctx context.Context, scope QueryScope, filter ActivityFilter) (Activity, error)
    Events(ctx context.Context, scope QueryScope, filter EventFilter) (EventPage, error)
    Diagnostics(ctx context.Context) (AggregationDiagnostics, error)
}

QueryScope 必须由 Core 根据已认证主体构造:

  • 用户 scope 只能查询自己的 account/credential,且 DTO 只含金额;
  • 管理员 scope 才能指定任意 Key/Auth/model 并查看 Token、价格与核算质量;
  • Repository 不能信任浏览器传来的 account ID;
  • 分页、时间上限、导出数量和查询复杂度必须由服务端限制。

交付阶段固定为:

  • MVPmoney hourly/daily(合并基本 request outcome/quality count)、用户金额摘要/趋势/最近请求、管理员 Overview、请求事件分页和一个统一 money checkpoint
  • 第二阶段:独立 request health、Activity、latency sketch/散点、identity rollup、Key/模型/Auth 构成、realtime cache
  • 数据量达到实测阈值后:冷归档、影子表重建、排名和高维分析。

所有阶段都保留完整底层事实;延后页面能力不等于丢弃数据。

8. 重建、修正和归档

  • 所有聚合必须支持从 0 全量重建;
  • 也可按聚合类型或时间范围重建;
  • 重建写入影子表/新版本后原子切换,避免页面看到半成品;
  • 聚合 schema/算法版本必须记录;
  • 事实修正只能追加 revision/correction,不能原地篡改已入账历史;
  • MVP 不物理归档 Request/Execution/Usage 最小事实;账本引用关系先保持完整;
  • 后续若引入冷归档,所有依赖 checkpoint 必须追过归档上界,统一 view 仍能读取重建来源,不能只保留聚合后删除账本依据。

价格变化不触发历史金额重建。只有数据修复或追加账本会改变金额聚合。

9. 数据质量与诊断

统计服务必须公开:

  • 各 checkpoint 当前值、目标值和 lag
  • 最后成功/错误时间与脱敏错误;
  • measured、partial、unmeasured、inconsistent 数量;
  • 未定价、迟到结算和冲正数量/金额;
  • 近期缓存是否降级;
  • 聚合版本与是否正在重建。

用户视图不展示内部 Token 或价格错误细节;用户只看到本次金额是否“待核算/已补记”。管理员可以查看完整质量原因。

10. 如何参考现有项目

cpa-usage-keeper 是本模块的主要实现参考:

主题 源码位置
Overview 小时/日聚合纯函数 cpa-usage-keeper/internal/overview/aggregate.go
Activity 多粒度边界与聚合 internal/activity/grain.goaggregate.go
延迟 sketch 与稳定抽样 internal/latency/aggregate.gosketch.gosample.go
独立 checkpoint 和公平 runner internal/poller/usage_aggregation_runner.go
聚合实体 internal/entities/usage_*_stat.gousage_aggregation_checkpoint.go
查询与右边界补偿 internal/repository/usage_overview_stats.gousage_recent_event_cache.go
Analysis 与事件查询 internal/repository/usage_analysis_projection.gousage.go
服务层投影 internal/service/usage.go
API 输出 internal/api/usage_overview.gousage_analysis.gousage_events.go

应该吸收:确定性聚合、独立 checkpoint、批量追赶、实时补偿、分位 sketch、有界样本、查询服务分层和丰富的测试。

不能直接照搬:

  • 查询时依据当前价格重算历史费用;
  • 把 Token 作为普通用户的核心展示单位;
  • 所有分析功能一次性进入 MVP
  • 为每个 UI 组合增加写放大很高的索引或聚合维度。

cpa-plugin-key-billing 只用来核对账单输出字段和简单累计口径,不作为统计架构参考。其状态内累计和 30 天日志不能替代事实表、聚合表和 checkpoint。

11. 验收标准

  • 同一事实集全量构建与任意批次增量构建结果一致;
  • 重复通知、重复执行、崩溃恢复不会重复累计;
  • MVP 统一 money checkpoint、后续各聚合 checkpoint 可以按阶段独立失败和恢复;
  • 请求数、执行数和账单数不会混淆;
  • 金额直接来自不可变账本,价格更新不会改变历史;
  • 取消/失败的可靠 Usage 被计入金额,无 Usage 被计入 unmeasured
  • 迟到结算按原请求时间修正消费趋势,同时保留真实入账时间;
  • 用户查询只能看到自己的金额投影;
  • 管理员 Overview、Events、账本和余额在相同过滤范围内可核对;
  • DST、空窗口、部分窗口、大范围查询和归档边界有测试;
  • 百万级事实容量下,前台写入不会被聚合长事务持续阻塞。