返回
RSS AWS Big Data Blog 发布 2026-08-04 23:55 24

Delivery Hero迁移至OpenSearch Service:径向搜索优化语义检索

Delivery Hero自2024年起将语义搜索用于杂货垂直领域,从基于SpringBoot和Lucene 9.9的原型系统迁移至Amazon OpenSearch Service。他们采用径向搜索替代传统k-NN搜索,并与词法检索结合,构建高性能、低成本且灵活的混合搜索系统。该方案在Kubernetes上实现生产级部署,支持大规模产品相关性检索。
推荐理由:该案例展示了数据平台中语义搜索与向量检索的工程实践,对使用OpenSearch或关注检索基础设施的从业者具有参考价值。
AWSAmazon OpenSearch Service

译文 AI 逐段翻译

在 Delivery Hero 转型搜索:使用径向搜索迁移至 OpenSearch Service 的历程

你是否曾在任何在线杂货店搜索过“低脂酸奶”之类的词,并注意到结果似乎理解你的意思?除了仅显示精确匹配的项,排名靠前的产品通常在语义上相关。你可能会看到“希腊酸奶”或“0.5% 脂肪的酸奶”等项目,即使只有单词在词汇上匹配。这就是语义搜索的力量,与传统的词汇搜索相结合时,它创造了一种混合搜索体验,同时提供精确性和召回率。

语义搜索返回与低脂酸奶查询语义相关的产品

在 Delivery Hero,全球领先的在线食品配送平台之一,搜索团队自 2024 年起一直在杂货垂直领域使用语义搜索。最初的概念验证已演变为由 Amazon OpenSearch Service 驱动的生产级混合搜索系统。该系统将径向向量搜索与词汇检索相结合,以大规模提供高度相关的产品结果。

在本文中,我们介绍了 Delivery Hero 如何将其语义搜索基础设施迁移到 Amazon OpenSearch Service,为什么他们选择径向搜索而非传统的 k 最近邻 (k-NN) 搜索,以及使系统快速、经济高效且便于实验的优化。

传统系统概述

最初的语义搜索系统是使用 SpringBoot 和 Apache Lucene 9.9 构建的独立服务,部署在 Kubernetes 上。检索流程如下:

  1. 用户在应用程序上开始搜索。
  2. 语义搜索系统从静态内存 Lucene 索引中检索前 50 个最近邻候选。
  3. 这些候选通过过滤层以移除缺货商品。
  4. 过滤后的语义结果与并行的一组词汇搜索结果合并。
  5. 最终排序步骤结合两个候选集以产生响应。

团队迭代了这个系统七个版本,并进行了多次 A/B 测试以完善方法。最初系统表现良好,但随着业务扩展,出现了几个痛点:

  • 可扩展性限制:在 Kubernetes pod 内将向量索引作为静态内存结构运行意味着扩展需要配置更大的 pod 或添加副本。这两种选择都昂贵且操作复杂。
  • 多模型实验困难:使用三种不同产品嵌入模型变体进行 A/B/C 测试需要将所有模型放入 Kubernetes 无状态工作负载中。这造成了内存压力并使部署流程复杂化。
  • 运营开销:管理基于自定义 Lucene 的服务的索引构建、部署和版本推出需要大量的工程工作,而托管服务则不然。

使用 OpenSearch Service 的架构现代化

到 2025 年底,Delivery Hero 已将其整个搜索基础设施从 Google Kubernetes Engine (GKE) 上的自管理 Elasticsearch 7.x 迁移到完全托管的 Amazon OpenSearch Service 3.x。这次迁移为将传统语义搜索服务整合到 OpenSearch 中创造了自然的机会。

新架构将关注点分离为两个不同的管道:用于索引产品嵌入的摄取管道,以及用于实时混合检索的推理管道。

摄取管道

对于摄取管道,Delivery Hero 选择了 Amazon OpenSearch Ingestion (OSIS) 来将产品嵌入数据从 Amazon Simple Storage Service (Amazon S3) 同步到 OpenSearch 域。

通过 OpenSearch Ingestion 将产品嵌入从 Amazon S3 同步到 Amazon OpenSearch Service 的摄取管道

流程如下:

  1. ML 模型
  2. Airflow 作业: 现有的 Apache Airflow 作业定期使用外部机器学习 (ML) 模型生成产品嵌入,并定期将结果(产品父 ID + 嵌入向量)转储到 S3 存储桶。
  3. OpenSearch Ingestion 管道: 配置了 OpenSearch Ingestion 管道,带有计划 S3 扫描,每晚从 S3 扫描并更新 OpenSearch Service 中的新 k-NN 索引。
