pinterest 插画网站pinterest

Pinterest 的内部搜索引擎 Manas 是一个通用的信息检索平台 。正如我们在上一篇文章中讨论的那样,Manas 被设计为兼具高性能、可用性和可伸缩性的搜索框架 。如今,Manas 支持大多数 Pinterest 产品的搜索功能,包括广告、搜索、Homefeed、Related Pins、Visual 和 Shopping 。

搜索系统的关键指标之一是索引延迟,也就是更新搜索索引以反映更改所花费的时间 。随着我们系统的功能不断增加,新的用例持续引入,即时索引新文档的能力变得越来越重要 。Manas 之前已经支持了增量索引,能够提供数十分钟数量级的索引延迟 。不幸的是,这还不能满足我们来自广告和 following feeds 持续增长的业务需求 。我们决定在 Manas 中构建一个新模块,以进一步将索引延迟减少到几分之一秒的水平 。

在这篇博客文章中,我们描述了这一系统的架构及其主要挑战,并介绍了我们所做权衡的细节内容 。

本文由 Michael Mi 发表在 medium.com,经授权由 InfoQ 中文站翻译并分享
挑战新的需求伴随着新的挑战 。以下是我们面临的几个主要挑战 。
索引延迟对于 Lucene、Vespa 等开源项目来说,小批(tiny batch)方法(又称近实时)是最受欢迎的选择 。使用这种方法,只有在调用索引提交时才可以搜索新编写的文档 。结果,你需要在索引延迟和吞吐量之间进行权衡 。不幸的是,我们无法利用这种方法将索引延迟减少到几秒钟级别 。
索引刷新能力实时服务的缺点之一是缺乏索引刷新敏捷性 。对于一个批处理管道来说,重新运行索引作业以立即获取所有模式更改是很简单 。但当涉及到实时服务管道时,实现高效的索引刷新支持就是一件很复杂的事情了 。
为不断变化的数据实现扩展为了避免过度配置,系统采用了自动缩放以根据实际查询负载来调整副本 。如果索引是不可变的,那么新副本创建起来就相对容易:你只需将索引复制到新节点即可 。困难之处在于处理不断变化的索引:如何确保所有副本都具有相同的索引?
错误恢复Manas 是一项数据密集型服务,其中每台主机可提供的索引高达数百 GB 。Manas 也是一个有状态的系统,一个错误的二进制文件可能会导致连回滚都无法解决的数据问题 。我们需要构建一个同时支持容错和错误恢复的系统,以便从二进制错误和数据损坏中恢复 。
从静态到实时

pinterest 插画网站pinterest

文章插图
我们来简要介绍一下常规静态服务和实时服务之间的区别 。如上图所示,实时服务的主要工作是将索引管道从离线迁移到在线 。

对于静态服务,索引是通过一个批处理工作流离线生成的,然后将它们复制到 Leaf 用以在线服务 。对于批处理工作流,由于高昂的框架开销,几乎不可能在几分之一秒内建立可服务的索引 。实时服务不是使用脱机工作流,而是在服务中即时处理所有写入 。此外,实时索引管道用的是与静态索引管道相同的索引格式来处理写入,从而使我们能够重用整个索引读取逻辑 。记住这一点,我们来继续了解实时服务的工作机制 。
索引接口我们不是直接使用 RPC,而是使用了 Kafka 作为我们的高写入吞吐流 。Leaf 服务器不断拉取突变以建立增量索引 。事实证明,这一决策以多种方式极大简化了我们的系统:

数据复制和写入失败由 Kafka 负责 。借助回查能力,Kafka 队列也可以用作WAL 。在每个分区中都有严格的顺序保证,系统可以随意应用删除操作,而不必担心正确性 。架构概述由于服务逻辑可以通过共享索引格式重用,因此我们将重点放在索引数据流上 。

本质上,实时 Manas leaf 是一个LSM引擎,它将随机 IO 写入转换为顺序 IO,并为读取放大和写入放大应用程序提供高效的服务 。如下所示,整个索引流程包括三个关键步骤 。我们来一一讨论 。
pinterest 插画网站pinterest

文章插图
实时段构建除了现有的静态段(segment)外,我们还引入了实时段 。如上所示,系统中有两种实时段:活动实时段和密封(sealed)实时段 。

活动实时段是唯一可变的组件,用于累积从 Kafka 拉取的突变(添加/删除) 。值得一提的是,将一个文档添加到一个实时段后,在文档级别提交后即可立即搜索 。一旦活动实时段达到一个可配置的阈值,它就会被密封,转为不可变并放入一个刷新队列中 。同时,系统创建了一个新的活动实时段以继续累积突变 。