每天早晨,全球数十万美容与健康服务商家打开 Fresha 首页,第一时间查看昨天的营收、当天预约和员工表现。几个看似简单的数字背后,是每日 60 万笔预约、数十亿条数据库变更事件,以及峰值每秒 3,000 次请求共同支撑的实时数据管道。
随着实时链路的压力持续上升,Fresha 的数据工程团队逐渐意识到,单点优化已经难以解决根本问题,必须重新设计整套架构。
Fresha 是全球领先的美容、健康与自我护理行业 SaaS 平台,总部位于英国,服务全球数百万消费者与商家。不到一年时间里,团队完成了一次系统性的数据平台升级:从以 Postgres、Snowflake 和 Amazon MSK 为核心的传统架构,演进为基于 AutoMQ 与 StarRocks 的现代实时分析数据栈。
旧架构的困境
消息层:当 Kafka 的数据中心架构遇上云
Fresha 的数据管道构建在 Apache Kafka 之上。约 100 个 Postgres 数据库的变更事件,通过 Debezium CDC 写入 Kafka;下游的 Flink、Spark、StarRocks、Snowflake 等系统,再从 Kafka 中消费数据。对 Fresha 而言,Kafka 不只是消息队列,而是整个数据平台的“神经系统”。
当时,Fresha 在 Amazon MSK 上运行了两个 Kafka 集群:一个是 CDC/Warehouse 集群,承载数据平台的全部 CDC 流量,每天流转数十亿条事件;另一个是 Outbox 集群,作为微服务之间的事件驱动集成层,对延迟的要求远高于 CDC 场景。
两类工作负载截然不同,却运行在同一种托管 Kafka 服务上。
Kafka 诞生于数据中心时代,其存储模型也带有明显的数据中心架构特征:每个 Broker 将数据写入本地磁盘,并通过 ISR(In-Sync Replicas)机制,在多个 Broker 之间跨可用区复制数据,以保证数据持久性。
在传统数据中心环境中,这套设计是合理的:磁盘成本相对可控,机器之间的数据复制通常不会产生额外费用,存储和计算绑定在同一台物理机上,也是一种简单直接的架构选择。
但当 Kafka 被搬到云上,存算耦合的模型开始带来一系列问题:
存储成本高:EBS 按容量和 IOPS 收费,三副本机制意味着接近三倍的存储成本。
跨 AZ 流量费用高:Broker 之间跨可用区复制副本,会产生额外网络流量费用,数据规模越大,成本越明显。
扩容粒度粗:扩容往往意味着增加整台 Broker,CPU、内存、网络和存储一起扩,容易造成资源浪费。
弹性受限:分区绑定在特定 Broker 上。新增 Broker 后,需要通过物理搬迁分区数据来重新平衡负载,这一过程可能持续数小时甚至更久,难以及时响应流量变化。
Fresha 的业务特点进一步放大了这些问题。它的流量呈明显的尖峰型特征:每天早晚高峰,请求量可能达到平时的数倍。但在存算耦合架构下,团队必须按照峰值流量预留整台 Broker 的容量,导致大部分时间资源处于闲置状态。
与此同时,数据管道还在持续增长。Flink 作业越来越多,每新增一条管道,往往都需要引入新的中间 Topic。问题在于,MSK 的分区数与 Broker 实例规格绑定:不同实例类型有对应的分区数上限,要承载更多分区,就必须升级到更大的实例。再加上 MSK 的成本也会随分区数量增加而上升,Topic 和分区越多,Kafka 的资源消耗和费用压力就越明显。
作为 Amazon 提供的托管 Kafka 服务,MSK 在一定程度上降低了部署门槛,却没有改变传统 Kafka 的底层架构约束,甚至带来了新的运维压力。比如,MSK 会定期执行强制运维窗口(OS patching),并对 Broker 逐个进行滚动重启。由于 Broker 本身是有状态的,每个 Broker 都绑定着大量分区数据,重启过程中往往会伴随 Leader 重新分配和数据重平衡,集群进入 rebalance 状态,进而触发告警和短暂的性能波动。团队不得不在每次运维窗口前后保持高度关注。
为了解决这些问题,Fresha 花了六七个月评估替代方案,包括 Confluent 和 Aiven。但这些方案本质上仍然延续了传统 Kafka 的存算耦合架构,只是换了一种托管形态,并没有解决根本问题。
也就是说,消息层的困境并不在于 MSK 本身“不好用”,而在于传统 Kafka 的架构模型与云环境之间存在天然错位。
分析层:从 Postgres 到 Snowflake,瓶颈仍在继续
消息层将数据送往下游,但在分析层,Fresha 也经历了一轮又一轮瓶颈。
最初,Fresha 的分析需求直接由 Postgres 承担。商家首页上的实时分析组件,例如近期营收、即将到来的预约、热门服务、员工表现对比等,查询都会直接打到 OLTP 数据库上。
问题很快暴露出来。Fresha 的访问流量具有明显的尖峰特征:每天早晨和傍晚,大量商家会集中打开首页查看经营数据,形成请求高峰。在其他时段,Postgres 的 buffer cache 主要保存 OLTP 事务热数据,查询响应还能维持稳定。但一到高峰期,分析查询会加载大量历史数据页,直接挤占 buffer cache 中的事务热数据。
结果是,第一个冷查询往往会因为拉取大量数据页而超时;后续请求虽然可能命中缓存,但 OLTP 事务所需的热数据已经被挤出缓存,连下单接口也会受到影响,出现响应变慢。
对于小商家来说,数据量有限,查询还能勉强跑起来。但 Fresha 最重要的大商家,往往也是数据量最大的客户:他们付费意愿更强,对平台贡献更高,却也最容易触发性能瓶颈。
在这类场景下,P99 延迟一度飙升到 4 秒以上,大量请求直接返回 500 错误。也就是说,最需要被稳定服务的客户,反而获得了最差的体验。
为了将分析负载从 Postgres 中剥离出来,团队引入了 Snowflake:通过 Debezium CDC 将数据同步到 Snowflake,再用 dbt 做批量建模。这样一来,传统 BI 看板的需求得到了满足。
但 Fresha 需要的不只是 BI。商家打开首页时,希望看到的是最新营收数据,而不是 20 分钟前的快照。团队尝试过多种方式来降低链路延迟,但都没有真正走通:
dbt 批量建模:即使优化到极限,也只能做到约 20 分钟刷新一次,距离真正的实时仍有明显差距。
Lambda 架构:通过实时事件流与批量预计算结果做去重和合并,延迟可以降到几十秒,但架构复杂度迅速上升,维护成本难以接受。
ClickHouse:Anton 有多年使用经验,但 Fresha 的查询经常涉及 20–30 个 Join。对于这类复杂 Join 场景,ClickHouse 通常需要先预建模成宽表,前期投入过重。
此时,消息层还没有找到理想替代方案,分析层也在一次次迁移中遇到新的天花板。继续修补单点问题已经不够了,Fresha 需要的是一次系统性的数据栈升级。
团队的设计原则也很明确:不是为了解决眼前某个问题,再引入一个新工具;而是构建一个可组合、可持续演进的平台。这样,当下一个需求出现时,团队不必再从头设计架构。
新架构全景
这一理念最终落地为一条统一的数据摄取主干(Ingestion Spine),并在其之上延伸出多条面向不同业务需求的数据链路。
约 100 个 Postgres 数据库通过 Debezium 捕获 CDC 事件,经 Schema Registry 以 Avro 格式序列化后写入 AutoMQ;随后,再由 Flink 和 Spark 将数据分发到不同下游系统:StarRocks 用于支撑实时 Dashboard,Iceberg / Paimon 用于 Lakehouse 长期存储,Elasticsearch 用于全文搜索。
在新架构中,StarRocks 作为统一的 SQL 查询入口,通过 MySQL 协议对外提供服务。工程师可以用一条 SQL,将实时数据、历史数据与搜索索引关联起来完成分析查询。
为什么选 AutoMQ
传统 Kafka 的存算耦合模型,与云环境之间存在天然错位,也让 Fresha 看到了一个更清晰的方向:Diskless Kafka。
它的核心思路是将数据从 Broker 本地磁盘迁移到云对象存储(如 S3),让 Broker 变成无状态计算单元,存储则交给云基础设施。S3 本身已经提供 11 个 9 的持久性和跨 AZ 冗余,应用层不再需要通过多副本复制来保证数据可靠性。
这样一来,传统 Kafka 在云上暴露出的几个核心问题,包括三副本带来的存储成本、跨 AZ 副本复制产生的流量费用、扩容必须增加整台机器,以及分区迁移需要物理搬运数据,都能在这一架构下被同时化解。不过,Diskless 并不是没有代价。市面上已有的一些 Diskless Kafka 方案,通常将数据完全构建在 S3 之上,写入延迟受对象存储响应时间限制,往往在数百毫秒级别。
对于 Fresha 的 CDC 集群来说,这样的延迟可以接受。CDC 场景更关注吞吐量和成本,而不是毫秒级响应。但 Fresha 还有一个 Outbox 集群,承载微服务之间的事件驱动集成,对延迟的要求远高于 CDC。如果选择纯 S3 架构的 Diskless Kafka 方案,CDC 集群的问题可以解决,但 Outbox 集群仍然无法承载。这样一来,团队又会回到维护两套消息系统的老路。
AutoMQ 的架构创新,正是针对这一问题而来。作为新一代云原生 Diskless Kafka,AutoMQ 在 Broker 与 S3 之间引入了 WAL(Write-Ahead Log),并将其作为存储引擎的核心组件。数据写入时,先落到 WAL 完成持久化并立即返回 ACK,再由后台异步批量刷入 S3。
这一设计既保留了 Diskless Kafka 的核心优势——Broker 无状态、存储交给 S3、无需应用层副本复制——又通过不同 WAL 后端,覆盖不同场景下的延迟需求。高吞吐、低成本场景可以使用 S3 WAL;延迟敏感场景则可以使用低延迟 WAL。两类负载不再需要拆成两套系统。
对 Fresha 来说,这意味着 CDC 集群可以通过 S3 WAL 降低成本,Outbox 集群则可以通过低延迟 WAL 获得个位数毫秒级写入延迟。两个集群运行在同一套 AutoMQ 架构上,共用一套运维知识和监控体系。
这也解释了 Fresha 此前长达六七个月的选型僵局为何最终被打破:答案不是在成本和延迟之间二选一,而是用一套架构同时覆盖两种工作负载。
选择 AutoMQ 后,Fresha 获得的收益主要体现在三个层面:
1. 架构驱动的成本降低。
AutoMQ 将数据持久化从 EBS 三副本改为 S3 单份存储,存储成本显著下降;Broker 之间不再进行跨 AZ 副本复制,网络流量费用也从架构上被消除。同时,AutoMQ 的定价模型不与分区数绑定,Flink 中间 Topic 可以按需创建,不再像 MSK 那样随着分区数量增长持续推高成本。
Anton 曾在 EC2 上部署 AutoMQ,并将 CDC 集群的全部生产流量镜像过来验证 3 天。结果显示,AutoMQ 的 S3 存储成本比 MSK 低了 17–20 倍。
"We deployed AutoMQ on our EC2s. I mirrored all the traffic from our CDC warehouse cluster and I watched the cost in S3 and it was like 17-20 times less than our AWS cost."
— Anton Borisov, Principal Data Engineer, Fresha
2. 一套先进的 Diskless 架构覆盖多类场景。Broker 无状态意味着集群可以实现秒级弹性扩缩容。面对早晚高峰,团队不再需要按峰值预留整台机器。滚动升级时,也不再伴随分区重平衡和 Leader 切换,MSK 强制运维窗口带来的告警风暴成为历史。
更重要的是,通过 WAL 层的不同后端,CDC 集群和 Outbox 集群可以运行在同一套 AutoMQ 架构之上。这让 Fresha 终于结束了长达六七个月的选型僵局:不再需要为两类工作负载分别寻找两套系统。
3. 100% Kafka 兼容,零代码改动无缝迁移。AutoMQ 完全兼容 Apache Kafka 协议,Fresha 现有的 Producer、Consumer、Flink 作业和 Connector 都可以继续使用,不需要修改代码。配合 AutoMQ 内置的 Kafka Linking 零停机迁移工具,团队在一周内完成了近 1000 个 Topic 的迁移,下游系统几乎无感知。
为什么选 StarRocks
Fresha 需要的并不只是一个“更快”的分析引擎,而是一套能够真正承接实时业务分析的查询层。它的查询模式有两个非常鲜明的特点:
一是 Join 多且复杂,首页分析通常涉及 3–5 个 Join,支付日志分析则经常达到 20–30 个 Join,并且跨多个数据库;
二是这些查询并不是传统 BI 场景中的低频离线分析,而是直接服务商家首页,既要求低延迟,也要求分钟级的数据新鲜度。
这让很多常见方案都变得不够合适。继续把分析查询压在 Postgres 上,会持续伤害 OLTP;而如果采用 ClickHouse,面对大量复杂 Join,往往需要提前把数据预 Join 成宽表,前期建模和维护成本都很高。团队需要的是一种更直接的方式:尽量少做预建模,就能把复杂查询先跑起来,再随着业务增长逐步扩展。
StarRocks 正好满足了这一点。它能够在不依赖重度预建模的情况下支撑复杂 Join 查询,同时兼容 MySQL 协议,工程师可以直接复用现有的客户端和接入方式,降低了首个场景上线的门槛。
落地迁移实战
AutoMQ:一周内零停机迁移近 1000 个 Topic
架构选定、成本验证通过后,真正的挑战来到迁移阶段。Fresha 的 CDC 集群承载着近 1000 个 Topic,下游连接着 Flink 作业、各类 Connector、StarRocks Routine Load 以及 Snowflake 数据管道。每一个 Topic 背后,都有下游消费者依赖对应的 offset 来维护消费位点。
一旦 offset 不一致,就可能导致 Flink checkpoint 失效、Consumer 消费位点丢失。换句话说,如果迁移处理不好,成本几乎等同于重建整条数据管道。
传统迁移工具 MirrorMaker2 的问题也正在于此:它会重新序列化消息,导致目标集群中的 offset 与源集群无法保持一致。Anton 在 POC 阶段使用 MirrorMaker 做流量镜像时,就已经遇到过这一问题——数据可以同步过去,但 offset 对不上,下游系统必须重新配置。
正式迁移时,Fresha 选择使用 AutoMQ 内置的 Kafka Linking。它不是额外接入的外部迁移工具,而是 AutoMQ 产品内置的零停机迁移能力,专门用来解决迁移过程中最关键的两个问题:业务不中断、消费位点不丢失。
有了这些能力,Fresha 不需要用“赌一把”的方式推进迁移,而是采用了分阶段切换:
1. 先迁移非关键负载:优先将监控、日志类 Topic 迁移到 AutoMQ,验证数据完整性和延迟表现,将影响范围控制在较小范围内。
2. 逐步切换核心管道:第一批负载稳定运行后,再逐步切换 Flink 作业、StarRocks Routine Load 和 Snowflake 数据同步链路。
3. 滚动更新客户端:上下游 Producer 和 Consumer 通过标准滚动更新完成切换,不需要统一安排停机窗口。
在整个过渡期间,Kafka Linking 的写转发机制保证新旧集群数据始终一致。任何阶段如果发现问题,团队都可以直接回切。
最终,Fresha 在一周内完成了近 1000 个 Topic 的迁移,全程零停机,Flink 作业和 Connector 任务也几乎无感知。没有凌晨三点的维护窗口,也不需要多个团队同时开会协调切换时间。
由于 offset 实现了 1:1 保留,从下游消费者视角看,Kafka 集群几乎没有发生变化,只是底层架构完成了一次升级。
StarRocks 迁移实战
Fresha 最先迁移到 StarRocks 的,是首页分析这条最核心的实时链路。过去,这些查询直接打在 Postgres 上:小客户场景尚可,但大客户的数据量一上来,页面加载常常拖到 15–20 秒,甚至直接超时,还会击穿 buffer cache,连带拖慢 OLTP 事务。最需要被服务好的客户,反而最容易在高峰期遇到最差体验。
团队对新链路提出的要求非常明确:不仅要快,还要做到分钟级数据时效。他们曾评估过 Iceberg,但高频写入带来的小文件和 Compaction 压力,让这一目标难以稳定满足。最终,Fresha 将热点实时数据落入 StarRocks 内部表,同时保留 Iceberg/Paimon 作为历史数据的长期存储层。
团队没有单独使用物化视图,而是基于实时表构建了分层 SQL 视图,将业务关联、状态口径和“recent”“top”这类分析语义统一封装起来。产品侧只需要查询高层视图,不必重复理解底层逻辑;而 StarRocks 的优化器负责将过滤和裁剪下推到整个查询链路中。
Fresha 最先迁移到 StarRocks 的,是首页分析这条最核心的实时链路。
过去,这类查询直接打在 Postgres 上。小客户场景下还能勉强支撑,但一到大客户,数据量迅速放大,页面加载时间经常被拉长到 15–20 秒,甚至直接超时。同时,分析查询还会挤占 Postgres 的 buffer cache,连带影响 OLTP 事务性能。最需要被稳定服务的客户,反而最容易在高峰期遇到最差体验。
团队对新链路的要求很明确:不仅要快,还要具备分钟级数据时效。他们曾评估过 Iceberg,但高频写入会带来小文件和 Compaction 压力,很难稳定满足这一目标。因此,Fresha 最终选择将热点实时数据写入 StarRocks 内部表,同时保留 Iceberg / Paimon 作为历史数据的长期存储层。
在建模方式上,团队没有简单依赖物化视图,而是基于实时表构建了分层 SQL 视图,将业务关联关系、状态口径,以及 “recent”“top” 等分析语义统一封装起来。这样一来,产品侧只需要查询高层视图,不必反复理解底层逻辑;StarRocks 的优化器则负责将过滤和裁剪下推到整个查询链路中。
迁移完成后,首页分析查询在复杂过滤和聚合条件下的响应时间缩短到约 200 毫秒左右,满足了分钟级实时性的要求。Postgres 从分析负载中解放出来,恢复为稳定的事务数据库,而 StarRocks 则承接了首页的实时分析并发。
(启用 StarRocks 查询链路(通过 feature flag)前后的延迟分位对比:左图为旧的 Postgres 方案,查询经常出现多秒级峰值;右图为开启 StarRocks 后,p95 降至接近 1 秒以内,并且长尾(p99/p99.9)的峰值基本消失。)
👉更多迁移实践细节,可参考:https://mp.weixin.qq.com/s/xNNbhxAoMKG4lepSdr8xDA,本文不再展开。
生产成果
目前,Fresha 的新数据平台已在生产环境全面运行,支撑全球商家的实时分析、历史趋势查询与搜索服务。
消息层(AutoMQ):
分析层(StarRocks):
AutoMQ 提供了可靠、低成本、可承载混合工作负载的消息基础设施,StarRocks 提供了统一的实时分析查询能力,两者共同构成了 Fresha 新一代数据平台的底座。
现代化数据栈的组合拳
Fresha 的实践验证了一个正在被越来越多团队接受的架构方向:消息层与分析层需要同步现代化。
单独升级其中一层,往往只是把瓶颈从一个地方转移到另一个地方。分析引擎再快,如果消息层在成本、弹性和运维复杂度上跟不上,数据管道的持续增长迟早会受限;消息层再高效,如果分析层仍然依赖 Postgres 承担 OLAP 查询,用户体验问题也不会真正解决。
AutoMQ 与 StarRocks 的组合之所以有效,是因为它们分别在消息层和分析层,解决了同一类根本问题:用真正面向云环境的架构,替代延续自数据中心时代的设计。
AutoMQ 通过 Diskless 架构,将 Kafka 的存储从 Broker 本地磁盘迁移到对象存储,化解了存算耦合带来的成本、弹性与运维压力。StarRocks 则通过存算分离架构,让分析查询摆脱对本地存储的绑定,计算节点能够按需弹性扩缩容。
同时,两者都保持了对原有生态的高度兼容:AutoMQ 兼容 Kafka 协议,StarRocks 兼容 MySQL 协议,因此迁移成本相对较低。
对于仍在云上运行 Kafka + OLAP 数据栈的团队来说,Fresha 的实践提供了一条可参考路径:不必一次性推翻现有系统,而是分别在消息层和分析层选择云原生替代方案,通过零停机迁移逐步切换,在不中断业务的前提下完成整套数据架构升级。
回到开头的场景:每天早晨,商家打开 Fresha 首页查看经营数据。如今,这些数字能够在秒级完成更新,背后是 AutoMQ 每天稳定流转的数十亿条事件,以及 StarRocks 的亚秒级查询响应。
数据管道不再是团队需要反复救火的系统,而成为可以稳定依赖的基础设施。