version: '2'
embedding-pipeline:
  source:
    s3:
      acknowledgments: true
      scan:
        buckets:
          - bucket:
              name: my-bucket-name
              filter:
                include_prefix:
                  - vector-search/json-index/latest
        range: PT24H
        scheduling:
          interval: PT24H
      aws:
        region: eu-central-1
        sts_role_arn: arn:aws:iam::<aws-account-id>:role/osis-pipeline-role
      codec:
        ndjson: {}
      compression: none
  workers: '1'
  sink:
    - opensearch:
        hosts:
          - "https://<search-domain>.<aws-region>.es.amazonaws.com"
        aws:
          serverless: false
          region: eu-central-1
          sts_role_arn: arn:aws:iam::<aws-account-id>:role/search-xxx
        index_type: custom
        index: emb_products_v1
        template_content: ...
        template_type: index-template
        routing: '${global_entity_id}'
        document_id: '${global_entity_id}:${master_code}'
        max_retries: '3'

由于索引存储产品父 ID,且嵌入是批量重新生成的,无需实时更新。这使得团队每天刷新并 强制合并 索引,从而高度优化的段结构和快速检索速度(高峰时段 p99 < 35 毫秒)。

设置 OSIS 管道仅需几行 Terraform,使其易于配置和维护为基础设施即代码。

推理管道

在检索方面,系统运行混合搜索策略,并行结合径向向量搜索和词汇搜索:

混合推理管道并行运行径向向量搜索和词汇搜索,然后合并和重新排序结果
词汇和语义搜索的 p95 OpenSearch 耗时对比图
  1. 查询嵌入:用户的搜索查询首先到达查询理解 (QU) 服务,在那里使用与产品嵌入相同的实时 ML 模型将其编码为嵌入。为了优化性能,热门查询的嵌入会被缓存。
  2. 对产品嵌入索引运行径向 k-NN 搜索,使用 min_score 来检索所有相似度高于阈值的语义相似产品。
  3. 对产品目录索引运行词汇 BM25 搜索。 比较词汇和语义搜索的 p95 OpenSearch 时间。
  1. ID 解析和库存过滤: 由于 k-NN 索引存储产品父 ID,解析步骤通过维护近实时库存更新的辅助索引将这些映射到单个产品 ID。这种方法在单次检索调用中满足两个关键业务需求:产品 ID 解析和实时可用性过滤。
  2. 合并和重新排序:一个自定义的后处理步骤结合了词法搜索和径向搜索的结果,应用重排序逻辑,并返回最终的结果集。

为什么使用径向搜索?

OpenSearch中的传统k-NN搜索采用top-k方法:你请求k个最近邻,无论它们实际有多相似,你都恰好得到k个结果。这对于许多用例来说效果很好,但对于产品搜索有一个根本性的局限。它总是返回固定数量的结果,即使其中一些结果在语义上不相关。

径向搜索通过翻转范式解决了这个问题。你不是问“给我最接近的50个物品”,而是问“给我所有至少这么多相似度的物品”。这可以通过k-NN查询中的min_score参数来实现:

GET product-embeddings/_search
{
  "query": {
    "knn": {
      "embedding": {
        "vector": [0.12, 0.45, 0.78, ...],
        "min_score": 0.72
      }
    }
  }
}

当使用径向搜索并将空间类型设为余弦相似度时,OpenSearch使用相关公式对分数进行归一化(score = (1 + cosine_similarity) / 2),如OpenSearch knn-spaces参考文档所述。

这意味着查询示例中的min_score 0.72并不直接对应余弦相似度。相反,0.72是归一化后的OpenSearch分数,它对应于44%的余弦相似度(即cosine_similarity = 2 × 0.72 – 1 = 0.44)

如果你需要至少90%的余弦相似度的结果,应用公式:

min_score = (1 + 0.90) / 2 = 0.95。所以,你应该在查询中设置“min_score”:0.95

这种方法为产品搜索提供了几个优势:

  • 质量优先于数量:低相关性的结果在检索阶段就被排除,而不是依赖下游的重排序来过滤掉它们。
  • 结果集大小可变:系统自然地适应查询的具体性。针对特定查询返回更少、更精确的结果。宽泛查询返回更多候选给重排序器处理。例如,一个高度具体的查询如“Oatly燕麦奶咖啡师版”可能返回5个结果,而一个更宽泛的查询如“牛奶”可能返回200个。
  • 更好的召回-精确权衡:通过调整min_score阈值,团队可以直接控制在返回过多不相关结果和错过相关结果之间的平衡。

Delivery Hero如何选择径向搜索的阈值

选择合适的min_score阈值很重要。设置得太高会错过相关产品;设置得太低会用噪音淹没重排序器。

