通过使用 Elasticsearch 将性能提高 1,000%
Voxpopme 是全球领先的视频洞见平台之一。我们公司成立于 2013 年,公司的初创理念很简单:视频是让成百上千的人们同时表达自己心声的最强大工具。我们的专有软件会采集由多家调查合作伙伴提供的消费者录制视频和长格式内容(例如焦点小组),并以图表、可浏览主题和定制宣传片的形式提供宝贵的营销数据。
自从成立以来,我们花了四年时间来对可从视频中获得的洞见进行调优和自动化,从而清除阻碍品牌和客户之间建立真正联系的壁垒。为了改善客户体验,我们使用 IBM 沃森提供的最新 NLP 工具来识别和聚合调查回复者的情感,并且与 Affectiva 达成了独家合作伙伴关系来分析面部情绪。在 2017 年,我们将 Elasticsearch 加入了我们的工具集内,以便为客户提供最佳体验。
增长速度超出遗留基础架构的承受范围
在过去 12 个月内,Voxpopme 的技术堆栈发生了翻天覆地的变化。在 2017 年,我们的平台处理了 50 万份视频调查,这可是我们前四年调查数量的总和,而且我们预计这一数值在 2018 年还会翻倍。然而我们在扩展时遇到了问题,虽然遇到扩展问题是个好现象(表明我们的业务量越来越大),但这个问题也得予以解决。
问题源自我们的遗留系统,此遗留系统由一个巨大的 PHP 应用程序构成,该 PHP 应用程序会与多个不同数据库进行交互以提供核心功能。我们最初采用数据分离设计,在开始阶段,这一设计背后的逻辑的确经得起推敲:
- 我们的大部分数据都存储在 MySQL 数据库中。这些数据包括用户、个人的视频回复等等,都是通过外部键关联到一起的结构化关系型数据,并且会通过 RESTful API 进行创建、读取、更新和删除;
- 客户数据则存储在 MongoDB 集群中,对于客户提供的所有数据,我们不拘形式统统都接受。这样用户便能向视频中添加标签和备注,并使用他们自己的术语进行筛选。
- 我们将受访者视频的文字转录存储在一个小型的 Elasticsearch 集群中,并会使用此集群进行全文本查询。
很长一段时间以来,这种方法的效果都很好,但是却有一个明显的问题。
计算能力既非免费,也非无限
我们平台上的搜索请求有时会出奇地简单,有时却又会十分复杂。在比较简单的实例中,我们允许用户按照视频回复的主键进行搜索,此类搜索很简单,只需对已索引的 MySQL 数据库进行一次简单查询即可。
但如果是下面这种情况呢:用户不仅希望按照已索引的整数进行筛选,还希望按他们某些自由形式的自有数据进行筛选,同时还希望将结果范围缩小至提到特定主题的全部回复者?如下面这个例子:
查找符合下列条件的所有记录:回复日期介于 6 月份第一天和最后一天之间,回复者的家庭收入位于 10 万至 12.5 万美元区间,并且他们提到了词语“太贵”。
下面是我们之前的方法:
如果使用我们的遗留系统,需要完成下列各个步骤:
- 运行一个 MySQL 查询,找到此日期范围内的所有记录;
- 运行一个 MongoDB 查询,找到此收入范围(收入只是一个例子而已;客户经常在我们这里存储完全随意的数据)内的所有记录;
- 运行一个 Elasticsearch 查询,找到在文字转录中包含匹配词语的所有记录;
- 从这三组记录集合中计算 ID 的重合部分;
- 重新运行一组 MySQL 和 MongoDB 查询,找到每条记录的完整数据集;
- 对这些数据进行排序和分页。
对于一个简单的搜索条件集合,很可能需要五次单独的数据库查询才能完成。最初,这是一个次秒级操作,但是在我们拥有了多达五年的数据之后,而且鉴于 PHP 是单线程运行(数据库查询必须逐个完成),所以查询时间经常会长达 30 秒。
当初我们正是使用这一模型快速在市场上推出了自己的服务,而且在多年内运行效果也不错,但是随着数据量的扩大,这一模式的局限性日益凸显。当觉察到用户体验开始下降时,我们决定是时候重新编写搜索机制了。
在原有的堆栈中,我们已经拥有了一些 Elasticsearch 使用经验,所以我们与 Elastic 的销售经理进行洽谈,讨论我们在对位于不同位置的一系列数据运行复杂搜索时遇到的问题。找到正确的解决方案至为关键:我们产品的核心就是快速高效地展示内容并对我们的数据进行计算;我们无法接受在检索数据时存在瓶颈。
我们同时还考虑了扩展我们既有的 MongoDB 集群,但是与 Elastic 进行简短的电话咨询后,开发团队确定 Elasticsearch 才是唯一的理想解决方案,能够允许我们轻松地存储、搜索和操控数据(即聚合)。
我们的第一印象
我们之前用于文本搜索的 Elasticsearch 集群是来自 Compose.io 的 1.5 版集群。由于我们的其他基础架构都大量使用 AWS,所以最初选择的是 Amazon Elasticsearch 服务,运行的是一个 5.x 版集群。
我们的新模型要求将每条记录的所有不同数据都存储到单一的 Elasticsearch 文档中,针对我们用户更加难以处理的自由形式数据则使用带有已知键的嵌套值。这样,无论是什么样的用户搜索,都能由 Elasticsearch 通过单次查询来完成。
在一周内,我们便使用几千份文档进行了基本的概念验证,对于在 Kibana 中运行查询,我们的第一印象还不错,所以我们十分高兴地加紧开展下一步工作,即完全重新编写后端的搜索机制。
下面便是我们的新方法:
(目前,我们还未对遗留数据库进行更改,但是由于我们已针对复杂搜索推出了新的 Elasticsearch 集群,所以我们能够制定计划来将 MongoDB 从我们的堆栈中完全移除)
通过新堆栈,我们像之前一样继续向 MySQL 和 MongoDB 中写入数据。但是,每次写入操作还会触发一个事件,此事件会从各种来源收集我们的全部数据,并将这些数据压平为单一的 JSON 文档,然后再将此文档插入到 Elasticsearch 中。
我们在后端编写了一套新的搜索机制,这套机制会基于用户的既有搜索请求来构建复杂的 Elasticsearch 查询,从而完全移除了查询 MySQL 和 MongoDB 时所用的遗留代码。由于能够在单一 Elasticsearch 集群内进行查询,这使得我们能够在极短时间内获得同样的数据。
由于 Elasticsearch 文档的结构,我们还实现了其他改进。因为 API 返回 JSON 文档,所以我们能够调整 Elasticsearch 文档的结构,使其与我们的既有 API 输出准确匹配,从而进一步缩短时间,而在之前,我们还需要花费时间将来自 MySQL 和 MongoDB 的输出数据进行整合并重新确定结构。
我们得以向公司的不同利益相关方提交报告,并承诺初期将平台关键部分的性能提高 1,000%。
这并不是您要寻找的供应商
在向所有人承诺将性能提升 1,000% 后,下一步就是对我们的全部数据进行索引,并希望这些性能指标在压力之下也能尽如人意。
将我们的所有数据都索引到 AWS 集群中后,很遗憾,效果并不太好。
如果只是简单地对数据进行单次索引,然后完成多次查询,我们可能不会遇到任何问题。但是下面这点对我们的模型而言至为关键:无论 MySQL 和 MongoDB 中的数据何时进行更新,此模型都要对数据进行整合和重新索引。所以,在用户极有可能进行搜索的期间,该模型可能需要进行数千次这样的整合和重新索引操作。
我们发现性能大幅下降,并且查询时间有时会长达数秒。这还会造成一个不良影响,即可能会锁定我们的 PHP 应用程序,进而断开 MySQL 的连接,由于可怕的多米诺效应,我们的整个堆栈可能会变为无响应状态。
我们遇到这个问题的时候,正好赶上 Elastic{ON} 活动在伦敦举行,所以我们充分利用这次机会与 AMA(百事通)展台的 Elastic 代表讨论了集群规模以及我们面临的问题。我们提到最短案例查询时间大约是 40 毫秒,这时 Elastic 专家建议我们选用他们自己的 Elastic Cloud 服务,而不要使用 AWS;如果选用 Elastic Cloud 服务,通过默认配置便可将响应时间缩短至接近 1 毫秒。
Elastic Cloud 上提供的 Elastic Stack 功能(之前的 X-Pack)对我们帮助很大,因为我们那时仅拥有极其有限的日志和图表系统。由于 AWS 上的 Elasticsearch 产品不提供 X-Pack,所以我们决定快速部署一个 Elastic Cloud 集群以开展并行测试;如果效果能够做到至少与 AWS 相当,由于 Elastic Cloud 还有其他功能,我们就会改用 Elastic Cloud。
Elastic Cloud 的 UI 十分简洁易用。在索引期间,我们需要加大规模,因为公司增加了更多人员来处理数据,我们对管理集群的轻松程度印象十分深刻:只需将滑条向左或向右移动,然后点击“更新”就可以了。
数据成功索引到新集群之后,我们能够以最快 2 毫秒的速度执行复杂查询,而且基本或者完全不会发生锁定情况(我们之后还开展了进一步优化以完全避免锁定情况)。尽管终端用户在查询时通常感觉不到几微秒的差别,但是对我们技术人员来说,能够将延迟缩短至之前的 5%,我们都感到十分高兴。
最充分利用我们的数据
但是,我们不想止步不前,也不想仅将 Elasticsearch 局限于面向用户的搜索引擎。我们听到的有关此技术的很多成功案例好像都专注于日志,我们很快就发现自己也不例外。
我们设置了一个单独的日志集群来采集来自 Kubernetes Pod 的日志,现在我们能够更清晰地了解服务器的运行状况,并以快得多的速度对问题进行处理,而在之前我们根本不能获得服务器运行状况的相关数据。
我们还能够向平台的用户提供类似体验。通过使用聚合,我们能够让用户以图表形式对他们的数据进行可视化。这本身就是我们平台新增的一项优异功能,而且完全是将数据存储在 Elasticsearch 中之后取得的附带优势。
在过去几个月中,我们对流程进行了调整和改进,现在每周都能看到 Elasticsearch 集群的性能越来越优异。性能提升包括这一点:由于已将需要使用 fielddata 的字段从主集群转移至访问频率较低的小型集群,我们在内存使用率方面实现了大幅改进(现在的基线内存压力为 25%,而之前则高达 75%)。我们同时还对代码进行了调优以便批量写入新数据,而不是按需写入数据,这大大提高了集群在峰值期间的响应性能。
展望未来,我们已制定计划来充分利用 Elasticsearch 对我们公司存储的内部数据进行分析。作为面向客户的产品中的核心组件,Elasticsearch 已经证实了自身的优异性,所以我们已经开始试验将我们自己的数据也存储到 Elasticsearch 索引中,并想使用 Elastic Stack 来获得在公司运营效率方面的洞见。
David Maidment 是 Voxpopme 的高级软件工程师,专注于在公司高速增长期间确保平台代码库能够满足未来需求。
Andy Barraclough 是 Voxpopme 的联合创始人暨 CTO,主要负责管理技术团队并协调制定公司的长期愿景。