Index模块用来创建索引并管理于索引相关的各个方面。由于一些模块的生命周期内都跟索引息息相关,因此在创建索引时可以指定相关模块的一些配置(事实上这也是推荐配置索引的方式)。
index settings:
有些索引级别的配置是跟其他模块无关的。比如:
index.compound_format:是否设定compound format格式。compound format在基于文件的存储中能降低打开文件的句柄数目。默认设置是false,不启用。因为non compound format性能较好。所以OS分配给es足够多的file handle是及其重要的。另外:compound_format可以设置一个在0--1之间的数值。0代表false,1代表true,中间值代表一个百分比:如果合并的segment小于总体的这个百分比,那将会写成compound format,否则写成compound format。
index.compound_on_flush:一个新的segment(新创建的而非merge产生的)是否应该设置成compound format。默认是true,可以动态设定。
index.refresh_interval:refresh操作的执行间隔。默认是1s。设置成-1表示禁用。
index.shard.check_on_startup:shard打开时一致性检测。设置true表示检测,防止打开一些segment损坏的shard。设置fix,shard也会被检测,不过检测过程中出现的损坏的segment会被自动移除。默认设置是false,不检测,因为check过程会比较耗时,尤其是索引较大的时候。设置成fix,可能会导致数据丢失,请谨慎使用。
1:analysis
索引分析模块相当于分析器的注册表。当一个文档进行索引时,会分解字段,以及查询时处理查询字符串。
分析器通常有一个Tokenizer和0个或者多个TokenFilters组成。在执行其他分析步骤之前,可以跟分析器关联一组CharFilters来处理字符。无论在mapping定义还是在其他API中,分析模块允许注册TokenFilters,Tokenizers和Analyzers,并且给定一个自定义的逻辑名称。在没有显式定义分析器的情况下,默认的analyzers,token filters和tokenizers会自动注册。
2:index shard allocation
shard allocation filtering:
使用include/exclude filter来控制索引在node中的分配,可以在index level和cluster level设置。下面给出一个在cluster level设置的例子:
我们有4个节点,都有一个叫tag的属性。每一个节点的tag属性都有唯一的值。比如:Node1如此设置node.tag:value1。以此类推。现在创建一个索引,一句tag的值进行分布,假设分布在值为value1和value2的节点上。
curl -XPUT localhost:9200/test/_settings -d '{
"index.routing.allocation.include.tag" : "value1,value2"
}'
现在要创建一个索引,除了values3的节点其余节点都可以分布。
curl -XPUT localhost:9200/test/_settings -d '{
"index.routing.allocation.exclude.tag" : "value3"
}'
其他类似设置之前的博客中有介绍,参照
total shards per node:
index.routing.allocations.total_shards_per_node:针对一个具体的索引在单一节点上允许分配多少个shard(包括primary shard 和 replicas)。
disk-based shard allocation:
es可以根据磁盘使用情况来决定是否继续分配shard。默认设置是开启的,也可以通过api关闭:cluster.routing.allocation.disk.threshold_enabled: false
在开启的情况下,有两个重要的设置:
cluster.routing.allocation.disk.watermark.low:控制磁盘最小使用率。默认85%.说明es在磁盘使用率达到85%的时候将会停止分配新的shard。也可以设置为一个绝对数值,比如500M.
cluster.routing.allocation.disk.watermark.high:控制磁盘的最大使用率。默认90%.说明在磁盘使用率达到90%的时候es将会relocate shard去其他的节点。同样也可以设置为一个绝对值。
watermark setting可以通过update-api动态修改,默认es每隔30s会收集各个节点磁盘的使用情况,可以cluster.info.update.interval来设置时间间隔。
3:index slow log
search slow log:
Shard level slow search log可以记录慢查询(query and fetch executions)到一个特定的日志文件中去。
query阶段和feteh节点的阈值都可以设置,比如:
#index.search.slowlog.threshold.query.warn: 10s
#index.search.slowlog.threshold.query.info: 5s
#index.search.slowlog.threshold.query.debug: 2s
#index.search.slowlog.threshold.query.trace: 500ms
#index.search.slowlog.threshold.fetch.warn: 1s
#index.search.slowlog.threshold.fetch.info: 800ms
#index.search.slowlog.threshold.fetch.debug: 500ms
#index.search.slowlog.threshold.fetch.trace: 200ms
默认情况下,以上设置都是关闭的(设置为-1).日志级别(warn,info,debug,trace)控制什么情况下记录日志。logging是设置在shard level的。跟request level相比,shard level更贴近query在具体机器上的实际运行情况。所有的设置都是index level的,每一个index可以有自己区别于其他index的设置,并且可以动态修改。
logging file的默认设置是这样的:
index_search_slow_log_file:
type: dailyRollingFile
file: ${path.logs}/${cluster.name}_index_search_slowlog.log
datePattern: "'.'yyyy-MM-dd"
layout:
type: pattern
conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n"
index slow log:
跟search slow log类似。
#index.indexing.slowlog.threshold.index.warn: 10s
#index.indexing.slowlog.threshold.index.info: 5s
#index.indexing.slowlog.threshold.index.debug: 2s
#index.indexing.slowlog.threshold.index.trace: 500ms
logging file设置:
index_indexing_slow_log_file:
type: dailyRollingFile
file: ${path.logs}/${cluster.name}_index_indexing_slowlog.log
datePattern: "'.'yyyy-MM-dd"
layout:
type: pattern
conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n"
4:merge
es中的一个shard是一个lucene index,一个lucene index分为许多segments。segment是索引数据的内部存储单元,并且存储的位置是不变的直到删除。Segment周期性的合并成较大的segment,以此来保持索引大小在一个范畴内并执行实际的删除操作。
一个lucene index中含有的segment越多,意味着查询速度的变慢和更多的内存消耗。merge操作就是减少segment数目的过程,然后merge操作是一个昂贵的操作,尤其是在io不好的情况下。
policy:
index merge policy模块用以控制merge的合并策略,默认是tiered。
tiered策略:
每一次合并过程在满足最大分段数目的前提下都会产生大小大致相等的分段。跟log_bytes_size的合并策略相比,tiered策略可以合并不相邻的segment,并且对一个合并多少个segment和每一个合并层次上允许合并多少个段进行了区分。这个合并策略不支持over-merge(比如级联merge)。
具体设置如下:
index.merge.policy.expunge_deletes_allowed:当expungeDelete操作调用时,我们只能merge那些delete比例超过指定阈值的segment。默认是10%.
index.merge.policy.floor_segment:小于这个值的segment会四舍五入,比如当作大致相等的segment用于merge。这样可以防止很小的segment的频繁flush,同时避免索引中的长尾问题。默认是2M.
index.merge.policy.max_merge_at_once:在normal merge过程中一次merge多少segments。默认10.
index.merge.policy.max_merge_at_once_explicit:在optimize和expungeDeletes的过程中,一次merge允许最大的segment数量,默认30。
index.merge.policy.max_merged_segment:normal merge过程中允许生成的最大尺寸的segment。这个大小可以预估为所有合并段的大小之和(delete docs会降低这个值)。默认是5g。
index.merge.policy.segments_per_tier:每一轮merge的segment的允许数量。较小的值会导致较多的merge发生,但是最终的segment数目会较少。默认是10.注意,这个值的设置要大于等于max_merge_at_once。否则将会导致太多merge发生。
index.merge.policy.reclaim_deletes_weight:控制如何积极回收更多的删除合并是好的。值越大,越有利于回收删除。设置为0表明删除不影响合并选择。默认为2.0
index.compound_format:index存储是否选用compound file。默认false。
对于normal merge,这个策略首先计算一个预算出来,表明当前索引中允许多少个segment存在。如果当前索引超过了这个预算值,之后,就对segment按照大小(考虑删除比例这个因素)降序排列,然后寻求最低消耗的合并。merge的消耗倾向于较小的偏移量,能回收更多的删除空间和能生成较小的索引大小,因此是几个因素的共同考量:合并的偏移量,总共merge的大小,回收的空间。如果merge会产生大于max_merged_segment的segment,那么会尝试少合并一个segment(1也是有可能的,如果这个段含有delete),以此来保持段大小不超过阈值。
通过以上的策略可以知道,对于gb级别甚至更大的shard,一个索引可能含有多个segment,这会导致search操作降低效率。可以通过indices api看segments的信息,或许可以尝试增加max_merged_segment或者执行optimize call(在负载较小的情况下操作)。
log_byte_size策略:
这个策略将segments合并成大小呈现指数级别增加的segment,每一个级别segment的数量将会小于合并因子。当出现了多出合并因子的segment,这个级别的segment就会全部参与合并。这个策略有以下设置:
index.merge.policy.merge_factor:在索引过程中segment进行merge的频率。较小的值,索引过程中使用较小的ram,在未作优化的索引上查询速度较快,但是索引较慢。较大的值,索引过程中使用较多的ram,在未作优化的索引上查询速度较慢,但是索引较快。因此,较大的值(>10)适合批量建立索引的情况,较小的值(<10)适合即建立索引又查询的交互情况。默认是10.
index.merge.policy.min_merge_size:设定最低级别的段大小。所有小于这个值的segment都被认为是同级别(即使相差很大),因此在达到merge_factor的时候,这些segment都将参与合并。这将有效截断较小的segment的长尾现象,要不然这些小分段会成为一个单独的级别。如果这个值设置过大,将会显著提升索引过程中的合并成本(如果你刷新许多小的segment)。默认是1.6M
index.merge.policy.max_merge_size:允许合并的最大segment大小(字节),默认是无限制的。
index.merge.policy.max_merge_docs:允许合并的最大segment大小(doc数目),默认是无限制的。
log_doc策略:
这个策略跟log_byte_size类似,只是衡量标准变成docs。以下设置也参考上述策略:
index.merge.policy.merge_factor index.merge.policy.min_merge_docs index.merge.policy.max_merge_docs
以上是merge的三种策略。下面说一下merge的调度:
merge scheduler(并行合并调度器)控制merge的执行。merge在单独的线程中执行,当达到最大merge线程数目时,未来的merge将会pending,等到新的可用线程。
merge scheduler支持以下设置:
index.merge.scheduler.max_thread_count:同时执行merge的最大线程数目。默认是Math.max(1, Math.min(3, Runtime.getRuntime().availableProcessors() / 2)).在SSD盘上运转良好。如果是在旋转盘片驱动上,设置为1.
串行合并调度器,只是为了兼容性考虑。可以用并行合并调度器设置线程数目为1即可。
5:store
store模块控制index的存储方式。
index要么存储在内存中,要么存储在硬盘上。内存中提供更好的性能,但是容量收到物理内存的限制。
es默认采用local gateway,文件存储形式设置为no in memory storage因此需要持久化存储,因为local gateway是从本地来创建状态信息。
基于内存的存储方式还有一个重要方面就是es支持memory存储类型,将索引存储在堆空间以为的内存中。事实上不需要很大的额外的堆空间。
存储级别的限制:
Lucene的工作机制是不断创建不可更改的段(直至被删除),并且不断的merge(根据配置的策略)这些段。merge过程是异步的,不会影响正常的index/search速度。尤其是在低io的环境下,merge是会影响index/search速度的,因为merge是一个非常消耗io的操作。store模块允许对无论是在node级别还是在index级别的merge操作进行限制。在节点级别的节流设置将会保证该节点上所有的shard每一秒不会有超过这个流量的字节通过,设置indices.store.throttle.type: merge,indices.store.throttle.max_bytes_per_sec:5m等。节点级别的设置可以cluster update api动态更改。默认是20m,type为merge。如果需要设置索引级别的节留,则忽略节点级别的设置,index.store.throttle.type和index.store.throttle.max_bytes_per_sec。默认的type值是node,表示根据node level的设置实行节流。这些设置都能动态修改。
file system storage types:
基于文件系统的存储是默认的存储方式。有不同的实现和存储类型。最适合的运行环境会自动选择:window64上是mmapfs,window32上是simplefs,其余是默认设置niofs。
这些都会在elasticsearch.yml中设置: index.store.type: niofs
同样可以在创建索引的时候设置:
curl -XPUT localhost:9200/my_index -d '{
"settings": {
"index.store.type": "niofs"
}
}';
以下介绍支持的存储方式:
simplefs是一个随机访问文件系统的简单实现,对应lucene中的SimpleFsDirectory。这个实现并发性能是瓶颈。当你需要对索引持久化,最好用niofs。
niofs通过NIO将索引写到文件系统中,对应lucene中的NIOFSDirectiry。允许多线程访问。由于在SUN的java实现中存在bug,因此在windows不推荐使用。
mmap fs将索引文件存储到文件系统,通过映射文件到内存中,对应于lucene的MMapDirectory。内存映射将划分出与内存映射文件一样的虚拟地址空间。请确保系统拥有足够的虚拟地址空间。
hybrid mmap/nio fs:将索引文件存储到文件系统的具体类型由将文件映射到内存中的类型决定或者用java nio。目前只有Lucene term dictionary和doc values files映射到了内存中以降低对操作系统的影响。所有其他的文件用Lucene NIOFSDirectory来打开。如果term dictionary非常大,那地址空间也需要进一步设定。
memory:memory type存储索引在主存中,用Lucene's RamIndexStore。
6:mapper
mapper模块存储了索引中type的定义,无论是在创建索引的时候创建还是用put api创建。对于没有显式定义的mapping,采用动态mappingd的方式设置mapping信息也会存储在mapper模块中。
动态mapping:在对文档进行index的过程中,新的types或者新的field可以动态的添加的索引中。当es遇到一个新的type,会用默认的mapping来设置这个type的mapping。当es遇到某一个type的新出现的field,会利用自动检测机制来确定这个field的数据类型然后自动添加到type的mapping中。详细的参见mapping模块。
缺省mapping:新type创建(index creation time 或者是 put mapping api)或者在索引文档过程中,这个type会用_default_ mapping作为基础。任何mapping的设定都会覆盖_default_的值。default mapping的定义是一个简单的定义:
{
_default_ : {
}
}
非常简单是么?但是,基本上所有设置都有默认值,这些默认值(包括动态添加自动时候的默认值)都会自动添加到默认的mapping中。内置的default mapping在新索引创建或者全局的mapping设置(所有索引)会应用。可以有几种方式覆盖。最简单的方式是定义一个default-mapping.json的文件,放在config目录下(这个目录可以通过index.mapper.default_mapping_location设置)。
可以通过设置index.mapper.dynamic : false来禁止新类型的动态创建。
7:translog
每一个shard都有一份事物或者叫预写日志与之相关联。目的是确保每一次索引/删除操作都是原子的,而你并不需要显式提交每一个操作。一个flush(commit)操作基于以下几个参数:
index.translog.flush_threshold_ops:多少操作之后执行一次flush,默认是;unlmited
index.translog.flush_threshold_size:满足多大数据量,触发一次flush。默认200mb
index.translog.flush_threshold_period:如果没有触发flush操作,多长时间强制执行一次。默认30m。
index.translog.interval:多久检测一次是否需要执行flush,基于设定的value,产生一个在value和2*value之间的数值。默认是5s。
index.gateway.local.sync:translog多久同步到磁盘(fsync)。默认是5s。
这些参数可以在运行期用index setting update api更新(比如在buck操作之前可以增加这些参数值来支撑更高的TPS)。
8:cache
一个索引有不同的内置缓存模块。包括过滤器缓存和其他缓存。
filter cache:
过滤器缓存用于缓存filter的结果数据(query时使用)。过滤器缓存的默认实现是node级别的过滤器缓存(这也是大多数情形下推荐的缓存类型)
node filter cache:
节点级别的过滤器缓存可以配置成一个百分比(占用es总共内存的比例)或者一个绝对的大小。这个节点上的所有shard共享这一块缓存(这也是为什么叫节点级别过滤器缓存的原因)。过滤器缓存使用LRU算法进行淘汰:当缓存已满,最近最久未被使用的数据将会淘汰。indices.cache.filter.size用于设置过滤器缓存的大小(可以设置一个百分比或或者一个绝对数值),默认是10%.注意这是一个节点级别的设置,而不是索引级别的设置。
9:shard query cache
1.4.0Beta版新添加的特性,暂时不介绍。
10:field data
字段数据缓存主要用于对字段进行排序或者facet操作。它会将字段对应的所有数据都加载到内存中,以提供快速的访问。字段数据缓存的创建是一个相当昂贵的操作,所以推荐有充足的可分配内存并且保持加载。
注意:重新加载过滤器缓存是一个昂贵操作,性能低下。
indices.fielddata.cache.size:过滤器缓存的最大值,比如30%的节点堆内存,或者一个绝对数值12G等。默认是unbounded。
indices.fielddata.cache.expire:过滤器缓存的过期时间,默认是-1.可以设置成5m等。
可以用nodes stats api来查看字段数据缓存的数值。
11:field data formats
field data format用来控制字段数据是如何存储的。
根据field的数据类型,有不同的field data type。
特别是:string类型和numeric类型支持doc_values format,该类型允许在索引时计算field data的数据结构并且存储在磁盘上。尽管会让索引变大,并且会有一些慢。这种类型实现对实时索引来说更友好并且相比其他实现会节省更多的内存。
下边是一个设置的实例:
{
"tag": {
"type": "string",
"fielddata": {
"format": "fst"
}
}
}
tag这个字段的fielddata使用了fst这个格式存储。
运用update api可以更改field data的存储格式。这样设置之后,已经存在的fielddata将会沿用之前的设置,而新进的数据将会使用新的设置。多亏有后台的merge进程,所有的segment将最终使用最新的设置。
string field data types:
paged_bytes(default)---将term依次存储在一个大的buffer中,然后将doc映射到这个buffer中。
fst---用FST来存储term。比paged_byte要慢,但是在term共享前缀或者后缀的情形下会节省内存。
doc_values:在索引阶段计算并存储field data的数据结构到磁盘中。较小的内存占用,但是只能应用在non-analyzed的string类型(index:no or not_analyzed),并且不支持filtering。
numeric field data types:
array(default)----用array存储field data
doc_values------同上边的介绍。
geo_point field data types:
array(default)----用array存储field data
doc_values------同上边的介绍。
global ordinals(全局序):
全局序是在field data之上的一个数据结构,所有的term以字典序维持一个递增的number:每一个term拥有一个唯一的数值,term A的number比term B的number要小。全局序只支持string类型。
Field data同样也拥有一个序列,在特定的segment上拥有一个唯一的number。全局序是在这个序列之上的一个序列,提供了segment序到全局序的一个映射。全局序在所有shard上都是唯一的。
在查询时,如果segment序已经使用(比如term aggregator),那么全局序将会受益,改善执行时间。通常,这些查询特性会合并不同segment上的排序结果。用全局序,这个映射关系实在load data的时候加载而不是在query time,而只需要在创建response的时候去应用实际的term,在执行过程中,只需要用term的全局序数字代表即可,这样会改善执行时间。
指定field的全局序绑定到shard的所有segment上,这与一个具体的field绑定到一个segment是不一样的。基于这个原因,当新的segment可见之后,全局序需要重建。This one time cost would happen anyway without global ordinals, but then it would happen for each search execution instead!
全局序的加载时间取决于field中的term数量,但一般是低的,因为他的源字段已经被加载了。全局序的存储开销也通常较小,因为能被很好的压缩.全局序列的eager加载可以把加载时间从第一次执行search转移到refresh上。
fielddata loading:
默认情况下field data是懒惰加载的,第一次执行需要该字段的操作的时候加载。然而,这样会使得这次查询后边紧跟一个merge操作,速度很慢,因为load fielddata是一个代价较高的操作。可以通过配置,eager加载:
{
"category": {
"type": "string",
"fielddata": {
"loading": "eager"
}
}
}
全局序也可以eager加载:
{
"category": {
"type": "string",
"fielddata": {
"loading": "eager_global_ordinals"
}
}
}
以上配置使得category字段的field data和全局序都实现了eager加载。
disabling field data loading:
field data占用很多RAM,因此在不使用fielddata的情形下可以禁止加载,比如那些只使用全文搜索的应用场景。如下设置:
{
"text": {
"type": "string",
"fielddata": {
"format": "disabled"
}
}
}
之后,所有要求加载fielddata的操作(aggregations or sorting)都将返回一个错误。
filtering fielddata:
可以控制那些field value可以被加载到内存中,尤其对string类型的会比较有用。当对一个field应用一个mapping的时候,同样可以制定一个fielddata filter。fielddata filter可以通过put mapping api来修改。修改之后,用clear cache来重新加载新的fieldata。
fitering by frequency----通过frequence来过滤,在min---max之间。注意这个frequency的计算是基于segment的。
{
"tag": {
"type": "string",
"fielddata": {
"filter": {
"frequency": {
"min": 0.001,
"max": 0.1,
"min_segment_size": 500
}
}
}
}
}
filtering by regex----使用一个正则表达式去过滤
{
"tweet": {
"type": "string",
"analyzer": "whitespace"
"fielddata": {
"filter": {
"regex": {
"pattern": "^#.*"
}
}
}
}
}
combined filter----使用组合过滤:
{
"tweet": {
"type": "string",
"analyzer": "whitespace"
"fielddata": {
"filter": {
"regex": {
"pattern": "^#.*",
},
"frequency": {
"min": 0.001,
"max": 0.1,
"min_segment_size": 500
}
}
}
}
}
12:similarity module
不做介绍。