Neil Zhu,简书ID Not_GOD,University AI 创始人 & Chief Scientist,致力于推进世界人工智能化进程。制定并实施 UAI 中长期增长战略和目标,带领团队快速成长为人工智能领域最专业的力量。
作为行业领导者,他和UAI一起在2014年创建了TASA(中国最早的人工智能社团), DL Center(深度学习知识中心全球价值网络),AI growth(行业智库培训)等,为中国的人工智能人才建设输送了大量的血液和养分。此外,他还参与或者举办过各类国际性的人工智能峰会和活动,产生了巨大的影响力,书写了60万字的人工智能精品技术内容,生产翻译了全球第一本深度学习入门书《神经网络与深度学习》,生产的内容被大量的专业垂直公众号和媒体转载与连载。曾经受邀为国内顶尖大学制定人工智能学习规划和教授人工智能前沿课程,均受学生和老师好评。
Scaling elasticsearch Part 2: Indexing
原地址
在本节我们介绍:
- 我们的数据被划分为索引以按照时间扩展
- 优化bulk索引
- 扩展实时索引
- 我们管理索引失败和挂机
数据划分
因为Wordpress数据不断地增长,所以我们需要一个索引结构可以伴随时间演化。但是ES的一个限制是一旦索引建立,不能再更改shard的数量。那么解决这个问题的方式是知晓在一个拥有10个shard的索引上搜索其实和在10个只有1个shard的索引上搜索是一样的,而索引是可以不断地添加的。
我们的场景,对每1千万博客创建一个索引,每个索引有25个shard。我们使用index template
来创建这样可以动态地添加一个不存在的索引。
影响索引和shard大小的因素有下面一些:
- 均匀的shard大小:shard的大小应当差不多,这样可以获得均匀的反应时间。更大的shard需要更长的时间去查询。我们尝试对1百万博客用一个索引,发现了太多的差异性(variation)。例如,当我们迁移Microsoft的LiveSpace到Wordpress.com我们获得了大约1百万的博客加入了我们DB中的一行。它们保持了相当的活跃度。这种差异性让我们将很多博客都放进了每个索引。我们相当依赖hash算法来分布这些博客在索引的众多shard中。
- 限制每个索引的shard数:当一个新索引创建时,新的shard不会持续创建。技术上讲,集群的状态在一小时间内变成红色。我们测试每个索引有200个shard。在这些情形下,我们有时看到一些文档的索引失败,因为主shard仍在分配到集群上。
- shard大小的上界:早期,我们试着在每个索引上只有5个shard。刚开始测试情况良好,但是我们发现拥有大量shard的索引(老的博客)会出现很长的查询延迟。这就迫使我们去尝试shard大小的上界。shard大小的极限通常和你数据的类型相关,并很难去估计。
- 最小化shard的总数:在下个部分我们来探讨这个。但是当shard数据增大时,搜索的性能将会下降,所以降低shard的树木可以使得全局查询更快速。
就像所有的工程问题那样,这里并不存在一个简单或者易见的答案,通常使用猜测,测试,最终找到合适的方式。我们的最大shard的大小是30GB。我们接着创建了相当大的shard,但是我们不认为很长时间后会达到那个最大值。
在生产后几个月,也是我写作这篇文章时候,我们想知道我们的shard是否过大。我们并没有考虑到删除的文档会对shard的大小产生负面影响,每次我们重新索引或者更新一篇文档时,我们高效地删除了旧的版本。我们仍在深入了解这个过程,所以这里无法介绍更多了。你shard中删除的文档数目与你真实索引的时间和merge policy setting相关。
Bulk indexing practicalities
批量索引速度是开发过程中迭代的主要的限制,索引在后来讲从此成为新的特性的限制因素。更快的批量索引意味着迭代时间的缩短,测试更多的不同shard/index配置,更多的查询扩展的测试。
那么我们就花了很多精力在提升批量速度上。我们让批量索引从两个月降至一周。提升批量索引的速度是非常迭代和应用相关的。有一些明显的点需要注意,例如使用bulk
索引API以及在每个bulk API请求上测试不同数目的docs。另外也有一些东西让我大吃一惊:
- 架构负载:ES索引在WordPress.com架构的某些部分产生了重度负载,因为它从太多的地方拉取数据了。最终,我们索引的瓶颈不是ES本身,而是我们架构的其他部分。我假设我们可以在这个问题上提出更多的架构(infrastucture),但是这个是你调用批量索引的频率和那个架构需要的花费的代价之间的平衡。
- 极端情形:例如en.blog拥有数百万大的followers、likers,大量的评论。对这些建立一个列表(并且使用实时索引保持更新)肯定是会花费大量资源的( like, “oh $%#@ are we really trying to index a list of 2.3 million ids” costly (and then update it every few seconds)。
- 可选择的批量索引:增加字段到索引上,需要更新mapping并且批量重新索引所有我们的数据。如果有一种可选择的索引方式(例如找到某个类型的blog)可以加速批量索引的速度。
- 集群重新启动:在批量索引后,我们需要对集群进行一个整体的重新启动。
多么希望在项目的前期就进行批量索引的优化方式啊!
扩展实时索引
在正常的操作中,我们索引的频率(20m+ document changes a day)从来不会成为ES集群的问题。我们实时索引问题大多数来自合并太多信息到每个文档,这样子去搜集数据就会成为数据库表的高负载的缘由。
建立一个正确的触发器以不会过度索引文档的方式来检测一个用户已经改变了一个字段通常不是一件轻松的事情。因为发布的内容每秒都可能会有评论或者喜欢数的增加。在这些情形下,重新建立整个文档肯定浪费时间。我们在多数情形下都大量使用了Update API
来降低重新建立ES文档大的负载。
实时索引成为问题的其他情形当集群发生故障时出现。例如:
- 当shard在一些节点上重新分布或者初始化时,网络可能变得不稳定,会备份索引job,可能会导致大批量的job挂掉。
- 如果一个低性能的查询加入了生产环境,那么查询负载会变得很高
- 人为导致了错误并断掉了一些服务器
- 我们也会偶尔发现ES在生产中的bug。尤其是那些围绕在
deleteByQuery
的问题,可能是因为我们运行了大量这类操作 - 路由或者服务器挂掉
实时索引将我们架构的其他部分和索引联系起来。如果索引变得很慢我们会在DB或者我们运行ES索引的job系统上制造出过重的负载。
扩展实时索引分为两个部分:
- 我们如何管理ES的宕机和性能问题,并将这个问题与其他系统独立开来
- 当索引失败时,我们如何恢复和避免批量索引所有的数据集
管理宕机
之前我们提到,在我们收到特定的错误时让ES停机。如果一个节点down了,那么系统处理索引操作的能力就有所下降。更多节点挂了,能处理的索引数量就降低了。
当我们检测到一个服务器已经挂了几分钟时,我们实现了几个启发式方法来降低索引复负载。一旦被触发,我们就把某些索引job加入工作队列,
任何节点,挂掉的时间越长,只能有更少的job类型被允许。一旦出现问题,我们就会关闭对整个博客的批量索引。如果问题持续了5分钟,我们开始关闭整个博客的reindex
,最终也会关闭文档或者删除的任何更新。
在实现索引延迟机制时,我们遇到了实时索引吞没了系统性能的情形。由于我们也没有弄出该思想的实现,我们在保持我们全局查询负载的基础上,我们实际上可以平滑地失败在保持我们全查询的负载。
管理索引失败
- 最终ES会有相同的数据
- 最小化需要批量索引每个东西
- 使用正常的操作,索引就是在一分钟内得到更新
下面可能会导致失败的不同索引方式:
- 独立的索引job会崩溃
- 独立索引在尝试索引到ES时会返回错误
- 通过将其加入一个队列的启发式延迟索引
- 引入一个bug进入索引影响了一小部分的发布内容
- 关闭了实时索引
我们有一些解决这些不同问题的方法:
- 问题1,3和5:前面提到的索引队列。这个让我们在任何失败后都能够重新捡起并且避免了批量重新索引的发生。
- 问题2:当索引job失败,使用一个指数恢复机制来重试这个操作(5m, 10 min, 20 min...)
- 问题4:我们对索引使用滚动查询来找到可能会被一个bug影响的博客集合,只对这些博客进行批量索引。
统统关于失败和迭代
回顾经过的这些,我们希望大家认识到,索引就是处理错误条件。这是我最大的忠告。让系统可以处理失败并获得快速的inplace批量索引非常重要。
下面部分,我会谈谈查询性能和平衡全局及局部查询。