【橘子ES】Lucene 中的索引排序

简介

在 es 6.0 中,引入了一项名为索引排序的新功能。用户现在可以优化 es 索引,以按特定顺序将文档存储在磁盘上。它是优化 es 性能的另一个有用工具。

通过本文,我们将深入探讨多个领域:

  • Lucene 的索引排序功能
  • 索引排序将提高查询性能的示例
  • 对时间序列数据使用索引排序时要考虑的注意事项
  • 性能注意事项

Lucene 中的索引排序

多年前,lucene 引入了一种称为 IndexSorter 的离线工具。IndexSorter 将源索引复制到新的目标索引,并根据用户指定的顺序对磁盘上的文档进行排序,注意,这里是磁盘排序,而且是用户指定的方式对磁盘排序。当时,由于无法直接更新目标索引,因此每次将新文档添加到源索引时,此功能的用户都必须重新构建排序视图(重新构建一遍)。IndexSorter 是第一次尝试提供一种对磁盘上的文档进行排序的方法,不是在搜索时,而是在索引时

通过索引排序,引入了一个称为 early termination称之为提前终止查询 的新概念。例如,假设你要检索按日期排序的 N 个文档(日期是索引中的一个字段)。如果索引在磁盘上按此日期字段排序,则可以在访问与查询匹配的前 N 个文档后“停止”请求(因为它们已经按照用户指定的顺序,可以借助mysql的索引来理解)。这就是我们所说的“提前终止”。提前终止查询可以显著缩短搜索响应时间,尤其是对于基于排序的查询,并导致 IndexSorter 工具在 Lucene 用户中越来越受欢迎。该工具的静态特性阻止了它用于具有大量更新的索引,这就是为什么它最终被允许增量更新的解决方案所取代。提出了一种新的解决方案,在合并时对文档进行排序,而不是对静态索引进行一次性排序。换言之他可以支持你在写入文档的时候进行排序,而不是写入一次就完了,下次再写入得重新全排序。

Lucene 改进

在没有索引排序之前,最初的时候,Lucene 按接收文档的顺序对文档进行索引,并为每个文档分配一个增量(和内部)文档 ID(按区段分配)。在区段中索引的第一个文档的文档 ID 为 0,依此类推。在搜索时,将按文档 ID 顺序访问每个区段,以检索与用户查询匹配的文档。为了检索查询的最佳 N 个文档,Lucene 需要访问所有区段中与查询匹配的每个文档。如果查询与数百万个文档匹配,则仅检索最佳 N 仍需要访问数百万个文档。就是说最初排序是按照文档写入的顺序排序的,无法按照指定字段排序。这种按照顺序排序使得当你要按照某个字段有序返回的时候,你要n个文档,就需要索引全部的内容才能知道完整的顺序。

每当触发刷新(flush)时,Lucene 索引都会创建一个新分段。此新区段包含在上次刷新后添加的所有文档。当区段被刷新时,搜索者可以看到它,并且新文档可以显示在搜索结果中。由于刷新不断发生,因此索引中的分段数量很容易激增。区段合并在后台进行,以限制区段数增长过大。合并是根据选择符合合并条件的区段的策略触发的,然后,所选区段将合并到替换旧区段的新区段中。默认情况下,区段合并过程会根据文档的内部文档 ID 将不同区段中的文档复制到新区段。为了替换静态工具(上面提到的 IndexSorter), 引入了一个新的合并策略,以允许对动态索引进行索引排序,该索引在合并过程中根据可配置的顺序(例如字段的值)对文档进行重新排序。这种新设计是朝着正确方向迈出的一大步,它允许对索引进行动态排序,并按段使用这些信息。某些区段已排序(通过合并创建的区段),而有些区段未排序(新刷新的区段)。在合并时,未排序的区段首先被“排序”,然后与其他已排序的区段合并。

然后,这个位于模块中的合并策略被移动到 IndexWriterConfig 上的顶级选项中,以使索引排序成为 Lucene 中的一等公民
尽管一些基准测试表明,合并时排序的成本可以将索引的总吞吐量除以 2 倍:
在这里插入图片描述
这也无可厚非,在索引时的开销减轻了在搜索时的开销。具体的测试报告可以查看测试报告
索引性能降低的原因很简单:重新排序 Segments 会产生成本,导致这些索引的合并时间和内存消耗大幅增加。

