支持食べログ餐厅搜索的技术包括Debezium和Apache Kafka
你好,我是食べログ系统总部技术部微服务化团队的@weakboson。
今年的Advent Calendar中,我们介绍了食べログ引入Debezium和Apache Kafka(以下简称Kafka)来提升餐厅搜索索引同步系统的性能的案例。
什么是微服务团队?
我所属的微服务团队有一个使命,即解决巨型单体服务开发的困难,并构建一个能够让小团队自主决策并进行开发的系统基础设施。
食べログ自2007年以来采用Ruby on Rails开始开发和运营,已经15年的时间,没有进行根本性的架构更新,导致系统变得庞大而笨重,代码和数据量都非常巨大。说实话,目前的开发效率并不是很高。要将这个庞大的系统重构为高内聚、低耦合的结构,仅仅依靠开发工程师从业务领域进行整理是困难的。我们需要一项支持重构策略的武器。“我们需要一种新武器!”
题目:餐厅搜索索引同步系统是旧版的,性能不佳。
在食べログ中存在一个问题,即从业务数据生成导出数据的许多系统都是基于传统架构运行的。其中一个典型的系统是用于同步业务数据和餐厅搜索索引数据的系统。
食べログ的餐厅搜索是使用 Apache Solr(以下简称 Solr)构建的。 Solr 的搜索索引需要非规范化的数据,很可能有许多系统会从规范化的业务数据异步更新。食べログ也不例外,它从 MySQL 中异步更新业务数据,但同步系统的综合性能较差,导致无法进行大量数据维护或者进行大规模变更索引的难题。

-
- 同期処理全体が cron バッチで稼働しており業務データの更新内容が検索インデックスデータに反映されるまでのレイテンシが悪い
-
- 業務データの更新タイムスタンプを WHERE 句で指定したクエリで更新を検知しており負荷が高いうえに遅い
- 検索インデックスデータの更新処理がシングルプロセスでスループットが低い
由于开发团队在餐厅搜索方面频繁遇到性能问题,所以微服务团队提出了重新设计架构的建议。微服务团队已经验证了派生数据生成基础设施的实践并且,这个项目是开发团队和微服务团队共同的双赢合作项目,使得负责系统得以改进。
提高对课题的解决能力
我先调查了详细信息,以提高解决问题的分辨率。
我认为了解当前状况的定量情况是设定一个能够使动力上升的目标所不可或缺的步骤,虽然有一点困难但并非不可能的。
我們分析了前幾個月的日誌,並繪製了以下的成果圖表。橫軸表示檢索索引數據的更新數量,縱軸表示處理時間。藍色的點表示業務數據更新的檢測時間(左邊的縱軸),橙色的點表示檢索索引數據的更新時間(右邊的縱軸)。