Delivery Hero通过系统性的实验来选择阈值。为了在不同市场实现最佳精度,为每个国家和查询类型分配了定制的min_score阈值。这些阈值通过严格的离线评估精心确定,离线评估使用历史用户交互和人工标注数据来建立粗略估计。然后通过一系列在线A/B实验进一步细化和验证这一初步估计。

新搜索系统的评估

新架构的关键优势之一是它自然地支持实验。在Delivery Hero,我们在单个文档中存储三种产品嵌入变体:

PUT product-embeddings/_doc/1?routing=FP_DE
{
  "master_product_code": "abc123",
  "embedding_variant_1": [0.12, 0.45, 0.78, ...],
  "embedding_variant_2": [0.21, 0.4, 0.98, ...],
  "embedding_variant_3": [0.13, 0.65, 0.58, ...],
  "global_entity_id": "FP_DE"
}

在此示例中,embedding_variant_1、embedding_variant_2和embedding_variant_3由三种不同模型生成,用于A/B/C测试。每次测试后,获胜变体被指定为对照,而另外两个则被新模型替换以进行进一步实验。通过这种方法,团队可以在保持恒定空间复杂度的同时持续迭代。

大规模生产系统的优化

引擎升级:OpenSearch 2.17到3.3

OpenSearch 3.3升级后,来自最繁忙国家之一的生产k-NN查询延迟指标

来自最繁忙国家之一的生产指标。

OpenSearch 3.x引入了显著的性能改进,特别针对向量搜索工作负载。升级到OpenSearch 3.3后,我们观察到k-NN查询的p95延迟降低了约18%

对于Delivery Hero的用例,k-NN搜索延迟在OpenSearch 2.17上已经非常低(p99为20-30毫秒),这意味着所有集群并非必须升级到3.3。在A/B测试中服务对照组的集群仍运行在OpenSearch 2.17上。

分片路由

为了最小化k-NN查询期间的跨分片开销,Delivery Hero根据地理市场实现了自定义分片路由。由于每个市场(例如德国、瑞典和芬兰)都有自己的产品目录,将查询路由到特定市场的分片避免了整个索引的不必要扇出。

这是一个如何在索引时和搜索时使用_routing字段配置路由的示例:

PUT product-embeddings/_doc/1?routing=FP_DE
{
  "master_product_code": "abc123",
  "embedding_variant_1": [0.12, 0.45, 0.78, ...],
  "embedding_variant_2": [0.21, 0.4, 0.98, ...],
  "embedding_variant_3": [0.13, 0.65, 0.58, ...],
  "global_entity_id": "FP_DE"
}

在查询时:

GET product-embeddings/_search?routing=FP_DE
{
  "query": {
    "knn": {
      "embedding_variant_2": {
        "vector": [0.12, 0.45, 0.78, ...],
        "min_score": 0.72
      }
    }
  }
}

这确保了针对德国市场的查询只会命中包含德国产品的分片,从而降低延迟和计算开销。

刷新间隔

由于产品嵌入索引每天仅通过OSIS批处理管道更新一次,因此不需要默认的1秒刷新间隔。Delivery Hero在摄取期间为索引配置了更长的刷新间隔,并在夜间批处理完成后触发手动刷新和强制合并。

对业务的影响

从自管理的Kubernetes上的Lucene迁移到Amazon OpenSearch Service实现了p95延迟降低约50%,将响应时间从不稳定的200ms以上降至稳定的100ms基线。这一转变通过消除先前架构中的高方差和节律性延迟峰值,显著提高了系统一致性。

在foodpanda和yemeksepeti上推出基于OpenSearch的语义搜索后,端到端服务延迟降至稳定的100ms基线

在foodpanda和yemeksepeti上推出基于OpenSearch的语义搜索后的最终服务延迟。

除了原始延迟,运营收益也很显著:

  • 降低基础设施复杂性:消除独立的Lucene服务移除了一整套部署管道、监控栈和值班轮换。
  • 更快的实验:新嵌入模型可以通过创建新索引并调整查询路由来测试,无需代码部署。
  • 成本效率:使用 OpenSearch 的托管基础设施和批量摄取模式(每天刷新一次)相比运行常驻 Kubernetes Pod 和内存索引,降低了计算成本。

结论

通过结合径向搜索和词汇检索,Delivery Hero 团队构建了一个能动态适应查询意图的系统。它为具体查询返回精确结果,为一般查询返回更广泛的候选集。

迁移到 Amazon OpenSearch Service 表明,托管搜索平台如何简化向量搜索的运维复杂性,同时提升性能。

要开始使用 Amazon OpenSearch Service 上的向量搜索,请参阅AI 搜索文档OpenSearch 径向搜索指南

关于作者

分享这条资讯
分享海报
保存图片
iOS 也可以长按图片保存