2018年10月23日 工程

节省空间:关于 Elasticsearch 中的索引排序,很多人不知道的一项优势

作者 Shane Connelly

在 Elasticsearch 6.0 中,我们推出了一项新功能:索引排序。如需了解有关这一功能的详细信息,请查看所链接的博文,但简而言之,这一功能可在索引时通过键或键集按照您所选的顺序对文档进行排序。我们已经讲过,这一功能有数项优势:

  • 如果您要求 Elasticsearch 在返回结果时按照您索引时所用的同一个键对结果进行排序,Elasticsearch 则无需在执行查询时进行这一操作。因为这些结果已经预先排过序啦!
  • 如果您不需要查看符合搜索条件的全部条目,并且已按照排序键对数据进行排序,则实际上 Elasticsearch 在找到符合您的请求的足够条目后便会停止查询。这可以无限改善查询性能。
  • 如果您的查询需要对多个字段使用 AND,那么通过对这些字段进行索引排序,Elasticsearch 最后汇总结果时便可以跳过大块不匹配的文档,这也会加快搜索速度。

简而言之,对某些用例而言,索引排序可以加快搜索速度;如果在您的用例中,人们通过几种常见方式对文档进行搜索和排序,则这一优势会更加明显。 然而此功能还有一项我们较少提及的优势,即还可以减少索引所占用的磁盘空间。我在这篇文章中将会介绍原因以及实现过程。

注意:索引排序并不一定适合所有人

在详细讲解之前,我必须再次告诉大家,索引排序并不一定适合所有人。这一功能会导致在索引时进行排序操作。由于排序是一个很耗时的操作,所以如果您最关注的是索引速度,则必须谨慎考虑是否要开启索引排序功能。此功能最多可将写入速度降低 40-50%。所以,如果您的主要关注点是索引处理量(高数据量的日志、指标、安全分析用例通常便属于这种情况),很可能索引排序并不适用于您。 如果您对索引速度的要求较低,或者如果您在自己的用例中最关注的是查询速度,再或者如果您已确定会在非索引高峰期完成定期再索引进程(无论采用何种形式),则索引排序功能可能会起到帮助作用。

仔细研究可能的排序顺序:示例

假定我正在运行一个 Elasticsearch 实例来搜索产品,而且我拥有一系列文档,这些文档在索引时格式如下(为方便查看,我将文档转换为了矩阵格式):

产品编号 产品类别 产品颜色 价格
206f467b-8cfe $97.00
4f89fbec-acc3 夹克 $120.50
47771396-dfe3 夹克 $170.10
c6c8fbdf-651b 帽子 $15.00
dc18c426-0eb3 $107.20
ee304259-df57 夹克 $88.00
9332c0ac-e55e $49.00
30e96765-52a1 帽子 $11.00
811cc8ca-d6bb 夹克 $92.99

现在假设我们想启用索引排序功能。需要根据什么进行排序呢?我们拥有数项选择:产品类别、产品颜色和/或价格都可能对我们有用。如果用户搜索时基本只会按价格排序,而且我们不提供按类别或颜色筛选功能,则最合理的方法就是使用价格作为排序键进行排序。然而,用户很有可能会至少先选定类别,然后再查找价格最便宜的商品,而且用户可能还会偏好某一种具体颜色。 我们按照下面的顺序进行排序:首先按类别升序排列,然后按颜色升序排列,最后按价格升序排列。

"sort.field" : ["product_category", "product_color", "price"], "sort.order" : ["asc", "asc", "asc"]

排序后的索引如下所示:

产品编号 产品类别 产品颜色 价格
30e96765-52a1 帽子 $11.00
c6c8fbdf-651b 帽子 $15.00
ee304259-df57 夹克 $88.00
4f89fbec-acc3 夹克 $120.50
811cc8ca-d6bb 夹克 $92.99
47771396-dfe3 夹克 $170.10
9332c0ac-e55e $49.00
206f467b-8cfe $97.00
dc18c426-0eb3 $107.20

