Trifle:只存答案、不存事件的开源分析库
文章摘要
Trifle 是一个面向开发者的开源时序分析库,核心设计取舍藏在标题里:它聚合嵌套计数器,而不存储原始事件,并且全部数据都写进你已经在用的数据库里。作者说这个项目十年间被重写了两次,如今在他的正职工作中每天追踪约 10 亿个事件。
项目的来历很能说明设计动机。它 2015 年起源于作者自己写的 Rails APM,挂在 ActiveSupport::Notifications 上,来了几个小用户,然后一个大用户的爬虫应用把一切都压垮了。这件事催生了核心想法:把计数器聚合进预先定义好的时间桶,这样一次写入就能同时递增多个桶。那个 APM 后来没什么起色,逐渐淡出。2021 年作者在正职工作中需要分析能力,于是没有采用现成方案,而是把 Trifle 的想法复活成一个更通用的分析库,借用了一些数据仓库的思路。存储层先用 Redis,再用 Postgres,最终落到 MongoDB——这也解释了为什么 Trifle::Stats 自带多个 driver:DSL 保持统一,存储层随需求变化。他们的场景(巨大写入量、少量读取)下,Postgres 读更快但在大写入量下变慢。
嵌套值是整个方案的关键技巧。一次 Trifle::Stats.track 调用可以在一个 key 下同时提交 count、按状态码细分的 status 映射、size、以及 {sum, count} 形式的 duration,一次性为多个时间桶累积出请求数、成功率、状态码分布和耗时。取回时某个小时的桶看起来就是一个嵌套的聚合结果。要注意的是成功率从不被存储,而是用 200 的数量除以总请求数算出来;平均耗时同理,用 sum 除以 count。你给出指标 key、粒度和时间范围,拿回每个时间点上的聚合值,可以直接喂给图表,也可以回答「过去 30 天的平均响应时间」这类问题。
生态由三部分组成:Trifle Stats(一次 API 调用即可埋点的轻量库)、Trifle App(云端或自托管的看板,含告警和定时摘要)、Trifle CLI(终端查询工具,带 MCP server 模式以对接 AI 智能体)。库支持 Ruby、Elixir 和 Go 三种语言,且三者互相兼容——在一种语言里写入,可以在另一种里读取。作者说他之所以写了 Elixir 版本,是因为 Trifle App 是用 Elixir 写的;Go 版本则是为了 CLI。存储侧可以对接 Redis、Postgres、MongoDB、MySQL 或 SQLite。
规模与成本数据颇有说服力:他们如今追踪每天超过 1 亿个后台任务的活动,折算成约 10 亿个事件。作者说在愿意牺牲一些安全性(在 Mongo 里关掉 journaling 和 write concern)的前提下,它跑起来出人意料地便宜——一个三节点的 Hetzner MongoDB 集群,主节点利用率 20%,每月约 1000 美元。
作者也很坦诚地列出了局限:payload 不能装成千上万个 key,否则文档会变得太大、更新效率低下;需要提前做一些规划;以及没有维度(dimensions)的概念——有时你可以把维度嵌套进去(比如国家,总量有限),有时更适合为每个维度值建专门的指标 key(比如客户,会无限增长),而后者会让被追踪的事件数成倍增加,这也正是 1 亿个任务变成 10 亿个事件的原因。许可上,几个库是 MIT,App 是 ELv2 的源码可见许可——可免费自托管,也提供付费的托管云版本。作者说这是他在业余时间做的,没有投资人的钱可以烧在免费服务上。
HN 评论精华
-
xnx 提出了最直击设计取舍的质疑:「10 亿个事件在磁盘上也就 4GB 左右。既然磁盘这么便宜(即便是现在),为什么要立刻扔掉这些数据,而不是至少留 30 天、一年,等你确认自己的分析口径是对的?」作者 iluzone 的回答很实在:这是个公平的观点,磁盘成本如今很少是瓶颈;但 4GB 是按每事件几个字节算的,他们的场景更接近每天 100GB,而且那是按原始文本存储;要能重建这些数据就需要某种能高效跑的管道,而那恰恰违背了 Trifle 的意义。他补充说这真的取决于用例——对他们来说最近 24 到 48 小时的数据价值最高,更早的只是参考,误差范围可以接受,「即便我们修正了两周前的历史数据,也不会改变我们今天做的决策」,但对别人可能正好相反。他还给了一个很务实的建议:「其实没什么阻止你两件事都做。Trifle 只是一个往你自己数据库里写的库。如果你的数据需要偶尔重新分析,那就把事件写进它们自己的 events 表,这样你就有能力按需重建统计。」
-
freakynit 补充了一组很有参考价值的容量估算:他做过一个网站分析产品,每个页面浏览/事件要存浏览器、设备、操作系统、页面参数、referrer 等一堆常规字段,在数据库层压缩后大约是每 10 亿事件 200GB(数据库当然是列式的)。「在这种存储量级上,成本开始动真格了。」按预期每月 100 亿事件计算,每月要增加 2TB。因此他不得不转向 S3 支撑、NVMe 做缓存的架构,但那让查询时间产生了一定程度的可感知波动。他的结论是:那个「只要 4GB」是针对极小的每指标样本而言的,大概最多 3 到 4 个数值字段。
-
t-writescode 提了另一个关键的定位问题:「这和 Prometheus 及其时序数据库有什么区别?看起来跟 prom 的工作方式差不多,好的坏的都包括——比如伴随这类统计而来的那些问题,例如 p95 计算。」作者承认确实有相似之处:在 Trifle 里也能像 Prometheus 那样做直方图,p95 的问题两边都存在,sum 和 count 只能带你走这么远,你可以得到百分位数的正态近似,但这是你要做的妥协。他认为差别更清晰地体现在受众上:Prometheus 是一个你需要运维的服务器,而 Trifle 把数据推进你已经拥有的数据库;另外 Prometheus 是抓取(scrape)你的数据,你需要 pushgateway 才能推给它。「一个是为基础设施监控而生,另一个是你用来推送增量的库。」0x008 一句话点评:「没错,这应该是网页首先要回答的第一个问题。」作者接受了这个批评——他原本把一个基础对比藏在另一个页面上,现在把它移到了落地页,并表示会做更深入的针对特定工具的对比。
-
renlo 问了个运维层面的顾虑:「所有这些指标涌进来,你的数据库会怎么样?」他随后补充了更尖锐的版本:「加载一个性能看板会不会影响到用户购买产品的能力?」作者的回答分两层。写入侧:数据库会更忙,但目标就是让每次动作发生时造成一点小负载,这样就永远不会有某条重查询在读取时造成大负载。他也给出了具体的踩坑经验——MongoDB 的主要问题是当聚合 payload 中的 key 超过 1000 个以后写入会变慢,如果你有「年度桶」这类东西就很容易触发;而 Redis 不受此影响,因为递增是逐个完成的,写入延迟保持平坦。读取侧:看板反而是容易的一面,渲染一个看板只是从预聚合的桶里查几个点,你只需要一个 key 加上时间范围和粒度,除非你想拿一整个月的每分钟数据,否则会很快。如果你想要真正的隔离,可以给 driver 一个带自己配额的专用用户,或者指向一个专用数据库。他还回顾了自己的演进路径:一开始用 Redis 图方便,然后为了确保持久化换成 Postgres,最后落到能轻松吃下写入量的 MongoDB。
-
veverkap 问了标准兼容的问题:你提到了 OTEL 相关的东西(Jaeger 等),有没有考虑过用 OpenTelemetry 这样的开放标准?虽然我承认把它跑起来有难度,但标准是有帮助的。作者的回答坦率且有信息量:一部分是时机问题,一部分是层次问题。Trifle 的起源要追溯到 2015 年,2021 年他复活它时 OTel 还很新,当时并不真的是一个「二选一」的决定。但他也认为两者处在不同层次:OTel 是发射遥测数据的标准,数据最终还是要落进某个后端(trace 进 Jaeger,metrics 进 Prometheus),而 Trifle 把数据留在你已经有的数据库里——这从一开始就是他的要点,「你不需要一个专门的数据库或技术栈来做基础分析」。他还觉得受众有所不同:OTel 长成了基础设施/可观测性方向,而 Trifle 大多用来追踪产品和业务计数器,目前他不把两者视作竞争关系。有趣的是他还提到 2021 年自己也开始做
Trifle::Traces,一个极其(真的极其)简单的 Ruby 追踪库,把代码包在不同的 tracing block 里——他很讨厌到处塞puts语句然后再从原始日志里拼凑信息的做法;最后他们把可搜索的元数据放进 MongoDB、实际数据放进 S3,再围绕它建了个小的内部 UI。他自己承认「不否认这听起来非常像 OTel tracing 加 Jaeger」,并说「如果 OTel 当年就在今天这个位置,也许我的仓库会少几个」。他的总结句很值得记:「我认为我们常常迷失在『以为做基础的事情也需要这些大工具』的想法里,而这正是 Trifle 要填的空白。」veverkap 表示这个回答很合理,并好奇未来做一个兼容层会有多难。 -
venkat971(DeepSQL 的作者,本期另一个 Show HN 的主角)来问技术栈和推荐机器配置,同时说「顺便,我喜欢你的定价页面,我打算给我的项目 deepsql.ai 也做个类似的」。作者回答说库本身没有技术栈——它只是一组插件,你可以直接用(Ruby、Elixir、Go),也可以配合框架用(Rails、Sinatra、Phoenix、Ash 等),它们写入你已经在跑的数据库(Redis、Postgres、MongoDB、MySQL 或 SQLite),配置就是加上插件并指向你的库。关于什么时候会开始感觉到负载,他给了个很有用的经验值:「真的取决于你的量,我会说大概每天 10 万以上事件是负载开始变得可感知的地方。在那之前我不会太担心额外负载。」至于自托管 App,它相当轻量——虽然是个 Elixir 应用、你也可以自己编译运行,但它已经打包成 Docker 镜像,并且有 Kubernetes/Helm 部署文档;你可以指向自己的 Postgres,也可以让它自己启一个来存用户、看板、监控项等自身数据。「2 到 3 个用户的话一个实例就绰绰有余,1 个 CPU 加 2GB 内存就能轻松跑起来。想要冗余就翻一倍。」
-
slig 给了一句简短但作者显然很在意的评价:「网站很漂亮,看得出你很用心。」作者回应说它经历了几轮重新设计,「我还是不确定它有没有把信息传达清楚,我会继续迭代」。这与前面 0x008 的批评呼应:一个技术上打磨了十年的项目,最大的短板反而是没能在落地页上第一时间回答「这和我已知的东西有什么不同」。