Elasticsearch 是默认延迟加载fielddata到内存里的。当elasticsearch第一次遇到一个查询须要一个指定field的fielddata的时候,就会把索引的每一个段中整个field加载到内存。对于小段,这是个能够忽略不计的时间,可是若是你有一些5G大小的段而且须要加载10GB的fielddata到内存里,这个过程须要数十秒,习惯于秒内响应时间的用户会被网突如其来的迟钝所打击。java
有三种方法应对这种延迟尖峰:
Eagerly load fielddata(饿汉式(预)加载fielddata)
Eagerly load global ordinals(饿汉式(预)加载全局序数)
Prepopulate caches with warmers (使用warmer提早加载缓存)。json
全部这些都是一个意思:预先加载fielddata到内存里,这样当用户须要执行一个搜索的时候就感觉不到延迟了。api
饿汉式(预)加载fielddata缓存
首先是预先加载(而不是默认的延迟加载),当一个新的段造成时(无乱是刷新,写入或者是合并),能够预先加载的field会提早把段的fielddata加载到内存里,在这段能够用于搜索以前。数据结构
这意味着当你第一次查询的时候,若是碰到在这个段上,你不须要再触发加载fielddata的操做,它们已经在内存中了,这会防止你的用户遇到一些冷到缓存而发生延迟尖峰。app
预先加载是基于每一个field的,因此你能够控制哪些field进行预加载。electron
PUT /music/_mapping/_song { "price_usd": { "type": "integer", "fielddata": { "loading" : "eager" } } }
备注: 经过设置fielddata.loading: eager,告诉elasticsearch预先加载这个field的内容到内存里。elasticsearch
fielddata的加载能够设置成饿汉模式(预先加载)仍是懒惰模式(延迟加载),使用update-mapping的api。ide
预先加载是简单对fielddata加载的开销的转移,从查询时间转义到刷新时刻。性能
大段的刷新时间会比小段的时间长,一般大段的产生都是由哪些已经可搜素的小段合并而来的,因此慢一点的刷新时间不是那么重要(译者注:意思是大段的刷新时间长不影响你的搜索,在大段合并成前的小段能够用于搜索)。
全局序数
其中一项用于减小string类型的fielddata占用内存的技术叫作序数。
假设咱们有十亿条文档,每一个文档都有一个status的field,只有三个值:status_pending, status_published, status_deleted,若是咱们把全部的status加载到内存里,每一个文档须要14-16byte,也就是说15GB
相反,咱们能够确认这三个特殊的字符串,对他们排序,依次编号0,1,2
Ordinal | Term ------------------- 0 | status_deleted 1 | status_pending 2 | status_published
序号对应的字符串值要在序号列表中存储一次,每一个文档只要使用他们的编号来表示他们所包含的值就能够了。
Doc | Ordinal ------------------------- 0 | 1 # pending 1 | 1 # pending 2 | 2 # published 3 | 0 # deleted
这个能够把15GB的内存占用减小到小于1GB
可是这里有个问题,记住fielddata缓存是针对每一个段的,若是一个段包含两个状态-status_deleted 和 status_published,这个序号是(0 和1),若是其它段中有三个不一样的状态,那相同的序号的含义在两个段中是不一样的。
若是咱们试图执行一个status 的field的汇总操做(aggregation ),咱们须要在实际的字符串上进行汇总。觉得着咱们要在全部的段上识别出有相同值的文档,一个单纯的方式就是在每一个段上进行汇总操做,从每一个段上返回字符串的值,而后合并他们获得最终的结果,这样是可行的,这会很慢很耗CPU。
想反,咱们实验了一个数据结构叫作全局序数,全局序号只是在fielddata之上,一小部份内存数据结构,把全部的段的惟一值存储在一个序号列表里,就像前面咱们描述的那样。
如今,咱们的汇总操做只要在全局序号上进行汇总,把序号转换成实际字符串,只要在最后一个汇总中进行就能够了,这大大提升了在只要三四个值的field上进行汇总(还有排序)操做的性能。
构建全局序数
固然,天下没有免费的午饭,全局序数是针对一个索引的全部段的,因此若是增长了一个段或者删除了一个段,这个全局序数都要进行重建,长袖须要读取每一个段的每一个惟一term。存储基数越大,惟一tems越多,这个过程会更长。
全局序数是建立在内存里的fielddata和doc values之上的,实际上,他们也是doc values性能表现的主要缘由。
和fielddata的加载同样,全局序数的构建默认也是延迟进行的,当第一个请求须要fielddata时候,会触发全局序数的构建,和你的field的基数有关,这可能致使用户的一个延迟响应。一旦全局序数构建完成,他们将被使用,直到索引中的段发生变化:刷新,写入和合并。
饿汉式(预先加载)全局序数
PUT /music/_mapping/_song { "song_title": { "type": "string", "fielddata": { "loading" : "eager_global_ordinals" } } }
注:设置为eager_global_ordinals 也表明实现了预先加载fielddata。
就像预先加载fielddata同样,全局序数会在一个新的段可进行搜索以前进行构建。
注意:序数只用于字符串类型的field,数值类型(整型,地理位置,时间等)都不须要一个序数映射,由于他们自身就是一个内存的序数映射。
因此,你只能开启字符串类型的全局序数的预加载。
文档的值也能够有他们的全局序数:
PUT /music/_mapping/_song { "song_title": { "type": "string", "doc_values": true, "fielddata": { "loading" : "eager_global_ordinals" } } }
注:这种状况,fielddata就不加载到内存里了,而是文档的values会被加载到系统级缓存里。
和fielddata的预加载不一样,对全局序数的预加载会对数据的实时性有影响,队友一个很大基数的field,构建全局序数会致使数秒的延迟刷新,是把时间花在每次刷新上呢,仍是每次刷新以后的第一次查询上面这是一个选择。若是你插入数据频繁,查询不多,最好是把代价花在查询时刻,而不是每次刷新上。
注意:让你的全局序号本身处理,若是你有一个很大基数的field,致使很长时候构建,你能够增长refresh_interval 配置,这样回增长全局序数的有效时间,减小CPU的利用率,一样构建全局序数的次数也会减小。
Index warmers(索引热身)
最后,咱们到了index warmers,wamers能够提前加载fielddata和全局序数,可是他还有一个目的。一个索引的warmer容许你指定一个查询或者汇聚操做在你的新段可被查询以前执行,这个idea就叫作预热、或者叫暖身,缓存等,那你的用户就永远不会遇到延迟尖峰时刻了。
期初,这个warmers的最重要的用处是为了确保fielddata的预先加载,这一般是开销最大的部分,这是咱们以前讨论的利用这个技术控制的最好的地方,然而,warmers能够用于构建filter 缓存,也能够用于预加载fielddata,这个目的随你选择。
先让咱们注册一个warner再去讨论发生了什么:
PUT /music/_warmer/warmer_1 { "query" : { "filtered" : { "filter" : { "bool": { "should": [ { "term": { "tag": "rock" }}, { "term": { "tag": "hiphop" }}, { "term": { "tag": "electronics" }} ] } } } }, "aggs" : { "price" : { "histogram" : { "field" : "price", "interval" : 10 } } } }
warmers是注册在一个特定的索引上面的,每个warmer都有一个惟一的id,由于你能够为一个索引指定多个warmer。
而后你能够之低昂一个查询,或者多个查询,他包含查询语句,filters,aggregations,sort,脚本等有效的查询语句。重点是要注册那些你的用户将要进行,有表明性的查询,从而适当的缓存能够被预先加载。
当一个新的段产生的时候,elasticsearch就会执行你这些warmers里的查询语句。执行这些查询会致使缓存进行加载,只有索引warmers都被执行了,新的段才会变成可搜素状态。
注意:相似于预先加载,warmers 把对冷缓存的开销转移到了刷新时刻。当你注册了warmers,你必须是通过明智判断的。你能够增长成千上万个wamer确保预热全部的缓存,可是这将极大的增长一个新的段从建立变为可搜索的时间。
实践中,选择几条表明大部分用户的查询语句进行注册。
一些管理方面的细节(例如获取存在的warmer,删除warmer)就不在这里过多解释了,阅读warmer相关文档,获取其它细节。