ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

agentsview 聚合用量缓存:让 30 天 Token 统计从全量扫描降到亚秒级

agentsview 聚合用量缓存:让 30 天 Token 统计从全量扫描降到亚秒级 agentsview 聚合用量缓存让 30 天 Token 统计从全量扫描降到亚秒级【免费下载链接】agentsviewLocal-first session search, analytics, insights, and token use statistics for coding agents, supporting Claude Code, Codex, and more than 20 other agents.项目地址: https://gitcode.com/GitHub_Trending/ag/agentsview本文基于 agentsview 仓库的 Usage Aggregate Cache 设计文档 展开并结合 internal/db 下的实际实现代码完整讲解这套「一次性兄弟数据库 时区日粒度 rollup 窄异常行」的缓存体系它如何让 SQLite 后端的日用量、Top 会话、计费会话数等聚合查询不再逐条对每条带 token 的消息做排序、定价和传输同时保证缓存失效、多进程退役、时区规则变更等边界场景下结果与实时路径逐字节一致。读完后你将理解聚合缓存的两层派生数据如何划分、freshness 契约的完整请求流程、去重异常组的收敛条件以及 2 秒 warm 30 天性能门禁背后的验证手段。一、为什么需要一个「可随时删除」的兄弟数据库agentsview 的 SQLite 后端此前对聚合类用量请求走的是「live path」把窗口内每条带 token 的消息全部取出在 Go 侧逐条做模型解析、费率档位选择、按行金额舍入和去重排序。会话历史越深这个成本线性甚至超线性增长。Usage Aggregate Cache 的方案核心是一句话聚合读取改为查一个派生的、可随时删除的兄弟数据库sibling database归档库archive始终是权威数据源。由此得到几个设计约束见 设计文档缓存只服务四类聚合日用量daily usage、Top 会话、计费会话数、宽松匹配计数单会话明细仍走有界 live pathPostgreSQL 后端保留 live 实现并用完整结果pgtest夹具与 SQLite 结果对齐。删除任何被识别的缓存代次generation不丢失任何用户数据——因为归档库才是权威。发布目标在生产规模的受保护克隆protected production-scale clone上warm 状态的 30 天 CLI 完整结果不超过 2 秒且缓存构建前后的结果逐字节一致。失败语义同样明确聚合缓存读取失败就是错误绝不回退到 live 路径这一约束在 实施计划文档 中被再次强调避免「缓存没算完就悄悄返回近似值」这类难以排查的静默降级。二、两层派生数据facts 是构建基座rollups 是读取层缓存内部严格分为两层这一分层直接决定了 warm 读取的成本模型。2.1 facts 层窄事实只含解析结果不含转录内容facts 层存储的是解析后的消息与 usage 事件字段但不含任何转录正文。它的意义是让「换一个时区重建 rollup」这件事与被文档明确指出的 39 GB 归档库及其 JSON 列解耦——重建不再需要回读大字段。从 usage_cache_schema.go 的usageCacheSchemaSQL可以看到 facts 层的真实形态usage_cached_sessions记录每个已缓存会话的来源指纹source_sync_marker、source_transcript_rev、usage_event_fingerprint与install_revision这是后续所有失效判断的锚点usage_facts是主事实表WITHOUT ROWID主键(cached_session_id, fact_index)保存 token 各类计数input/output/reasoning/cache_creation/cache_read、web_search_requests、上报成本reported_cost_microdollarscost_source、request_scoped标记以及去重身份字段claude_message_id、claude_request_id、source_uuid、usage_dedup_key并用三个部分索引partial indexes分别覆盖这三类去重键服务跨会话身份检查cursor_usage_facts单独存放 Cursor 用量事件带charged_microdollars与is_headless。设计文档中一句关键描述是facts 层只是构建基座build substratewarm 聚合请求只读 rollups 和异常行绝不回退 live 聚合路径。2.2 rollups 层时区日粒度聚合rollups 层按「逻辑粒度(session_id, local_day, model)」保存每日贡献外加活动行activity rows和窄去重异常。每条日行daily row保存token 各类计数、web-search 请求数定价身份priced_model、matched_pattern、rate_ok、rate_hash、pricing_timestamp、band_threshold费率档位阈值无档位时为 -1见 usage_rollup.go 的installUsageRollupRows按行舍入后的估算成本estimated_cost_microdollars、节省savings_microdollars、权威成本标记authoritative_cost_microdollars请求计数computed request/aggregate count、reported count、base request count与被丢弃的快照输出discarded_snapshot_output_tokens。模型解析、费率档位选择、金额舍入与结果组装全部由 Go 负责SQL 只做窄索引读取——这与 usage_rollup.go 中构建侧调用export包定价解析器export.PricingResolver的事实一致。安装install粒度是「每个源会话一个 rollup install」对应表usage_rollup_installs其中每个 install 携带来源指纹、fact_install_revision、烘焙进行的baked_agent/baked_started_at、pricing_hash与install_revision。会话的其余元数据项目、机器、自动化状态、终止状态、策展字段等过滤条件保持为 live 归档元数据不进入缓存唯一烘焙进去的是agent与started_at前者参与通用去重键后者为没有自身时间戳的事实提供日期且这两个精确值在每次读取时都会被重新校验usage_rollup.go 的readUsageRollupInstalls中baked[session] ! [2]string{agent, startedAt}即判定 install 过期。2.3 去重异常只有「不可约」的组才落盘去重是这套缓存里语义最复杂的部分。构建某个会话 rollup 时按组分类一组只有在其解析结果可证明不随查询窗口或 live 过滤器变化时才允许收敛finalize进日行。收敛条件设计文档逐条列出全部是必要条件组内每个成员都属于正在构建的会话且共享同一个本地日期——因为查询窗口以整天为单位这样的组作为整体要么在窗口内、要么在窗口外对通用source:/usage:组每个成员还必须共享同一个模型和 headless 状态——因为 live 的模型过滤和自动化过滤在通用排序之前生效组内没有任何成员同时连接 snapshot 去重与通用去重携带 usage 键的快照幸存者会在读取时重新进入通用排序组内没有任何成员携带 Copilot 权威成本——它在整个窗口内是顺序依赖的按会话选择组身份不出现在任何其他已缓存会话中对 usage 键还不能出现在 Cursor 事实库中。usage_facts上的部分身份索引就是为这个检查保守地服务的例如source:身份忽略烘焙的 agent。收敛时应用与读取路径完全一致的排序规则最大 output 的快照胜出、归属跟随最早一行、最大 web-search 计数带过去、落败快照的 output 记为 discarded。只有不可约的组跨会话、跨天、跨模型的真实重复才存为窄异常行usage_rollup_exceptions带group_kind为snapshot/general和group_key。由此异常量随真实重复组数量增长而不是随带 token 的消息数量增长——这是 warm 读取能做到亚秒级的根本原因。Cursor 事实则完全保留在异常层它们的键可能与会话 usage 键冲突且过滤器依赖逐行 headless 状态因此以一个按 Cursor high-water mark 键控的合成源 installusage_rollup.go 中的usageRollupCursorSessionID \x00cursor承载。读取时的路径查询只加载与请求窗口相交的、来自当前已校验候选会话的异常行然后在 Go 中套用既有的窗口内排序、归属、过滤与定价规则已收敛组和普通事实留在索引化日行路径上。失效联动同样精巧由于 rollup 按源会话安装改一个转录就重建该会话而当 fill、Cursor 批次或删除改变了某个会话贡献的去重身份集合时同一个缓存事务会删除所有持有该身份的其他会话的时区 rollup install且 rollup 安装事务内部会重新校验「没有已收敛身份新增了外部成员」否则按「源已移动」重试。于是「已收敛日行绝不会在获得兄弟后存活」这一不变量成立跨会话赢家变化会立即反映——下一次请求校验全部候选会话并从所有成员的最新异常行重新解析该组。usage_rollup.go 中installUsageRollupBuilds在BEGIN IMMEDIATE事务里重新加载跨会话身份集合并断言currentCross.subsetOf(buildCross)构建与安装之间竞争的重分类最多重试usageRollupMaxBuildAttempts 8次。三、Freshness 契约一次聚合请求的七步设计文档给出的请求流程可以完整还原usage_cache_consumers.go 的queryUsageRollups是其对应实现捕获快照归档数据库 ID、候选会话、来源指纹、精确的agent与started_at、live 过滤器元数据、定价行、Cursor high-water mark、请求时区在获取缓存写锁之前关闭归档快照——避免跨数据库持锁窗口打开代次由缓存格式版本 归档database_id命名的那个代次只填充缺失或过期的候选会话的规范化事实usageFillCoordinator.Ensureusage_cache_fill.go为请求时区构建缺失或过期的 rollupsusageRollupCoordinator.Ensure安装前重新检查来源指纹与烘焙元数据在一个钉住的缓存读事务内校验所有必需 install然后才组装结果。几个容易被忽视的细节定价失效不靠时间戳。定价代次使用规范的、与顺序无关的 effective-pricing 摘要日行还保留按模型解析出的费率哈希。在 usage_rollup.go 中usagePricingIdentity组合export.EffectivePricingDigest(rows)与pricingpkg.BillingPolicyVersion()——后者很重要定价策略本身的代码级变化如摘要只能哈希目录行、哈希不到的解析规则变更也能通过版本字符串触发重建。sync_marker不是单调版本它的触发器对一组可变时间戳字段取最大值值可能变小。因此安装时比较的是提取后的完整来源指纹而不是 marker 的顺序。被硬删除的会话按更新的态丢弃其他「源在移动」或「归档忙」的竞态最多重试 3 次usageFillMaxAttempts 3然后失败而不是返回过期用量——宁可报错也不给错数。请求取消只是把等待者从共享 fill 工作中摘除fill 本身继续跑让重试风暴收敛cached_at只作诊断不参与任何新鲜度判断。queryUsageRollups的重试循环与这套契约一一对应capture → acquire generation → deletion sweep → fill → rollup ensure → cache read任何阶段遇到errUsageCacheSourceChanged就重新捕获快照重试超过usageFillMaxAttempts才把最后一个错误抛给调用方。四、文件与代次多进程安全地退役缓存缓存文件放在归档库同目录文件名即代次身份。usage_cache_schema.go 中usageCacheGenerationPath生成usage-cache-v{版本}-{databaseID 的 SHA-256 前 16 字节 hex}.db。格式或数据库 ID 变化会选中一个全新代次而不是迁移旧文件。当前格式版本为 12注释里完整保留了 v5→v12 每次重建的缘由定价解析规则变化、计费身份保留、跨进程租约引入、特定模型费率修正等是一份很好的「为什么缓存代次要整体重建」的案例记录。退役协议retirement protocol保证了多进程共存时的安全删除删除或替换任何代次前必须同时用 SQLiteapplication_id0x41565543和usage_cache_metadata.cache_kindagentsview-usage-facts确认它是 agentsview 用量缓存文件名匹配不算数probeUsageCache的 fail-closed 探测逻辑v8 及以后的代次携带退役协议标记并在每个打开它的 SQLite 连接池的整个生命周期内持有一个跨进程共享租约flockpath .lease。一个打开者只有在拿到独占租约、按精确文件名重新核对格式版本与源数据库 ID、并关闭自己的句柄之后才能移除另一个被识别的代次随后主库、WAL、共享内存文件一起删除而极小的租约文件被保留让竞争中的打开者不可能锁到被替换的 inode 上协议之前的代次、身份不匹配的文件保持不动旧进程可能还开着它们比当前运行格式更新的代次也保留避免降级后的二进制强迫新版代次重建。若兄弟目录不可写进程退化为在临时数据库中使用相同 schema 与读取路径并告警「重启后会重建」——功能不中断只是失去持久性openTemporaryUsageCache分支。时区身份规则指纹防 zoneinfo 更新时区身份在请求窗口之间保持稳定命名时区用 IANA 名 1970 至 2100 年时区规则的指纹组合成 key任何 zoneinfo 更新只要在该范围内增删或移动了转换点就会换一个 key从而绝不会复用过期的日分桶usage_rollup.go 的usageTimezoneRuleFingerprint逐条哈希每个规则区间及其精确结束时刻。当进程本地时区只报告Local时会先尝试解析/etc/localtime的 zoneinfo 符号链接usageLocationName解析不出就用同样的规则指纹作为 key——防止懒加载的time.Local初始化或不同请求范围把同一时区裂成多个代次。五、后台维护latest-first 回填与数据库卫生HTTP 就绪之后可写的 daemon 进程按「最新会话优先」回填每批最多 256 个会话usageFillInstallBatchSize 256usage_cache_fill.gousage_cache_background.go 中回填批大小与其对齐以便前台等待者随每个已提交批次尽早释放构建进程本地 rollup 与最近请求过的最多 8 个命名时区变更工作只在归档提交之后入队缓存绝不在归档写事务内填充每批之间执行PRAGMA optimizefreelist 较大时做有界增量 vacuum阈值为 4096 页、每次最多 256 页见usageCacheVacuumFreelistThreshold/usageCacheVacuumPages代次创建后与完整回填后执行全量ANALYZE删除日志清扫只是卫生工作——聚合读取要求当前归档候选本来就不可能暴露孤儿缓存行一轮过程中源数据若移动worker 重新捕获整个快照重试最多 3 次已安装指纹让未变更的批次在多次尝试间可复用。回填入口是 usage_cache_background.go 的StartUsageCacheBackfill要求可写归档且以已安装会话版本为准——重启一轮会自然跳过已完成的工作WaitUsageCacheBackfill供 daemon 生命周期等待restartUsageCacheBackfillIfEnabled的注释还点明了一个克制的设计CLI resync 或 compaction 重开归档时不会自行拉起整库后台扫描只有显式启用的 daemon 生命周期才会。六、验证与性能门禁正确性验证的策略是「拿永久 live 路径当 oracle」随机化对拍公开 rollup 结果与 live oracle 在种子随机窗口、时区、过滤器、DST 边界、定价档位、上报成本、Cursor 行、空时间戳、跨会话去重组上逐一比对变更测试覆盖转录替换、resync 指纹、定价、agent、started_at、删除、schema 代次、数据库 ID 变化等每一类失效源受保护克隆发布检查7 天 / 30 天 / 全历史结果逐字节比对性能报告把冷构建与 warm 读取分开报 1 天、7 天、30 天与全历史warm 路径不得扫描规范化事实。成本模型的门禁表述非常明确warm 读取必须随日行数量与真实重复组数量增长而不是随带 token 的消息数量增长发布检查会连同耗时一起报告异常基数exception cardinality。前台慢请求超过 usage_cache_consumers.go 中usageRollupSlowRequestThreshold 2s的阈值会输出隐私安全的分阶段耗时与行计数日志capture / deletion sweep / fact fill / rollup build / rollup install / cache readwarm 请求则保持静默。冷构建与未缓存候选会话的历史成正比刻意放在后台、按最新优先消化。实施计划文档 的「Measured result」一节记录了受保护克隆上的观测值warm CLI 路径约0.19 秒1 天、0.35 秒7 天、0.85 秒30 天全部远低于 2 秒门禁冷构建与全历史则作为后台规模工作单独报告。需要注意设计文档同时声明受保护克隆的耗时会针对每次发布重新测量而不是把某个记录值当长期承诺。七、对读者的启示这套缓存实现里可迁移的 engineering 决策有四点派生数据与权威数据物理分离让「删除缓存零数据损失」成为架构承诺而非运维约定失效判断只信内容指纹来源指纹、定价摘要、烘焙元数据精确值不信任何可能回退的 marker 或时间戳把复杂语义去重收敛到「可证明与窗口无关」的子集其余走窄异常行 读取期 Go 侧排序使热路径成本与真实复杂度而非数据规模成正比竞态处理选择「重试到一致失败到明确」绝不静默返回过期结果。进一步阅读可以从这几个入口开始schema 与代次协议见 internal/db/usage_cache_schema.gorollup 构建/安装与重试见 internal/db/usage_rollup.go 和 internal/db/usage_rollup_build.go事实填充见 internal/db/usage_cache_fill.go后台回填见 internal/db/usage_cache_background.go消费端与慢请求日志见 internal/db/usage_cache_consumers.goPostgreSQL 侧的完整结果对拍夹具位于 internal/postgres/usage_facts_parity_pgtest_test.go设计全貌另有 实施计划 可对照阅读。【免费下载链接】agentsviewLocal-first session search, analytics, insights, and token use statistics for coding agents, supporting Claude Code, Codex, and more than 20 other agents.项目地址: https://gitcode.com/GitHub_Trending/ag/agentsview创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表