由于一次对多个 segment 重新排序的成本很高,因此决定在索引过程的早期对文档进行排序。我们不再等待合并时间对多个区段进行排序,而是将排序移至刷新时间(首次创建区段时):LUCENE-7579。 如果所有区段都已排序,则可以使用简单的合并排序策略进行合并,该策略要快得多。这种新策略首次在 Lucene 6.5 中引入,并将吞吐量基准提高了近 65%。

正如你在这个故事中看到的,索引排序在 Lucene 中有很多历史,但直到现在,从es 6.0开始便解锁了此功能。

索引排序的实际应用

提前终止搜索查询

在应用程序中,查询前 X 个结果(按值 Y 排序)是很常见的(顶级玩家分数、新用户、最新事件等)。在大多数情况下,在检查整个数据集之前,Elasticsearch 没有足够的信息来快速收集前 X 个结果并对其进行排序。文档值使此过程更加高效,但是,在数据集非常大的情况下,将检查和比较比用户需要的值多得多的值。

随着 es 6.0 中索引排序的引入,我们现在可以指定磁盘上文档的顺序,从而允许 es 更有效地短路并返回查询。例如,如果我们要为一家视频游戏公司创建一个排行榜来跟踪前 3 名玩家分数(而且我们有非常多的玩家),我们可以指示 es 按照他们的玩家分数顺序存储文档,从而使我们能够更高效地计算排行榜。

在这里插入图片描述

// 获取分数前三的玩家,分数基于points字段
GET scores/score/_search
{
  "size": 3,
  "sort": [
      { "points": "desc" }
  ]
}

根据 es 的版本和索引排序的使用情况,我们可以非常有效地将文档存储在磁盘上,以便进行上述查询:

在这里插入图片描述
我们看到在有了索引排序之后,因为文档按照字段在磁盘存放,我们只需要获取对应的top n即可,因为他是严格有序的。不过上面的查询仍然需要返回结果数量的计数(并且需要一些额外的工作)。我们可以通过将新选项 “track_total_hits” 设置为 false 来删除此要求:

// 获取分数前三的玩家,分数基于points字段
GET scores/score/_search
{
  "size": 3,
  "track_total_hits" : false,
  "sort": [
      { "points": "desc" }
  ]
}

现在,我们使用排序索引对顶级玩家分数进行了非常高效的排行榜查询。

在 Elasticsearch 6.0 中指定索引排序顺序

要继续上面的示例(创建顶级玩家分数的排行榜),我们需要告诉 es 如何对磁盘上的文档进行排序。我们可以通过在索引的设置中提供定义来做到这一点:我们在索引的settings中配置了排序字段为分数points,并且排序的方式是倒序排列。此时当我们往这个索引写入文档的时候,文档在磁盘中的排序就是按照points字段倒序排列的,这对于上面的简单查询(前 3 名玩家得分)很有帮助。因为我们知道严格有序了,所以他只需要在磁盘上扫描三条数据即可。放佛把mysql的索引引入了es中。

PUT scores
{
    "settings" : {
        "index" : {
            "sort.field" : "points", 
            "sort.order" : "desc" 
        }
    },
    "mappings": {
        "score": {
            "properties": {
                "points": {
                  "type": "long"
                },
                "playerid": {
                  "type": "keyword"
                },
                "game" : {
                  "type" : "keyword"
                }
            }
        }
    }
}

按类似结构对索引中的文档进行分组

存储按相似类型排序的文档有很多优点。例如,如果有一个名为 “scores” 的索引,则某些分数可能来自游戏 “Joust”,并包含特定字段,例如 “top-speed” 和 “farthest-jump”,其他游戏的分数,例如 “Dragon’s Lair” 可能包含 “sword-fight-score” 和 “goblins-killed” 字段:

// Score for the game "Joust"
{
  "game" : "joust",
  "playerid" : "1234",
  "top-speed" : 212,
  "farthest-jump" : 49
}

