Zepto如何用OpenSearch OR2实例实现亚秒级搜索
DataHot 速览
Zepto是印度快速电商平台,其搜索体验基于Amazon OpenSearch Service。随着业务扩张,索引量线性增长,维持亚秒级搜索延迟和控制成本成为挑战。Zepto迁移到OpenSearch Optimized实例后,以原有三分之二的数据节点承载同等负载,索引吞吐提升超100%,成本降低30%。本文介绍了其架构决策、压测结果及生产迁移经验。
为什么值得关注:该实践展示了通过实例选型优化搜索性能与成本平衡的路径,对数据平台和实时分析从业者有直接借鉴价值。
本文目录 13 节
译文
AI 逐段翻译亚秒级搜索是Zepto(印度一家快速增长的即时商务平台,成立于2021年,致力于在几分钟内完成配送)上每笔订单的起点。驱动搜索体验的是Amazon OpenSearch Service,这是一个基于OpenSearch构建的托管检索引擎,用于代理式AI、搜索和分析。
Zepto在印度各城市运营着数百个配送中心(暗店),为在Zepto平台上运营的卖家提供物流服务。每个中心维护自己的库存水平、定价和商品组合,涵盖数千个库存单位(SKU)。随着公司扩展到数百个中心,索引量线性增长,同时保持亚秒级产品搜索延迟并控制成本变得愈发具有挑战性。
为了解决这个问题,Zepto将其OpenSearch Service数据节点从内存优化型实例迁移到了OpenSearch优化实例。该实例系列专为高索引吞吐量和成本效益而设计。它使用本地存储作为主数据层,Apache Lucene段同步复制到Amazon Simple Storage Service(Amazon S3)以实现持久性。通过这次迁移,Zepto现在以之前数据节点数量的三分之二服务相同的工作负载,实现了超过100%的索引吞吐量提升和30%的成本节省。
在本文中,我们将探讨架构决策以及负载测试结果,这些因素促使Zepto选择OpenSearch优化实例用于延迟敏感的产品搜索。我们还将讨论生产迁移过程中的关键经验教训。
Zepto的搜索平台
Zepto的搜索平台围绕本地化配送中心模型构建。每个中心维护自己的库存、容量和履行优先级。当客户搜索产品时,查询不是针对全局目录解析,而是在服务于该客户配送地址的特定配送中心或中心的上下文中解析。这一区别至关重要:Zepto应用上的每个客户旅程都始于通过搜索、浏览和促销界面的产品发现。所有这些都必须实时反映中心特定的可用性,以便在几分钟内完成订单。
一种事件驱动架构为这种体验提供支持,随着产品、价格、优惠和库存跨越数百个配送中心的变化,保持结果新鲜。以下架构图展示了Zepto的端到端索引和搜索管道,从事件生产到流处理,再到OpenSearch Service上的搜索索引。

