想象一个场景:你负责的计费系统突然出现异常,某个站点的费率算错了,但全局汇总数据一切正常。你翻数据仓库,发现昨天的数据还没同步;你问业务团队,他们说还没收到投诉;你只能手动去查几个不同的数据库,拼出对比表,等拼完,几个小时已经过去了,卖家可能已经因为错误的扣费开始投诉。这是 eBay 商业化团队在 Minerva 之前每天都要面对的困境。
问题出在哪?计费系统本身是实时处理的,但监控它的工具却跟不上。数据仓库(HDFS,分布式文件系统,用来存海量历史数据)里的数据来得太晚,只适合事后复盘,没法支撑按小时盯着一场费率上线。而且数据仓库通常在不同的安全区,访问起来很麻烦。更糟的是,问题检测基本靠客户投诉和业务团队的事后分析,一个错误可能要几小时甚至几天才会被发现。
Minerva 的核心思路其实很朴素:**把监控从实时计费路径上拿下来**。计费服务在做出扣费决定时,只往本地队列里放一条很小的记录,里面只有商品 ID 和计费事件 ID 这类标识符。一个后台生产者线程负责把这个队列里的记录发布到事件流里。正常情况下,计费请求完全不用等网络发布、数据丰富、分析存储这些环节,延迟和可用性风险都被隔离在核心路径之外。
这个设计最让我意外的是它对"丢数据"的态度。本地队列是有容量和记录年龄上限的,如果下游长时间故障,队列里的旧记录会被直接丢弃或覆盖,而不是让积压无限增长。这等于明确宣布:**计费可用性优先于监控数据的无损交付**。因为 Minerva 的事件流只用于基于时间窗口的细粒度监控,它不是计费决策的事务性事实来源,丢了监控数据不会改变或作废原始的计费计算。这种"敢于丢数据"的取舍,在追求高可用的系统里其实非常关键——很多监控系统失败恰恰是因为想什么都保,结果把核心服务拖垮了。
数据丰富也是异步完成的。后台消费者拿到只有 ID 的记录后,再去事务读数据库里查对应的费用、商品、账户、促销属性,然后把丰富后的记录写入 ClickHouse。这样仪表盘和监控查询直接读丰富后的数据,不用每次都在多个操作型存储之间做 join(关联查询,把多个表按条件合并)。代价是监控数据是最终一致的——计费决定之后数据才慢慢可用,但计费请求本身和这些分析工作完全隔离。
存储层选型也很有意思。Minerva 的常见查询——按站点、品类、用户看费用趋势,按站点、品类、用户看促销使用情况——要在几百万条记录上聚合一小撮字段,而每条记录有将近 250 列。事务型数据库显然不适合这种反复的历史扫描,所以团队选了 ClickHouse。ClickHouse 按列存储(列式存储,查询时只读需要的列,而不是整行),查询只需要读需要的字段;它的 MergeTree 表引擎家族还提供了排序和分区控制,可以对齐常见的监控访问模式。
一个早期概念验证在 6 个 vCPU、44GB 内存的机器上测了大约 11 亿条 listing-session 行,示例查询和聚合都在一秒内完成,报告的范围大约在 0.11 到 0.72 秒。作者也诚实地说这不是生产基准测试,没记录重复运行、缓存状态、并发、压缩或对比基线,但足以证明初始设计的可行性。
这套思路能直接借鉴到哪?凡是"核心服务不能慢,但又要看清它内部发生了什么"的场景都适用:推荐系统、广告竞价、支付网关、风控决策,这些实时路径上的系统都可以用"本地队列 + 异步丰富 + 列式存储"的组合来搭监控。要注意的坑有两个:一是想清楚哪些数据可以丢,明确监控不是事实来源,否则队列积压迟早反噬核心服务;二是丰富数据的维度要提前设计好,Minerva 保留了站点、费用类型、促销、时间这些维度,才能回答"哪里变了"和"为什么变"这两个问题。如果只留聚合总数,遇到"全局正常但某个站点异常"的情况,你还是得回到手动查库的老路上去。

内容与图片版权归原作者所有 · 原文: https://innovation.ebayinc.com/stories/inside-minerva-building-scalable-observability-for-ebays-monetization-platform/