ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 倒排和正排索引 ## 1\. 正排(doc value)与倒排 > 搜索的时候,要依靠倒排索引;排序的时候,需要依靠正排索引,看到每个document的每个field,然后进行排序,所谓的正排索引,其实就是doc values > 在建立索引的时候,一方面会建立倒排索引,以供搜索用;一方面会建立正排索引,也就是doc values,以供排序,聚合,过滤等操作使用 > 正排索引: > > 1. 如果你的某个field设置为不分词,那么在建立索引的时候(index-time),就会自动生成doc value > 2. doc value(正排索引): > 是被保存在磁盘上的,此时如果内存足够,os会自动将其缓存在内存中,性能还是会很高;如果内存不足够,os会将其写入磁盘上 ~~~ doc1: hello world you and me doc2: hi, world, how are you ~~~ > 倒排索引:数据-文档的二维矩阵 ~~~ word doc1 doc2 hello * world * * you * * and * me * hi * how * are * ~~~ > 例如搜索hello you,搜索语句会按照index中对应的field的分词器进行分词 > hello you --> hello, you ~~~ hello --> doc1 you --> doc1,doc2 ~~~ ~~~ doc1: hello world you and me doc2: hi, world, how are you ~~~ > 正排索引 ~~~ doc1: { "name": "jack", "age": 27 } doc2: { "name": "tom", "age": 30 } ~~~ > 文档-数据的二维矩阵 ~~~ document name age doc1 jack 27 doc2 tom 30 ~~~ > 例如通过age排序,方便排序 ## 2\. 对于分词的field进行聚合 * 正常情况下,是不可以对分词的field进行聚合的 ~~~ PUT myindex/test/1 { "name":"dailin" } ~~~ 聚合测试 ~~~ GET myindex/_search { "size": 0, "aggs": { "groupby_name": { "terms": { "field": "name" } } } } ~~~ 报错 ~~~ "reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [name] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead." ~~~ Fielddata默认是关闭的,必须要打开fielddata,然后将正排索引数据加载到内存中,才可以对分词的field执行聚合操作,而且会消耗很大的内存。 如果对分词的field进行聚合,有以下几种方法 1. 对应field的Fielddata设置成true 首先设置mapping ~~~ POST myindex/_mapping/test { "properties": { "name":{ "type": "text", "fielddata": true } } } ~~~ 再次聚合 ~~~ GET myindex/_search { "size": 0, "aggs": { "groupby_name": { "terms": { "field": "name" } } } } ~~~ 成功 ~~~ "aggregations": { "groupby_name": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "dailin", "doc_count": 1 } ] } } } ~~~ 2. 使用分词field的keyword子field 5.X版本以后,es默认会为每个textfield生成一个keyword(不分词),我们可以利用这个field聚合 * 查看mapping `GET myindex/_mapping` ~~~ "name": { "type": "text", "fields": { "keyword": { "type": "keyword", # 生成一个不分词的子field "ignore_above": 256 } } ~~~ * 聚合测试 ~~~ GET myindex/_search { "size": 0, "aggs": { "groupby_name": { "terms": { "field": "name.keyword" } } } } ~~~ 同样得到一样的结果 > 1. 如果一定要对分词的field执行聚合,那么必须将fielddata=true,然后es就会在执行聚合操作的时候,现场将field对应的数据,建立一份fielddata正排索引,fielddata正排索引的结构跟doc value是类似的,但是只会讲fielddata正排索引加载到内存中来,然后基于内存中的fielddata正排索引执行分词field的聚合操作 > 2. fielddata加载到内存的过程是lazy加载的,对一个analzyed field执行聚合时,才会加载,而且是field-level加载的一个index的一个field,所有doc都会被加载,而不是少数doc不是index-time创建,是query-time创建 > 3. 监控fielddata内存使用 ~~~ # 各个分片的fiedata内存占用情况 GET /_stats/fielddata?fields=* # 各个node的fielddata的内存占用情况 GET /_nodes/stats/indices/fielddata?fields=* GET /_nodes/stats/indices/fielddata?level=indices&fields=* ~~~ > 4. circuit breaker(fielddata内存使用控制) ~~~ 如果一次query load的feilddata超过总内存,就会oom --> 内存溢出 circuit breaker会估算query要加载的fielddata大小,如果超出总内存,就短路,query直接失败 indices.breaker.fielddata.limit:fielddata的内存限制,默认60% indices.breaker.request.limit:执行聚合的内存限制,默认40% indices.breaker.total.limit:综合上面两个,限制在70%以内 ~~~ ### fielddata预加载 如果真的要对分词的field执行聚合,那么每次都在query-time现场生产fielddata并加载到内存中来,速度可能会比较慢 我们是不是可以预先生成加载fielddata到内存中来??? 1. fielddata预加载 ~~~ POST /test_index/_mapping/test_type { "properties": { "test_field": { "type": "string", "fielddata": { "loading" : "eager" } } } } ~~~ query-time的fielddata生成和加载到内存,变为index-time,建立倒排索引的时候,会同步生成fielddata并且加载到内存中来,这样的话,对分词field的聚合性能当然会大幅度增强 2. 序号标记预加载 global ordinal原理解释 ~~~ doc1: status1 doc2: status2 doc3: status2 doc4: status1 ~~~ 有很多重复值的情况,会进行global ordinal标记 ~~~ status1 --> 0 status2 --> 1 doc1: 0 doc2: 1 doc3: 1 doc4: 0 ~~~ 建立的fielddata也会是这个样子的,这样的好处就是减少重复字符串的出现的次数,减少内存的消耗 ~~~ POST /test_index/_mapping/test_type { "properties": { "test_field": { "type": "string", "fielddata": { "loading" : "eager_global_ordinals" } } } } ~~~