图1:Zepto的端到端索引和搜索管道架构
事件生产者和消费者:Zepto的应用程序微服务部署在Amazon Elastic Kubernetes Service(Amazon EKS)上,这是一个在AWS上运行Kubernetes工作负载的完全托管服务。这些微服务既是事件生产者也是消费者。卖家和Zepto管理员用户与Zepto合作伙伴和管理应用程序交互。
关键微服务: 目录管理服务在产品元数据变化(如新增产品、属性更新和类别重新分类)时发出事件。库存管理服务在仓库团队拣选、包装和补货时实时发布各配送中心的库存水平变化。定价管理服务在卖家更新定价时生成事件。优惠管理服务在创建、激活、修改或过期促销优惠时广播事件。这些微服务共同捕获下游索引的所有相关更新,每当业务状态变化时,将事件产生到流式层。
搜索事件流: 所有领域事件流经Amazon Managed Streaming for Apache Kafka(Amazon MSK),这是一个托管流数据服务,管理Apache Kafka基础设施和运维。
系统按业务领域将事件组织到专用的Kafka主题中。这些包括Catalog用于产品元数据变更,Inventory用于中心级库存更新,Pricing用于跨店铺的价格变化,以及Offers用于促销优惠生命周期事件等。这种基于主题的分区提供了每个域的独立扩展,确保每个主题内的顺序投递和消费者隔离,因此库存事件的激增不会中断目录索引。
流处理和路由: 来自MSK主题的事件被消费,并通过专用的Apache Flink OpenSearch Connector作业路由到两个基于优先级的索引管道,这些作业部署在Amazon EKS集群上:
- 作业#1:P0索引事件(管道#1): 处理需要近实时索引新鲜度的高优先级事件,例如库存变更、目录丰富和定价更新。
- 作业#2:P1索引事件(管道#2): 处理低优先级但高量的事件,例如标签更新、语义嵌入生成、优惠激活和每晚每展示收入(RPI)分数重新计算。这些更新提高搜索质量,但可以容忍稍高的延迟。
通过这种双作业方法,Zepto为关键信号(如库存可用性和当前定价)保持亚秒级新鲜度。计算密集型的丰富更新在不影响实时更新的情况下单独处理。
搜索索引: Zepto在OpenSearch Service上托管搜索索引,结构为城市-产品级别。配送中心特定的元数据,如库存状态和中心级需求信号,存储为每条记录中的嵌套文档。以下示例描绘了搜索索引中的典型文档。
{
"city": "mumbai",
"product_id": "SKU-29401",
"product_name": "Amul Butter 500g",
"category": "Dairy",
"offer_ids": ["OFFER-201", "OFFER-305"],
"tags: [weekend, liquidation],
"hubs": [
{
"hub_id": "MUM-HUB-01",
"rpi_score": 0.0142,
"stock_status": "in_stock",
"hub_signals": {"demand": "peak"},
"active": "true"
},
{
"hub_id": "MUM-HUB-02",
"rpi_score": 0.0147,
"stock_status": "low_stock",
"hub_signals": {"demand": "low"},
"active": "false"
},
..
]
}文档结构在按城市-产品对组织索引的同时支持店铺级个性化。
搜索管道:Zepto的搜索平台在应用层将搜索请求流程与索引流水线解耦。当客户发起搜索时,请求通过Zepto应用程序传递到搜索服务和编排层,该层查询OpenSearch索引并组装响应。
搜索服务和编排层处理完整的查询生命周期。这包括查询理解、候选检索、机器学习(ML)排名、广告插槽和响应组装。有关Zepto完整搜索架构的详细概述,请参阅《构建10分钟世界的搜索》,见Zepto工程博客。
扩展挑战
Zepto的搜索平台最初只有一个用例,即基本的产品搜索。随着业务扩展,平台引入了越来越复杂的体验,每个新体验都向索引流水线添加了索引信号,如优惠事件、清仓标签、定价变化、排名分数等。所有这些都需要被摄取并反映在索引中。同时,用户流量的增长和浏览面的扩大增加了集群的读取吞吐量需求。
在排灯节和新年等节日活动期间,挑战最为严峻,流量激增需要将数据节点数量扩展到1.4倍。尽管集群处理了节点添加,但团队需要在每一步监控分片迁移进度并验证搜索延迟是否保持在服务级别协议(SLA)内。这种操作开销随着每次扩展事件而增加。
向集群添加更多节点可以解决即时吞吐量限制,但代价是基础设施支出成比例增加。为了找到解决方案,Zepto设定了一个明确的目标:“在不增加数据节点数量的情况下提高吞吐量。”
解决方案概述
为了保持节点数量不变,Zepto尝试了多种配置。一种方法是重新分片,调整主分片的数量以更好地在现有节点之间分配工作负载。然而,在代表生产环境的流量下进行负载测试显示,每种重新分片配置都会降低搜索延迟。重新分片操作本身在操作上也很昂贵,需要完全重新创建索引、数据迁移和扩展验证窗口。
团队需要一种根本不同的方法。这种方法需要在不添加节点或重新分片索引的情况下提高吞吐量。
评估OpenSearch优化实例
OpenSearch优化实例是专为需要高索引吞吐量和成本效益的工作负载而构建的实例系列。它们通常用于日志分析和时间序列用例。这些实例将数据存储在Amazon Elastic Block Store(Amazon EBS)卷上,以便快速本地访问。Apache Lucene段被同步复制到Amazon S3,提供11个9的数据持久性。
尽管通常与日志分析用例相关联,但我们建议评估OpenSearch优化实例类型OR2,用于Zepto的产品搜索工作负载。团队评估了两个关键标准以确定可行性:
标准1:段复制能否解决吞吐量瓶颈?
在文档复制(内存优化实例的默认设置)下,每个写入都在主分片上建立索引,然后在每个副本上独立重新索引。这会在集群中重复CPU工作。在OpenSearch优化实例上使用段复制,段在主分片上构建一次,然后作为完整文件复制到副本。这消除了副本上的重复索引流水线,并释放了它们的计算资源用于服务搜索查询。Zepto的工作负载涉及来自多个流水线的持续索引,这些流水线与搜索流量竞争。这种分离是关键架构优势。
标准2:搜索平台能否容忍10秒的刷新间隔?
OpenSearch优化实例使用10秒的段复制刷新间隔,比内存优化实例的默认一秒刷新更长。这意味着新索引的文档在最多10秒的额外延迟后才可搜索。团队评估了这种权衡是否适用于他们的搜索用例。
Zepto高级架构师Rahul Pradeep解释道:
“缺货或现货不是检索的主要参数。它更像是一个决胜因素。相关性是我们的主要参数。我们在一次查询中检索数百种产品,然后针对我们的实时库存服务进行最后一刻验证。这就是为什么我们可能不需要一秒刷新。”
Zepto现有的架构中,产品丰富服务在检索后验证库存,表明10秒的刷新间隔不会影响客户体验;详见Zepto如何大规模构建产品丰富以了解更多详情。迁移是可行的,无需任何应用级更改。
基于这一评估,解决方案涉及从基于Graviton的内存优化数据节点迁移到OpenSearch优化实例。这一转变改变了索引工作在集群中的分布方式。不再采用每个节点重复完整索引流水线的模型,而是仅主分片执行索引,副本接收预构建的段。
负载测试
在承诺迁移之前验证假设,我们设计了一个概念验证,在其较低环境中设置了模拟生产特性的负载测试设置:
- 使用r7g.12xlarge实例的基线集群和使用or2.12xlarge实例的并行测试集群,每个集群有四个节点。
- 相同的分片配置(X个主分片,Y个副本,每个节点Z个分片副本)。
- 同时进行索引和读取负载模拟。
文档结构改进
除了验证基础设施更改外,团队还发现了优化文档结构本身以进一步提高搜索延迟的机会。他们添加了一个active_hubs 属性添加到基础文档中,一个扁平数组,仅列出产品当前有库存且活跃的集散地,如下面的更新文档结构所示。
{
<City and product metadata>,
"active_hubs: [MUM-HUB-01, MUM-HUB-03],
"hubs": [
{
"hub_id": "MUM-HUB-01",
"rpi_score": 0.0142,
"stock_status": "in_stock",
"hub_signals": {"demand": "peak"},
"active": "true"
},
..
]
}下表总结了负载测试的关键指标,比较了相同条件下 r7g.12xlarge 基线集群与 or2.12xlarge 测试集群。
| 指标 | r7g.12xlarge | or2.12xlarge | 变化 |
| 峰值索引滞后 | 约12M 文档 | 约6M 文档 | 2倍更快 |
| 索引吞吐量 | 基线 | 2倍更高 | 100% 改进 |
| 搜索延迟(p90) | 187 毫秒 | 89.1 毫秒 | 52% 改进 |
| 搜索延迟(p99) | 244 毫秒 | 175 毫秒 | 28% 改进 |
下图描绘了两个集群之间的 P90 搜索延迟比较。