我們從中發現,無論更新數量如何,檢測需要花費相當長的時間,而更新則大致上是根據更新數量相應的時間。至於具體的時間單位,我們暫且不予提供。根據計算,完全反映在搜索索引數據中將需要約16天的時間。
设定改善目标
接下来,我设定了具体的目标。
我个人非常尊敬且关注的工程师Tori Hira在他的演示幻灯片「独断专横的平台」中提到了以下内容。
确认问题设置是否可以进行测量是很重要的。
餐厅搜索索引同步系统的改进只是“稍微好一些”还不够,必须要达到“大量更新业务数据进行维护”和“搜索索引数据发生重大变化的规格更改”不会被排斥的状态。
与开发团队讨论后,提出了以下目标:“即使在口碑改变等大规模更新发生时,也希望能在工作时间内解决。”于是我们设定了目标:“在8小时内将所有业务数据同步到搜索索引数据中。”考虑到前面提到的大约需要16天完成的预期,这是一个48倍更具挑战性的目标。虽然很困难,但我们以“两个部门投入了相应的成本引进新武器,我们也应该有相当的性能!来吧!”的心态开始了项目。
我们选择新武器
技术选择1 变更数据捕获 – 更新检测和事件驱动的核心力量
首先,我们考虑了在先前的图表中用蓝色点标注的”检测时间”的改善。检测时间延迟的原因主要分为两个。
-
- 業務データには更新タイムスタンプにインデックスがないテーブルがありクエリが遅い
- 業務データにはレコードを物理削除しているテーブルがあり差分を検索インデックスデータと全件同士比較していて遅い
对于这两点,能够非常方便地满足的解决方案是Change Data Capture。它是一种将RDB的更新,包括DELETE操作,捕获并转换为事件流的技术。
Change Data Capture的工作原理有几种,但大多数都利用了RDB的复制接口,具有相似的特性。也就是说,它可以以低延迟、低负载的方式捕获包括DELETE在内的几乎所有更新事件。
我选择了作为Change Data Capture产品中最成熟的Debezium。Debezium之所以受到高度评价,是因为它采用Kafka作为事件流传输工具。
因为 RedHat 公司的技术博客中有一篇非常详细的 Debezium 特点说明,所以我想进行引用。
由于你可以将对现有数据库的数据操作转换为Kafka消息,因此这是一个非常方便的软件,特别是在想要实时处理数据但不想改动应用程序(如遗留系统)或者希望将数据流用于其他系统的情况下。
(略)
通过使用通用的消息总线Kafka,不仅可以进行简单的数据复制,还可以将同一消息流多次重用于不同的目的。作为遗留系统迁移的一种手段,它有很多用途。
近年有一些产品致力于成为更优质的Kafka替代品,但人们普遍认为,Kafka仍然是生态系统中效能最高的选择。有这样的想法,如果倚靠可持续发展的Kafka,就不会在不到几年的时间内变得陈旧过时。
使用Debezium捕获的更新事件示例在JSON中的表示如下。由于可以完全了解更新前后的字段,因此可以执行不必要的事件过滤操作,例如仅更新时间戳的情况。对于INSERT操作,”before”字段为空,对于DELETE操作,”after”字段为空。
{
"before": { // DBの更新前の値
"id": 47048985,
"restaurant_id": 1234567,
"hogehoge_status": 2,
},
"after": { // DBの更新後の値
"id": 47048985,
"restaurant_id": 1234567,
"hogehoge_status": 1,
},
"op": "u", // 操作の種別 "u" は UPDATE
"ts_ms": 1633865473919,
}
通过引入数据更改捕获技术,之前需要花费相当长的时间才能检测到的延迟几乎降为零。此外,还可以将整个驱动系统从cron批处理切换到以Kafka消息为基础的事件驱动模式。
使用Kafka Consumer进行技术选择并进行并行化处理。
然后,我们考虑了“搜索索引数据更新”的吞吐量提升。可以通过使用Ruby的性能分析工具stackprof和rblineprof来了解方法调用次数、所需时间和逐行所需时间。
在开发服务器上进行配置文件测量后,以下是占据搜索索引数据更新时间前3位的情况:
-
- ActiveModel 方法调用的时间
-
- 使用元编程动态定义方法 define_method 的处理时间
- 餐厅周边车站的预计算时间(包括 MySQL 处理)
我們發現Ruby代碼的執行時間比MySQL的處理時間更加支配。如上所述,批處理伺服器擁有16個虛擬CPU和32 GB的記憶體,這是非常奢侈的規格;然而,由於Ruby原則上只能使用一個處理器,因此在單進程批處理中無法充分利用這個規格。因此,我們需要將處理並行化為5個處理步驟。

Kafka主题有一种内部分区结构,可以使用与分区数相同的最大消费者并行处理。尝试了开发环境数据后,发现吞吐量大致与消费者数量成比例地提高了。
此外,Kafka还有一个特性,即具有相同键的消息每次都进入同一分区。(因为Kafka是事件流而不是RDB,所以相同键的消息不会被覆盖,而是全部保留。)这个特性在地下充满重要性,将消息的键和搜索索引数据的键合并在一起,消费者进程之间就不会发生更新相同键记录的竞争。即使并行化,也不会发生竞争,从逻辑上来说,纯粹是通过乘法扩展的能力。

我提供了一个2 vCPU * 2 VM的配置,虽然我觉得在实际数据上可能不能很好地运行。但实际上,批处理的吞吐量达到了大约4倍。一旦这样,我们就兴奋地建立了一个8 vCPU * 6 VM的配置来搭建生产用的消费者集群。
新的餐厅搜索索引同步系统的整体概述
通过Change Data Capture和Kafka Consumer构建的整个系统如下图所示。(为了更容易理解,省略了Kafka Consumer的并行结构。)可以将整个流程类比为所谓的ETL处理的Extract步骤,各个步骤分别表示为Extract、Transform、Load。