接下来便会发生一些有意思的事情,我会举例说明:

  • 如果我不需要知道符合条件的所有鞋的数量,只想让 Elasticsearch 返回最便宜的两款鞋并按价格对结果排序,那么 Elasticsearch 可以很高效地完成这一任务,因为它会跳过其他所有类别,只需在鞋的区块中进行查找即可。而且,一旦找到恰好两条结果,Elasticsearch 就可以停止处理剩余索引,直接返回结果。请注意,如要实现这一效果,您必须在索引中包括索引顺序的每个元素,即使您已经应用了相匹配的筛选条件,也须如此。
  • 如果我要求 Elasticsearch 返回 product_category:Jackets AND product_color:Black 的结果,Elasticsearch 可以跳过所有 Hat(帽子)和 Shoe(鞋),然后在 Jacket(夹克)类别中查找 Black(黑色),查找完之后,Elasticsearch 便可以十分快速地跳过所有其他颜色。
  • Elasticsearch 会在幕后完成大量压缩工作。当有重复值时,压缩便可发挥作用;如果索引中的重复值彼此相邻,压缩可以最高效地发挥作用。由于所有的 Jacket(夹克)或 Color(颜色)彼此相邻,Elasticsearch 可以高效地在磁盘上对这些数据进行压缩。这不仅意味着可以节省磁盘空间,而且还意味着操作系统能够在 filesystem(文件系统)缓存中存储更多内容,进而加快搜索速度。

一般而言,最佳做法是按照基数渐升的顺序进行排序,这样我们便可以在一行中包含尽可能多的重复值,从而获得优势。

我可以节省多少磁盘空间?

那么,通过启用索引排序,您可以节省多少磁盘空间呢? 和很多其他事情一样,答案是“不一定,取决于很多因素”。 最重要的一个依赖因素是您排序所用字段的基数。然而我必须要说,节省的磁盘量可能会十分可观。 在我的某个个人项目中,我会用到一些 IoT(物联网)/家庭自动化数据。上周,我决定将这些数据从旧机器迁移到新机器上。虽然可以通过一些更快的方法(例如快照/恢复)进行数据迁移,但是我时间比较充裕,可以进行再索引,而且我也想看看通过索引排序可以节省多少空间。首先我通过远程再索引,将数据变为未排序索引:

status    index           pri    docs.count    docs.deleted    pri.store.size 
open      devices-2017    1      33310674      0               4.2gb

共有 30 多台设备,每台设备大约每隔 30 秒便会发回一次状态,所以,总索引速度是大约每秒钟处理 1 份文档。我的数据量很小,远达不到会影响索引速度的限值,所以无需担心索引过程;我必须极大幅度提高索引速度或者大量增加设备数量,才有可能需要担心索引过程。所以,这么来看,这个用例是索引排序的一个很好选择。 数据包括硬件 ID、硬件名称、时间以及各种各样的传感器读数,例如温度,设备每个时刻处于开启还是关闭状态,以及其他传感器数据。我首先按照设备 ID,然后按照时间对索引进行了排序,我的想法是这样的:对于给定设备,其在同一个时刻左右拥有相似或完全相同数值的几率会高一些,所以这会改善压缩效果。例如,如果某个开关在 7:00:00 变为“打开”状态,则其在 7:00:30、7:01:00 以及至少几分钟后的这段时间内,很有可能仍会处于“打开”状态,所以按照设备 ID 进行排序可以实现很好的压缩效果。 排序后的索引统计数据显示……

status    index           pri    docs.count     docs.deleted    pri.store.size
open      devices-2017    1      3310674        0               2.5gb

节省了大约 40% 的磁盘空间!

再次提醒

看到针对同样的数据可以节省 40% 的磁盘空间,我相信每个人都会倍感欣喜,但是我此刻觉得十分有必要再次给大家提个醒。我会以简短的两句话来进行总结:

  • 可节省的空间量因具体情况而异。我针对另一个数据集启用索引排序后,发现空间节省比例为 20%。在确定排序字段时,一定要三思。
  • 索引速度可能会变慢。如果索引速度对您真的十分重要,例如您运行的是一个拥有很大数据量的日志或指标用例,您很可能会关注在短时间内索引的文档数量,所以这时启用索引排序功能就不是明智之选。

如果相对于索引速度,您更加关心磁盘空间,或者如果您的索引数据量很低,根本无需担心受限于索引速度,则您可以考虑是否可以尝试索引排序。