Elasticsearch 倒排索引 底层原理
正排索引(doc values )VS 倒排索引:
概念 :从广义来说,doc values 本质上是一个序列化的 列式存储 。列式存储 适用于聚合、排序、脚本等操作,所有的数字、地理坐标、日期、IP 和不分析( not_analyzed )字符类型都会默认开启。
特点 :倒排索引的优势 在于查找包含某个项的文档,相反,如果用它确定哪些项是否存在单个文档里。
总结:全文搜索需要用倒排索引,而排序和聚合则需要使用 正排索引。
在Mappings中有两个相关配置
1 2 3 4 5 doc_values:true/false 为该字段创建正排索引,默认true,不支持text类型 index:true/false 为该字段创建倒排索引,默认为true
1 2 3 4 5 6 7 8 9 10 11 12 PUT /product { "mappings" : { "properties" : { "tags" : { "type" : "text" , "index" : "true" } } } }
1 2 3 4 5 6 7 8 9 10 11 GET /product/_search { "aggs" : { "tags_group" : { "terms" : { "field" : "tags.keyword" } } } }
而doc_values
正排索引不支持text
字段,那text
字段怎么进行聚合操作呢?
1 2 3 4 5 6 7 8 9 10 11 GET /product/_search { "aggs" : { "tags_group" : { "terms" : { "field" : "tags" } } } }
1 2 3 4 Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [tags] in order to load field data by uninverting the inverted index. Note that this can use significant memory.
大概的意思是,必须要打开fielddata=true
,然后将正排索引数据加载到内存中,才可以对分词的field执行聚合操作,而且会消耗很大的内存。
1 2 3 4 5 6 7 8 9 10 PUT /product/_mapping { "properties" : { "tags" : { "type" : "text" , "fielddata" : true } } }
这时候再次执行上文的tags的聚合操作,就不会报错了,那么fielddata
和doc_values
都是开启正排索引,他们之间有什么区别呢?
维度
doc_values
fielddata
创建时间
index时创建
使用时动态创建
创建位置
磁盘
内存(jvm heap)
优点
不占用内存空间
不占用磁盘空间
缺点
索引速度稍低
文档很多时,动态创建开销比较大,而且占内存
默认值
true
false
doc_values
速度稍低这个是相对于fielddata方案的,其实仔细想想也可以理解。拿排序举例,相对于一个在磁盘排序,一个在内存排序。谁的速度快自然不用多说。
与 doc values 不同,fielddata 构建和管理 100% 在内存中,常驻于 JVM 内存堆。这意味着它本质上是不可扩展的。
fielddata可能会消耗大量的堆空间,尤其是在加载高基数(high cardinality)text字段时。一旦fielddata已加载到堆中,它将在该段的生命周期内保留。此外,加载fielddata是一个昂贵的过程,可能会导致用户遇到延迟命中。这就是默认情况下禁用fielddata的原因。
doc_values
虽然速度稍慢,但doc_values的优势还是非常明显的。一个很显著的点就是他不会随着文档的增多引起OOM
问题。正如前面说的,doc_values在磁盘创建排序和聚合所需的正排索引。这样我们就避免了在生产环境给ES设置一个很大的HEAP_SIZE
,也使得JVM的GC更加高效,这个又为其它的操作带来了间接的好处。
1 2 3 4 5 6 7 1.当没有doc value的字段需要聚合时,需要打开fielddata,然后临时在内存中建立正排索引,fielddata的构建和管理发生在JVM heap中。 2.Fielddata默认是不启用的,因为text字段比较长,一般只做关键字分词和搜索,很少拿来进行全文匹配和聚合还有排序。 3.ES采用了circuit breaker(熔断)机制避免fielddata一次性超过物理内存大小而导致内存溢出,如果发生熔断,查询会被终止并返回异常。 4.fielddata使用的是jvm内存,doc value在内存不足时会静静的待在磁盘中,而当内存充足时,会蹦到内存里提升性能。
为什么不可以用倒排索引计算聚合?
对于聚合部分,我们需要找到匹配的doc里所有唯一的词项(term)。需要遍历每个doc获取所有trem词项,然后再一个个去倒排索引表中进行查找,是一个 n x m 的操作,做这件事情性能很低,很有可能会造成全表遍历。
因此通过正排索引来解决聚合问题 。
批量查询 1 2 3 语法: GET /_mget GET /<index>/_mget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 GET /_mget { "docs" : [ { "_index" : "product" , "_id" : 2 }, { "_index" : "product" , "_id" : 3 } ] }
1 2 3 4 5 6 7 8 9 10 11 12 GET /product/_mget { "docs" : [ { "_id" : 2 }, { "_id" : 3 } ] }
1 2 3 4 5 GET /product/_mget { "ids" :[2 ,3 ] }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 GET /product/_mget { "docs" : [ { "_id" : 2 , "_source" : false }, { "_id" : 3 , "_source" : [ "name" , "price" ] }, { "_id" : 4 , "_source" : { "include" : [ "name" ], "exclude" :[ "price" ] } } ] }
1 2 3 4 5 Operate: create:PUT /index/_create/id/,强制创建(是否制定id) delete:删除(lazy delete原理) index:可以是创建,也可以是全量替换 update:执行partial update(全量替换,部分替换)
1 2 3 4 5 PUT /test_index/_doc/1/ { "test" :"123" }
1 2 3 4 5 6 7 8 9 10 11 PUT /test_index/_doc/1/_create { "test" :"123" } PUT /test_index/_create/1/ { "test" :"123" }
1 2 3 4 5 POST /test_index/_doc { "test" :"123" }
当使用PUT进行数据覆盖的时候,Version版本号会上升,旧的Version数据会被删除,不会马上删除,会有一个懒删除的机制。
批量操作 1 2 3 4 5 6 bulk:批量增删改 no-query 语法格式: POST /_bulk POST /<index>/_bulk {"action" : {"metadata" }} {"data" }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 POST /_bulk { "delete" : { "_index" : "product2" , "_id" : "1" }} { "create" : { "_index" : "product2" , "_id" : "2" }} { "name" : "_bulk create 2" } { "create" : { "_index" : "product2" , "_id" : "12" }} { "name" : "_bulk create 12" } { "index" : { "_index" : "product2" , "_id" : "3" }} { "name" : "index product2 " } { "index" : { "_index" : "product2" , "_id" : "13" }} { "name" : "index product2" } { "update" : { "_index" : "product2" , "_id" : "4" ,"retry_on_conflict" : "3" } } { "doc" : {"test_field2" : "bulk test1" } }
bulk
批处理操作要求数据分两行编写,不可以将{}
进行换行操作。
1 2 3 4 POST /_bulk?filter_path=items.*.error { "delete" : { "_index" : "product2" , "_id" : "1" }} { "create" : { "_index" : "product2" , "_id" : "2" }}
1 2 3 4 5 6 7 PUT /version_index/_doc/1?version=2&&version_type=external { "title" : "窈窕淑女,君子好逑" }
ES是通过CAS+Version解决并发的问题!!!