Elasticsearch 是分布式搜索和分析引擎,是满足搜索和聚合需求的最受欢迎的选择。
【资料图】
Elasticsearch 提供了 2 种数据类型来存储字符串值:
Text:- 在存储到倒排索引之前对这些内容进行分析,并针对全文搜索进行优化。 文本字段不允许聚合Keyword:- 它们按原样存储在倒排索引中,如果需要,可以在查询期间进行分析。 这些针对聚合进行了优化,因为它们也以柱状方式存储(称为 doc values),以便可以引用单个字段,而无需在内存中加载完整文档有关 text 及 keyword 搜索的更多比较,请参阅我之前的文章 “Elasticsearch:Text vs. Keyword - 它们之间的差异以及它们的行为方式”。
Elasticsearch 将 keyword 存储为 doc values 中的序数,以获得更紧凑的表示。 这种映射的工作原理是根据每个术语的字典顺序为每个术语分配一个增量整数或“序数(ordinal)”。 该字段的 doc values 仅存储每个文档的序数而不是原始术语,并具有单独的查找结构来在序数和术语之间进行转换。
在聚合期间使用时,序数可以极大地提高性能。 作为术语(terms)聚合的示例,依赖于序数将文档分组到分片级别的存储桶中,然后在跨分片组合结果时将序数转换回其原始术语值。
每个分片包含多个 “段(segements)”,其中一个段就是一个倒排索引。 分片中的搜索将依次搜索每个片段,然后将其结果合并到该分片的最终结果中。 当你为文档建立索引时,Elasticsearch 将它们收集在内存中(为了安全起见,收集在 trasaction log 中),然后每隔一秒左右将一个新的小段写入磁盘,并 “刷新” 搜索。这使得新段中的数据对搜索可见(即它们是 “可搜索的”)
每个段都定义自己的序数映射,但聚合会跨整个分片收集数据。 因此,为了能够使用序数(ordinals)进行聚合等分片级操作,Elasticsearch 创建了一个称为全局序数(global ordinals)的统一映射。 全局序数映射建立在段序数之上,并通过维护每个段的从全局序数到局部序数的映射来工作。
必须先构建全局序数映射,然后才能在搜索过程中使用序数。 默认情况下,第一次需要全局序数时会在搜索过程中加载映射。 通常,全局序数在加载时间和内存使用方面不会产生很大的开销。 但是,对于具有大分片的索引,或者如果字段包含大量唯一术语值,则加载全局序数可能会很昂贵。 由于全局序号为分片上的所有段提供了统一的映射,因此当新段可见时,它们也需要完全重建。
解决方案
聚合提供了一个参数 execution_hint,有助于控制存储桶的收集方式。 它默认为 global_ordinals,但可以设置为 map 来直接使用术语值。 这避免了创建全局序数的麻烦并提高了性能,但仅当很少有文档与查询匹配时才应考虑,否则基于序数的执行模式会明显更快。 默认情况下,仅在对脚本运行聚合时使用 map,因为它们没有序数。它可以指定如下:
"aggs": { "make": { "terms": { "field": "make", "execution_hint": "map", "size": 10 } } }
我们还可以使用 eager_global_ordinals,在这种情况下,全局序数是在刷新分片时构建的。 Elasticsearch 总是在公开索引内容的更改之前加载它们。 这将构建全局序数的成本从搜索时间转移到索引时间。 可以为字段启用它,如下所示:
PUT my-index-000001/_mapping{ "properties": { "tags": { "type": "keyword", "eager_global_ordinals": true } }}
可以通过更新 eager_global_ordinals 设置随时禁用预加载:
PUT my-index-000001/_mapping{ "properties": { "tags": { "type": "keyword", "eager_global_ordinals": false } }}