图 2:r7g 与 OR2 集群的 P90 搜索延迟比较
下图描绘了两个集群之间的 P99 搜索延迟比较。

图 3:r7g 与 OR2 集群的 P99 搜索延迟比较
下图描绘了两个集群之间的索引延迟比较。
图 4:r7g(左)和 OR2(右)集群的索引延迟比较
关键见解
- 索引吞吐量的改进:OR2 更高的索引吞吐量在夜间批量操作期间最为明显,当时来自 RPI 分数重新计算、标签更新和目录丰富的事件同时涌入索引管道。在 r7g 集群上,P1 索引滞后峰值超过 1200 万文档。在 OR2 上,索引吞吐量约为 2 倍,相同的事件量产生的峰值滞后仅为 600 万文档。更高的吞吐量直接转化为更低的滞后和更新的搜索结果。这归因于 OR2 的段复制方法,该方法消除了副本上的冗余索引工作。每个文档只在主分片上索引一次,而不是在每个副本上重放。
- 搜索延迟的降低:P90 搜索延迟从 187 毫秒降至 89.1 毫秒(改进 52%),P99 从 244 毫秒降至 175 毫秒(改进 28%)。这些收益主要归功于
active_hubs文档结构的变化,而不仅仅是实例类型迁移。通过在索引时预先计算活跃集散地的扁平列表,查询不再需要遍历嵌套的集散地文档来确定可用性。这创建了一个轻量级预过滤器,消除了搜索时不必要的计算。
生产规划和推出
负载测试结果让 Zepto 有信心做出一个关键的架构决策:减少整体数据节点数量。更高的每节点索引吞吐量意味着使用更少的节点即可用 OR2 处理相同的工作负载,并且成本节省是复合的。每个减少的节点都从集群中移除了计算、存储和运维开销。Zepto 将此带入生产环境,将 OR2 集群配置为原始节点数的三分之二。下表总结了前后对比。
| 指标 | r7g.12xlarge | or2.12xlarge | 变化 |
| 所需数据节点 | 3倍节点 | 2倍节点 | -33.3% |
| 成本节省 | 基线 | 基线的2/3 | +30% |
Zepto 没有完全切换,而是采用了分阶段的推出策略,使用基于桶的流量路由,在大约两个月内完成了迁移,零停机时间:
- 在 OR2 实例上配置了新的 OpenSearch Service 域,并启用了段复制。
- 执行并行索引管道以填充 OR2 集群,同时现有的 r7g 集群继续服务生产流量。
- 首先将内部用户路由到 OR2 集群,以在真实查询模式下验证搜索质量、相关性和延迟特性。
- 逐步增加外部用户流量,按桶进行,在每个增量时监控比较仪表板,以检查延迟回归或相关性漂移。
- 在整个迁移过程中维护并行仪表板,以实时比较 OR2 和 r7g 集群。监控的关键指标包括 p50 和 p99 搜索延迟、索引吞吐量、副本滞后、Java 虚拟机(JVM)堆利用率、熔断事件、每秒 I/O 操作(IOPS)利用率和磁盘吞吐量。
挑战和经验教训
在迁移过程中,团队遇到了一个值得注意的挑战:段合并期间的延迟峰值。这一观察为评估用于搜索工作负载的 OpenSearch 优化实例的团队提供了实用指导。
症状:在将大量流量转移到 OR2 后,Zepto 观察到与段合并操作相关的间歇性 p99 延迟峰值。
根本原因:大型段合并消耗了显著的 I/O 带宽,暂时影响了并发搜索查询性能。原始的 256 GB EBS 卷没有为并发合并和搜索操作提供足够的 IOPS 缓冲。
解决方案:实施了以下两项更改:
- 将每个节点的 EBS 卷大小增加到 1 TB。对于 gp3 卷,基准 IOPS 随卷大小增加。这为并发操作提供了缓冲。
- 调整了段合并策略。将
max_merged_segment(参见 OpenSearch:Force Merge API 了解更多详情)从 5 GB 减少到 2 GB,并将segments_per_tier(参见 OpenSearch:Index Settings 了解更多详情)从 10 减少到 5。这会产生更小、更频繁的合并,更均匀地分布 I/O 负载,而不是不频繁的大合并导致延迟峰值。
增加 EBS 卷大小并调整段合并策略后,延迟峰值减少。合并期间仍会发生瞬时峰值,但会迅速稳定在可接受的范围内。
生产切换
最后,Zepto 在四周内从部分流量逐步过渡到 100% 流量,并在确认多个高峰流量周期内性能稳定后退役了之前的集群。下表总结了迁移前后的集群配置。
| 参数 | 之前的集群 | 当前集群 |
| 实例类型 | r7g.12xlarge | or2.12xlarge |
| 数据节点 | 3倍节点 | 2倍节点 |
| 每节点 RAM | 384 GiB | 384 GiB |
| 复制策略 | 文档复制 | 段复制 |
| 默认刷新间隔 | 1秒 | 10秒 |
| 持久性 | 跨可用区副本 | S3同步复制 |
结论
在这篇文章中,我们描述了Zepto对OpenSearch优化实例在延迟敏感的产品搜索中的评估以及其生产迁移的结果。通过从内存优化数据节点迁移到启用段复制的OpenSearch优化实例,Zepto实现了超过100%的索引吞吐量提升和30%的成本节省,同时将集群的数据节点数量减少到之前的三分之二。
Zepto的迁移表明,OpenSearch优化实例不仅是日志分析的可行选择,也是延迟敏感的产品搜索的可行选择。对于检索层能够容忍秒级数据延迟的工作负载,因为实时一致性在其他层解决,适合采用OR2实例。对于将候选生成与可用性验证分离的电子商务和快速商务平台,这种模式可以显著降低基础设施成本。
如果您的工作负载具有高索引量,并且可以容忍10秒的刷新间隔,请考虑为您的集群评估OpenSearch优化实例。要开始,请:
- 评估您的工作负载适配性:查看您当前的索引吞吐量、副本数和刷新间隔要求。如果您的负载具有较高的写读比,请优先考虑这种方法。
- 执行概念验证:在较低环境中配置一个小型OpenSearch优化集群,使用相同的分片配置并恢复生产索引快照。同时执行索引和搜索负载,以验证吞吐量和延迟。
- 规划分阶段推出:使用并行索引和基于桶的流量路由来增量迁移,零停机,并在每个步骤监控索引滞后和搜索延迟。
要了解OpenSearch优化实例背后的架构,请参阅《深入了解:OpenSearch优化实例》。有关实际配置指导,请参阅《使用OpenSearch优化实例提高性能》。我们欢迎您在下方评论区提出问题和反馈。
这篇内容对你有用吗?
反馈只用于改善内容筛选,不等同于收藏