这个系统的性能远远超出了目标,竟然能在约3.5小时内同步所有业务数据。举个例子,比如在上午9点左右发布了一个大的规格变更,到中午一点多的时候,新规格的搜索索引数据就能够生成。这对我们设计的微服务团队来说也是惊喜。
因为我已经告诉您已经达到了最重要的改善目标,所以现在我会就之前没有提到的要素进行补充性的说明。
配置管理Change Data Capture
Change Data Capture 是一个系统基础设施,我们希望除了用于未来的餐厅搜索索引同步之外,还可以考虑其他的应用场景。由于这是第一次引入这项技术,所以我们首先只捕获餐厅搜索索引所需的最小表,并从设置每个主题的容量上限开始,而不是使用难以预测的时间范围来保留事件数据。
这样一来,我们就需要建立一种方法来处理餐厅搜索的规格更改以及 Change Data Capture 的应用范围扩大所带来的设置更改。
Debezium是Kafka数据同步生态系统的一种Kafka Connect,可以使用Terraform的Kafka Provider进行配置管理。
Kafka主题设置和Debezium捕获目标表以variable.tf文件的形式在GitHub存储库中进行管理,并且即使对于不熟悉Terraform的工程师,也可以提交Pull request来添加捕获目标。
關於 Kafka Consumer,它與食べログ本體一同通過 CI/CD 流程進行部署和重啟。更新搜尋索引資料的邏輯也完全重複利用了傳統的程式碼,因此在新系統中,開發團隊的工程師們能夠實現與以往相同的開發體驗。
事件聚焦消费者
这个图表中首次出现的元素 “事件汇总消费者” 是一个将被规范化的业务数据表中分散在多个主题上的事件聚合到以餐厅ID为键的一个主题上的组件。尽管在捕获后转换键会导致精确的更新顺序丢失的缺点,但由于业务数据中存在没有餐厅ID的表,我们别无选择而设立了这个组件。由于搜索索引数据的更新已经足够结果一致性,所以即使确切的顺序无法保持,也没有问题。
监视
只要保持警戒并进行监视,就能安心运作。
由以Kafka为核心的生态系统所提供的监控解决方案非常齐全,所以监视的建立也没有特别大的困难。

除了基本的指标监控外,我们还使用Kafka Monitor进行语义监控,使用Burrow进行消费者延迟监控。所有指标都在Prometheus和Grafana上进行可视化,当延迟出现突然激增或超过阈值时,我们会通过AlertManager向OpsGenie发出警报。
为了方便进行大量业务数据更新的维护工作,我们还准备了捕获数量和消费者延迟的仪表盘。这些仪表盘可用作更新速度调节的参考。

总结
-
- レストラン検索インデックス同期処理全体を cron バッチから Kafka Consumer によるイベント駆動に切り替えることで業務データ更新から検索インデックスデータに反映するタイムラグを数秒程度に短縮できました
-
- Change Data Capture により業務データの更新検出を低負荷・低レイテンシに改善できました
- 検索インデックスデータの更新が競合しないように高度に並列化することでスループットを大きく向上できました
最后
在这篇文章中虽然省略了一些辛酸的经历,但我们成功地通过各种技术基本上顺利实现了超越目标的改善。然而,食べログ系统仍然存在许多问题,我们的微服务团队正在寻找与我们一起推进改善的伙伴。
我不知道在这篇文章中是否能完全传达出来,但是作为微服务团队,您将参与从战略规划到选择引入技术等系统改进的初期阶段的决策过程,这是一个具有巨大裁量权和挑战性的职位。如果您对「连接用户和餐厅」这个食べログ的理念产生共鸣,请务必申请加入我们。
如果您希望先進行一次轻松的面谈来进行信息交流,我们也非常欢迎。如果是这种情况,请在申请时在自由文本填写栏中注明”希望进行轻松面谈”。
明天是 @skido 的「新入职工程师加入食评团队的学习心得」。敬请期待!
我们需要一种新的武器!这是电影《环太平洋》高潮部分的对白,也是原声带的第25首歌曲标题。
将餐厅名称的不同写法进行搜索转换,并且口碑、照片、菜单等文本的汇总处理是特别繁重的工作。一般来说,受欢迎的餐厅口碑数量较多,更新搜索索引需要时间,而且更新频率较高。这导致了后面提到的目标与达成值之间的差距。
在技术部门,我们采用OKR作为目标实现工具,每个季度各个团队设定这样的挑战目标,并在部门内公开。
除了JSON之外,还可以选择其他格式。很多系统采用Avro作为格式,但要享受Avro的优势,稳定运用Schema Registry是不可或缺的,所以我们认为一开始就引入它是有一定障碍的,所以这次我们暂时不采用。
由于还有其他的批处理在运行,所以这个规格并不是浪费的,但确实没有充分利用。
换句话说,分区数成为了并行度的上限,如果并行度不足,则需要增加主题内的分区切分数。
为什么性能超出预期的两倍以上呢?这并不是因为每个要素都有预想之上的改善效果,而是因为用于资源计算的实际数值并不是所有店铺的平均值。正如前面所说,更新时间长且频繁的受欢迎餐厅更新频率较高,因此可以推测实际数值存在偏差。
在重要的系统中,顺序一致性也需要使用Change Data Capture和Kafka进行投入,从投入时开始设计不对关键字进行洗牌。