// Score for the game "Dragon’s Lair"
{
  "game" : "dragons-lair",
  "playerid" : "5678",
  "sword-fight-score" : 89,
  "goblins-killed" : 3
}

将按游戏排序的文档存储在磁盘上将有助于将相似的文档(具有相似的字段名称)放在一起。这样做的优点是查询速度(尽管重要的是要记住这实际上取决于查询)和压缩。将相似的字段存储得更紧密可能会带来更好的压缩效果,并且 es(反过来是 Lucene)能够更有效地存储增量数据,因为把相似类型的字段排序放在一起总是方便压缩存储的:

PUT scores
{
    "settings" : {
        "index" : {
            "sort.field" : "game", 
            "sort.order" : "desc" 
        }
    }
}

更高效的 AND 连词

使用索引排序以特定顺序在磁盘上查找文档还可以改进 AND 连词 ,即具有许多条件的复杂查询。

让我们继续我们的视频游戏示例,当玩家加入游戏时,他们必须与同一地区、技能水平和课程中的其他玩家配对。用于查找类似玩家以开始新比赛的示例查询可能类似于以下内容(在“EU”区域内获取 10 名玩家,玩“Dragon’s Lair”,技能等级为 9,在“Castle”地图上):

GET players/player/_search
{
  "size": 3,
  "track_total_hits" : false,
  "query" : { 
    "bool" : {
      "filter" : [
        { "term" : { "region" : "eu" } },
        { "term" : { "game" : "dragons-lair" } },
        { "term" : { "skill-rating" : 9 } },
        { "term" : { "map" : "castle" } } 
      ]
    }
  }
}

我们来看下es如何收集检索结果:
下图为没有索引排序的情况下:
在这里插入图片描述
然后我们使用索引排序再来看下,我们这里指定多个排序。

PUT players
{
    "settings" : {
        "index" : {
            "sort.field" : ["region", "game", "skill-rating", "map"], 
            "sort.order" : ["asc", "asc", "asc", "asc"] 
        }
    },
    "mappings": {
        "player": {
            "properties": {
                "playerid": {
                  "type": "keyword"
                },
                "region": {
                  "type": "keyword"
                },
                "skill-rating" : {
                  "type" : "integer"
                },
                "game" : {
                  "type" : "keyword"
                },
                "map" : {
                  "type" : "keyword"
                }
            }
        }
    }
}

现在我们可以看到文档放置得更近:

在这里插入图片描述
通过使用排序索引,我们可以将具有相似字段值的文档更紧密地定位在一起,从而使我们查找给定对战游戏的玩家的查询更加高效。

当索引排序不适合时

与存储未排序的值相比,在索引时将排序的值存储在磁盘上需要 es 进行更多的工作。在某些情况下,索引排序的性能开销可能会使写入性能降低多达 40-50%。因此,确定是否应该针对查询性能或写入性能优化应用程序非常重要。优化应用程序的写入性能(并影响查询性能)很可能意味着索引排序不是一个好的选择。
这个自然,当你需要在写入时排序的时候,自然需要在磁盘上找到合适的位置写入,这个开销不可谓不大,尤其是写入比较高频的时候,这时候我们一般需要一个mq作为缓冲。
你可以检查索引的吞吐量(带索引排序和不带索引排序)。如上所述,性能影响会有很大差异,具体取决于您的使用案例。例如,geonames Elasticsearch 基准测试显示索引排序的性能影响非常小(标记为“Append Sorted”的蓝线):详情可以查看测试报告
在这里插入图片描述

或者,“NYC Taxis” 基准测试显示,使用索引排序时,索引性能会大幅下降:测试报告
在这里插入图片描述
在系统设计中,几乎每个级别都有权衡,对于索引排序,我们考虑的权衡是更快的查询(在特定情况下)的写入效率较低(因为必须对文档进行排序)与更高效的写入和较慢的查询(因为结果必须在查询时排序)。与任何新功能类似,使用特定用例和数据集测试索引排序非常重要。

详情操作可以查看es的索引排序的官方文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值