Elasticsearch监控(理论篇)

为了KPI、为了集群的稳定性,为了集群出问题后进行回溯追踪,为了能够更早的发现集群的瓶颈,
答案就在。。。
 
 http://rickywag.com/archives/506
 

二维码201710200137.png

 
为了KPI、为了集群的稳定性,为了集群出问题后进行回溯追踪,为了能够更早的发现集群的瓶颈,
答案就在。。。
 
 http://rickywag.com/archives/506
 

二维码201710200137.png

 

【翻译】Elasticsearch索引性能优化(2)


本文翻译自QBox官方博客的“Elasticsearch索引性能优化”系列文章中的第二篇,版权归原作者 Adam Vanderbush所有。该系列文章共有三篇,其中第一篇已有同行翻译,参考链接http://www.zcfy.cc/article/how ... .html   后续还会有第三篇的推送,敬请关注。


 
作者:Adam Vanderbush
译者:杨振涛@vivo
 
本系列文章重点关注如何最大化地提升elasticsearch的索引吞吐量和降低监控与管理负荷。

Elasticsearch是准实时的,这表示当索引一个文档后,需要等待下一次刷新后就可以搜索到该文档了。

刷新是一个开销较大的操作,这就是为什么默认要设置一个特定的间隔,而不是每索引一个文档就刷新一次。如果想索引大批量的文档,并不需要立刻就搜索到新的索引信息,为了优化索引性能甚至搜索性能,可以临时降低刷新的频率,直到索引操作完成。

一个索引库的分片由多个段组成。Lucene的核心数据结构中,一个段本质上是索引库的一个变更集。这些段是在每次刷新时所创建,随后会在后台合并到一起,以保证资源的高效使用;每个段都会消耗文件句柄、内存和CPU。工作在该场景背后的Lucene负责段的合并,一旦处理不当,可能会消耗昂贵的计算资源并导致Elasticsearch自动降级索引请求到一个单一线程上。

本文将继续关注Elasticsearch的索引性能调优,重点聚焦在集群和索引级别的各种索引配置项设置。

 
1 关注refresh_interval参数

这个间隔通过参数index.refresh_interval设置,既可以在Elasticsearch配置文件里全局设置,也可以针对每一个索引库单独设置。如果同时设置,索引库设置会覆盖全局配置。默认值是1s,因此最新索引的文档最多不超过1s后即可搜索到。

因为刷新是非常昂贵的操作,提升索引吞吐量的方式之一就是增大refresh_interval;更少的刷新意味着更低的负载,并且更多的资源可以向索引线程倾斜。因此,根据搜索需求,可以考虑设置刷新间隔为大于1秒的值;甚至可以考虑在某些时候,比如执行批量索引时,临时关闭索引库的刷新操作,执行结束后再手动打开。

更新设置API可以在批量索引时动态改变索引以便更加高效,然后再修改为更加实时的索引状态。在批量索引开始前,设置:
curl -XPUT 'localhost:9200/test/_settings' -d '{
"index" : {
"refresh_interval" : "-1"
}
}'

如果要做一次较大的批量导入,可以考虑设置index.number_of_replicas: 0来禁止副本。当设置了副本后,整个文档会被发送到副本节点,并重复索引过程;这意味着每个副本都会执行分析、索引及可能的合并操作。反之,如果索引时设置0副本,完成后再打开副本支持,恢复过程实质上只是一个网络字节流传输的过程,这比重复索引过程要高效得多了。
curl -XPUT 'localhost:9200/my_index/_settings' -d ' {
"index" : {
"number_of_replicas" : 0
}
}'

然后一旦批量索引完成,即可更新设置(比如恢复成默认设置):
curl -XPUT 'localhost:9200/my_index/_settings' -d '{
"index" : {
"refresh_interval" : "1s"
}
}'

并且可以强制触发一次合并:
curl -XPOST 'localhost:9200/my_index/_forcemerge?max_num_segments=5'

刷新API支持显式地刷新一个或多个索引库,以便让上次刷新后的所有操作完成并可被搜索感知。实时或近实时能力取决于所使用的索引引擎。比如,内置引擎要求显式调用刷新,而默认地刷新是周期性执行的。
curl -XPOST 'localhost:9200/my_index/_refresh'


 2 段与合并

段合并是一个计算开销较大的操作,而且会消耗大量的磁盘I/O。由于合并操作比较耗时,尤其是较大的段,所以一般设定为后台执行;这也没什么太大问题,因为大段的合并相对还是比较少的。

但也有时候,合并速率会低于生产速率;一旦如此,Elasticsearch将会自动地限流索引请求到一个单一线程。这能阻止段爆发问题,否则在合并前可能会生成数百个段。

Elasticsearch在这里默认是比较保守的:不希望搜索性能受到后台合并操作的挤兑;但有时(尤其是使用SSD,或写日志的场景)节流限制会过低。

默认的20 MB/s对于传统机械磁盘是一个挺不错的设置;如果使用SSD,可能要考虑加大该设置到100–200 MB/s。
curl -XPUT 'localhost:9200/test/_settings' -d '{
"index" : {
"refresh_interval" : "-1"
}
}'

如果正在做批量导入,且根本不介意搜索,就可以彻底关闭合并限流;这样索引操作就会根据磁盘的速率尽可能快地执行:
curl -XPUT 'localhost:9200/_cluster/settings' -d '{
"transient" : {
"indices.store.throttle.type" : "none"
}
}'

设置限流类型为none就可以完全关闭合并限流;等批量导入完成后再恢复该配置项为merge。
curl -XPUT 'localhost:9200/_cluster/settings' -d '{
"transient" : {
"indices.store.throttle.type" : "merge"
}
}'

注意:上面的设置只适用于Elasticsearch 1.X版本,Elasticsearch 2.X移除了索引级别的速率限制(indices.store.throttle.type、 indices.store.throttle.max_bytes_per_sec、index.store.throttle.type、 index.store.throttle.max_bytes_per_sec),下沉到Lucene的ConcurrentMergeScheduler,以自动管理限流。

合并调度器(ConcurrentMergeScheduler)在需要时会控制合并操作的执行。合并操作运行在独立的线程池中,一旦达到最大线程数,更多的合并请求将会阻塞等待,直到有可用的合并线程。

合并调度器支持下列动态设置: 
index.merge.scheduler.max_thread_count

最大线程数默认为 Math.max(1,Math.min(4,Runtime.getRuntime().availableProcessors() / 2)),对于固态硬盘(SSD)工作得很好;如果使用传统机械硬盘,则降低到1。

机械介质在并发I/O方面有较大的时间开销,因此需要减少线程数,以便能按索引并发访问磁盘。该设置允许每次有max_thread_count + 2个线程操作磁盘,所以设置为1表示支持3个线程。

如果使用机械硬盘而不是SSD,就要在elasticsearch配置文件中加入以下配置: 
index.merge.scheduler.max_thread_count: 1

当然也可以为单个索引库设置:
curl -XPUT 'localhost:9200/my_index/_settings' -d '{ 
"index.merge.scheduler.max_thread_count" : 1
}'

为所有已创建的索引库设置:
curl -XPUT 'localhost:9200/_settings' -d '{ 
"index.merge.scheduler.max_thread_count" : 1
}'


 3 事务日志的清理

在节点挂掉时事务日志可以防止数据丢失,设计初衷是帮助在flush时原本丢失的分片恢复运行。该日志每5秒,或者在每个索引、删除、更新或批量请求(不管先后顺序)完成时,会提交到磁盘一次。

对Lucene的变更仅会在一次Lucene提交后持久化到磁盘,Lucene提交是比较重量级的操作,索引不能再每个索引或删除操作后就执行。当进程退出或硬件故障时,一次提交后或另一次提交前的变更将会丢失。

为防止这些数据丢失,每个分片有一个事务日志,或者与之关联的预写日志。任何索引或删除操作,在内置的Lucene索引处理完成后都是写到事务日志中。崩溃发生后,就可以从事务日志回放最近的事务来恢复分片。

Elasticsearch的flush操作,本质上是执行了一次Lucene提交并启动了一个新的事务日志;这些都是在后台自动完成的,目的是确保事务日志不会变得过大,否则恢复数据期间的回放操作可能需要消耗相当长的时间。这个功能同样暴露了一个API供调用,虽然很少需要手动触发。

与刷新(refresh)一个索引分片相比,真正昂贵的操作是flush其事务日志(这涉及到Lucene提交)。Elasticsearch基于许多随时可变的定时器来执行flush。通过延迟flush或者彻底关闭flush可以提升索引吞吐量。不过并没有免费的午餐,延迟flush最终实际执行时显然会消耗更长的时间。

下列可动态更新的配置控制着内存缓存刷新到磁盘的频率:

index.translog.flush_threshold_size - 一旦事务日志达到这个值,就会发生一次flush;默认值为512mb。

index.translog.flush_threshold_ops - 在多少操作后执行flush,默认无限制。

index.translog.flush_threshold_period - 触发一次flush前的等待时间,不管日志大小,默认值为30分钟。

index.translog.interval - 检查是否需要flush的时间间隔,随机在该时间到2倍之间取值,默认为5秒。

可以把index.translog.flush_threshold_size的值从默认值的512MB调大比如到1GB,这样在一次flush发生前就可以在日志中积累更大的段。通过构建更大的段,就可以减少flush的次数以及大段的合并次数。所有这些措施加起来就会减少磁盘I/O并获得更好的索引效率。

当然,这需要一定数量的可用堆内存,用于额外的缓存空间,所以调整此类配置时请注意这一点。


 4 索引缓冲区的容量规划

索引缓冲区用于存储新的索引文档,如果满了,缓冲区的文档就会写到磁盘上的一个段。节点上所有分片的缓冲区都是独立的。

下列配置项是静态的,并且必须在集群的每个数据节点上都配置:

indices.memory.index_buffer_size - 可设置为百分比或者字节数大小,默认是10%,表示总内存的10%分配给该节点,作为索引缓冲区大小,全局共享。

indices.memory.min_index_buffer_size - 如果index_buffer_size设置为百分比,那么这项配置用于指定一个绝对下限,默认是48MB。

indices.memory.max_index_buffer_size - 如果index_buffer_size设置为百分比,那么这项配置用于指定一个绝对上限,默认是无限制。

配置项indices.memory.index_buffer_size定义了可供索引操作使用的堆内存百分比(剩余堆内存将主要用于检索操作)。如果要索引很多数据,默认的10%可能会太小,有必要调大该值。


5 索引和批量操作的线程池大小

接下来试试在节点级别调大索引和批量操作的线程池大小,看看否带来性能提升。

index - 用于索引和删除操作。线程类型是固定大小的(fixed),默认大小是可用处理器核数,队列大小queue_size是200,该线程池最大为1+可用处理器核数。

bulk - 用于批量操作。线程类型是固定大小的,默认大小是可用处理器核数,队列大小是50,线程池最大为1+可用处理器核数。

单个分片与独立的Lucene是一个层次,因此同时执行索引的并发线程数是有上限的,在Lucene中默认是8,而在ES中可以通过index.index_concurrency配置项来设置。

在为该参数设置默认值时应当多想一想,特别是对于往一个索引库索引数据时,一个节点只有一个分片的情况。

由于索引/批量线程池可以保护和控制并发,所以大部分时候都可以考虑调大默认值;尤其是对于节点上没有其他分片的情况(评估是否值得),可以考虑调大该值。


关于译者


杨振涛

vivo互联网搜索引擎团队负责人,开发经理。10年数据和软件领域经验,先后从事基因测序、电商、IM及厂商互联网领域的系统架构设计和实现。专注于实时分布式系统和大数据的存储、检索和可视化,尤其是搜索引擎及深度学习在NLP方向的应用。技术翻译爱好者,TED Translator,InfoQ中文社区编辑。


 
 
未经授权,禁止转载。
 
英文原文地址:https://qbox.io/blog/maximize- ... art-2 
继续阅读 »


本文翻译自QBox官方博客的“Elasticsearch索引性能优化”系列文章中的第二篇,版权归原作者 Adam Vanderbush所有。该系列文章共有三篇,其中第一篇已有同行翻译,参考链接http://www.zcfy.cc/article/how ... .html   后续还会有第三篇的推送,敬请关注。


 
作者:Adam Vanderbush
译者:杨振涛@vivo
 
本系列文章重点关注如何最大化地提升elasticsearch的索引吞吐量和降低监控与管理负荷。

Elasticsearch是准实时的,这表示当索引一个文档后,需要等待下一次刷新后就可以搜索到该文档了。

刷新是一个开销较大的操作,这就是为什么默认要设置一个特定的间隔,而不是每索引一个文档就刷新一次。如果想索引大批量的文档,并不需要立刻就搜索到新的索引信息,为了优化索引性能甚至搜索性能,可以临时降低刷新的频率,直到索引操作完成。

一个索引库的分片由多个段组成。Lucene的核心数据结构中,一个段本质上是索引库的一个变更集。这些段是在每次刷新时所创建,随后会在后台合并到一起,以保证资源的高效使用;每个段都会消耗文件句柄、内存和CPU。工作在该场景背后的Lucene负责段的合并,一旦处理不当,可能会消耗昂贵的计算资源并导致Elasticsearch自动降级索引请求到一个单一线程上。

本文将继续关注Elasticsearch的索引性能调优,重点聚焦在集群和索引级别的各种索引配置项设置。

 
1 关注refresh_interval参数

这个间隔通过参数index.refresh_interval设置,既可以在Elasticsearch配置文件里全局设置,也可以针对每一个索引库单独设置。如果同时设置,索引库设置会覆盖全局配置。默认值是1s,因此最新索引的文档最多不超过1s后即可搜索到。

因为刷新是非常昂贵的操作,提升索引吞吐量的方式之一就是增大refresh_interval;更少的刷新意味着更低的负载,并且更多的资源可以向索引线程倾斜。因此,根据搜索需求,可以考虑设置刷新间隔为大于1秒的值;甚至可以考虑在某些时候,比如执行批量索引时,临时关闭索引库的刷新操作,执行结束后再手动打开。

更新设置API可以在批量索引时动态改变索引以便更加高效,然后再修改为更加实时的索引状态。在批量索引开始前,设置:
curl -XPUT 'localhost:9200/test/_settings' -d '{
"index" : {
"refresh_interval" : "-1"
}
}'

如果要做一次较大的批量导入,可以考虑设置index.number_of_replicas: 0来禁止副本。当设置了副本后,整个文档会被发送到副本节点,并重复索引过程;这意味着每个副本都会执行分析、索引及可能的合并操作。反之,如果索引时设置0副本,完成后再打开副本支持,恢复过程实质上只是一个网络字节流传输的过程,这比重复索引过程要高效得多了。
curl -XPUT 'localhost:9200/my_index/_settings' -d ' {
"index" : {
"number_of_replicas" : 0
}
}'

然后一旦批量索引完成,即可更新设置(比如恢复成默认设置):
curl -XPUT 'localhost:9200/my_index/_settings' -d '{
"index" : {
"refresh_interval" : "1s"
}
}'

并且可以强制触发一次合并:
curl -XPOST 'localhost:9200/my_index/_forcemerge?max_num_segments=5'

刷新API支持显式地刷新一个或多个索引库,以便让上次刷新后的所有操作完成并可被搜索感知。实时或近实时能力取决于所使用的索引引擎。比如,内置引擎要求显式调用刷新,而默认地刷新是周期性执行的。
curl -XPOST 'localhost:9200/my_index/_refresh'


 2 段与合并

段合并是一个计算开销较大的操作,而且会消耗大量的磁盘I/O。由于合并操作比较耗时,尤其是较大的段,所以一般设定为后台执行;这也没什么太大问题,因为大段的合并相对还是比较少的。

但也有时候,合并速率会低于生产速率;一旦如此,Elasticsearch将会自动地限流索引请求到一个单一线程。这能阻止段爆发问题,否则在合并前可能会生成数百个段。

Elasticsearch在这里默认是比较保守的:不希望搜索性能受到后台合并操作的挤兑;但有时(尤其是使用SSD,或写日志的场景)节流限制会过低。

默认的20 MB/s对于传统机械磁盘是一个挺不错的设置;如果使用SSD,可能要考虑加大该设置到100–200 MB/s。
curl -XPUT 'localhost:9200/test/_settings' -d '{
"index" : {
"refresh_interval" : "-1"
}
}'

如果正在做批量导入,且根本不介意搜索,就可以彻底关闭合并限流;这样索引操作就会根据磁盘的速率尽可能快地执行:
curl -XPUT 'localhost:9200/_cluster/settings' -d '{
"transient" : {
"indices.store.throttle.type" : "none"
}
}'

设置限流类型为none就可以完全关闭合并限流;等批量导入完成后再恢复该配置项为merge。
curl -XPUT 'localhost:9200/_cluster/settings' -d '{
"transient" : {
"indices.store.throttle.type" : "merge"
}
}'

注意:上面的设置只适用于Elasticsearch 1.X版本,Elasticsearch 2.X移除了索引级别的速率限制(indices.store.throttle.type、 indices.store.throttle.max_bytes_per_sec、index.store.throttle.type、 index.store.throttle.max_bytes_per_sec),下沉到Lucene的ConcurrentMergeScheduler,以自动管理限流。

合并调度器(ConcurrentMergeScheduler)在需要时会控制合并操作的执行。合并操作运行在独立的线程池中,一旦达到最大线程数,更多的合并请求将会阻塞等待,直到有可用的合并线程。

合并调度器支持下列动态设置: 
index.merge.scheduler.max_thread_count

最大线程数默认为 Math.max(1,Math.min(4,Runtime.getRuntime().availableProcessors() / 2)),对于固态硬盘(SSD)工作得很好;如果使用传统机械硬盘,则降低到1。

机械介质在并发I/O方面有较大的时间开销,因此需要减少线程数,以便能按索引并发访问磁盘。该设置允许每次有max_thread_count + 2个线程操作磁盘,所以设置为1表示支持3个线程。

如果使用机械硬盘而不是SSD,就要在elasticsearch配置文件中加入以下配置: 
index.merge.scheduler.max_thread_count: 1

当然也可以为单个索引库设置:
curl -XPUT 'localhost:9200/my_index/_settings' -d '{ 
"index.merge.scheduler.max_thread_count" : 1
}'

为所有已创建的索引库设置:
curl -XPUT 'localhost:9200/_settings' -d '{ 
"index.merge.scheduler.max_thread_count" : 1
}'


 3 事务日志的清理

在节点挂掉时事务日志可以防止数据丢失,设计初衷是帮助在flush时原本丢失的分片恢复运行。该日志每5秒,或者在每个索引、删除、更新或批量请求(不管先后顺序)完成时,会提交到磁盘一次。

对Lucene的变更仅会在一次Lucene提交后持久化到磁盘,Lucene提交是比较重量级的操作,索引不能再每个索引或删除操作后就执行。当进程退出或硬件故障时,一次提交后或另一次提交前的变更将会丢失。

为防止这些数据丢失,每个分片有一个事务日志,或者与之关联的预写日志。任何索引或删除操作,在内置的Lucene索引处理完成后都是写到事务日志中。崩溃发生后,就可以从事务日志回放最近的事务来恢复分片。

Elasticsearch的flush操作,本质上是执行了一次Lucene提交并启动了一个新的事务日志;这些都是在后台自动完成的,目的是确保事务日志不会变得过大,否则恢复数据期间的回放操作可能需要消耗相当长的时间。这个功能同样暴露了一个API供调用,虽然很少需要手动触发。

与刷新(refresh)一个索引分片相比,真正昂贵的操作是flush其事务日志(这涉及到Lucene提交)。Elasticsearch基于许多随时可变的定时器来执行flush。通过延迟flush或者彻底关闭flush可以提升索引吞吐量。不过并没有免费的午餐,延迟flush最终实际执行时显然会消耗更长的时间。

下列可动态更新的配置控制着内存缓存刷新到磁盘的频率:

index.translog.flush_threshold_size - 一旦事务日志达到这个值,就会发生一次flush;默认值为512mb。

index.translog.flush_threshold_ops - 在多少操作后执行flush,默认无限制。

index.translog.flush_threshold_period - 触发一次flush前的等待时间,不管日志大小,默认值为30分钟。

index.translog.interval - 检查是否需要flush的时间间隔,随机在该时间到2倍之间取值,默认为5秒。

可以把index.translog.flush_threshold_size的值从默认值的512MB调大比如到1GB,这样在一次flush发生前就可以在日志中积累更大的段。通过构建更大的段,就可以减少flush的次数以及大段的合并次数。所有这些措施加起来就会减少磁盘I/O并获得更好的索引效率。

当然,这需要一定数量的可用堆内存,用于额外的缓存空间,所以调整此类配置时请注意这一点。


 4 索引缓冲区的容量规划

索引缓冲区用于存储新的索引文档,如果满了,缓冲区的文档就会写到磁盘上的一个段。节点上所有分片的缓冲区都是独立的。

下列配置项是静态的,并且必须在集群的每个数据节点上都配置:

indices.memory.index_buffer_size - 可设置为百分比或者字节数大小,默认是10%,表示总内存的10%分配给该节点,作为索引缓冲区大小,全局共享。

indices.memory.min_index_buffer_size - 如果index_buffer_size设置为百分比,那么这项配置用于指定一个绝对下限,默认是48MB。

indices.memory.max_index_buffer_size - 如果index_buffer_size设置为百分比,那么这项配置用于指定一个绝对上限,默认是无限制。

配置项indices.memory.index_buffer_size定义了可供索引操作使用的堆内存百分比(剩余堆内存将主要用于检索操作)。如果要索引很多数据,默认的10%可能会太小,有必要调大该值。


5 索引和批量操作的线程池大小

接下来试试在节点级别调大索引和批量操作的线程池大小,看看否带来性能提升。

index - 用于索引和删除操作。线程类型是固定大小的(fixed),默认大小是可用处理器核数,队列大小queue_size是200,该线程池最大为1+可用处理器核数。

bulk - 用于批量操作。线程类型是固定大小的,默认大小是可用处理器核数,队列大小是50,线程池最大为1+可用处理器核数。

单个分片与独立的Lucene是一个层次,因此同时执行索引的并发线程数是有上限的,在Lucene中默认是8,而在ES中可以通过index.index_concurrency配置项来设置。

在为该参数设置默认值时应当多想一想,特别是对于往一个索引库索引数据时,一个节点只有一个分片的情况。

由于索引/批量线程池可以保护和控制并发,所以大部分时候都可以考虑调大默认值;尤其是对于节点上没有其他分片的情况(评估是否值得),可以考虑调大该值。


关于译者


杨振涛

vivo互联网搜索引擎团队负责人,开发经理。10年数据和软件领域经验,先后从事基因测序、电商、IM及厂商互联网领域的系统架构设计和实现。专注于实时分布式系统和大数据的存储、检索和可视化,尤其是搜索引擎及深度学习在NLP方向的应用。技术翻译爱好者,TED Translator,InfoQ中文社区编辑。


 
 
未经授权,禁止转载。
 
英文原文地址:https://qbox.io/blog/maximize- ... art-2  收起阅读 »

一例Query Cache引起的性能问题分析

【携程旅行网  吴晓刚】

注:本文是针对社区问题 https://elasticsearch.cn/question/2484 的分析和总结。

问题概述:
一个线上集群,执行的Query DSL都是一样的,只是参数不同。 统计数据显示98-99%的查询响应速度都很快,只需要4 -6ms, 但有1%左右的查询响应时间在100ms - 200ms。 集群硬件配置较高,使用的SSD,系统可用内存远高于索引文件大小总和,并且线上已经运行有一段时间,数据应该已经充分预热。

诊断过程及结论:
比较巧的是,问题提出者刚好是我们自家公司的开发者,因此内部联系沟通了下,为问题的快速诊断提供了不少便利。

首先用公司的监控系统排查了一遍集群所有关键数据,未发现任何可能引起查询耗时高的性能瓶颈问题。 因此初步怀疑就是有查询本身比较慢。 幸好公司有应用埋点系统和日志系统,因此很方便的拿到了应用端发出的一些慢查询样例,包括请求体以及耗时。

以下是埋点系统里记录的一个耗时150ms的查询 (隐去了敏感信息,去掉了非关键部分):
POST /xxxindex/xxxdb/_search?routing=Mxxxxxxx
{
"from": 0,
"size": 100,
"query": {
"bool": {
"filter": [
{
"bool": {
"must": [
{
"bool": {
"must": [
{
"bool": {
"should": [
{
"match_phrase": {
"ord_orders_uid": {
"query": "Mxxxxxxx",
"slop": 0,
"boost": 1
}
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
},
{
"range": {
"ord_orders_orderdate": {
"from": "1405032032",
"to": "1504014193",
"include_lower": true,
"include_upper": true,
"boost": 1
}
}
},
{
"term": {
"ord_orders_ispackageorder": {
"value": 0,
"boost": 1
}
}
},
{
"bool": {
"must_not": [
{
"exists": {
"field": "ord_hideorder_orderid",
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
}
拿到查询后,自己手动执行了一下,0 hits,耗时1ms。  心里明白,命中了Query Cache,所以才会这么快。 
 
于是用clear api清掉Query Cache,然后再执行几次,有以下发现:
  1. 头两次查询耗时38ms左右。 这是因为没有cache,需要访问倒排索引,耗时符合预期。 之所以两次同样耗时,是因为索引有1个复制片,两次查询分别分配到主和副片上。
  2. 接下来两次查询耗时150ms左右。  这里要打一个大大的问号???
  3. 之后不管再查询多少次, 耗时全部是1ms,因为又开始命中Cache。

 
至此,大致明白,埋点系统里记录到的高耗时查询,是步骤2的两次操作。 什么操作耗时这么久呢? 根据经验,我判断主要是用于为range filter生成缓存,也就生成生成文档列表的bitmap,然后存放到Query Cache里。 

这个集群版本是5.1.1, 而我记得ES某个5版本开始,去掉了对term filter的cache,理由是term filter速度足够快,缓存term filter往往得不偿失。 查了官方release notes,证实这个改变正好是从5.1.1开始的(#21566),因此上面查询里的term filters被排除掉,注意力集中到了查询里唯一的一个range filter。 

单独执行了一下这个range filter,match的文档是千万数量级的。 询问用户,为何这个range filter会hit这么多文档,得知用户主要就是查询从当前时间开始至过去1年的数据,类似于做了一个now-1y  TO now这样的过滤。至此初步得出结论,因为这个range filter匹配的文档太多了,在Query Cache里为这个filter构建bitmap耗时会有些高,应该就是它带来了那额外的100多个ms。

但是还有一个待解释的问题,这种高耗时查询比例为何这么高? 再仔细想想也就明白了:因为这个集群的搜索并发量还是有点高,300 -400/s的样子,加上时间字段的精度是秒,所以,在某一秒刚开始的时候,头2次查询因为没有cache,耗时可能在38ms左右,之后会有2次查询因为需要缓存range filter,耗时会增加到150-200ms的样子,之后这1秒里剩余的查询都会命中cache,全部是几个ms, 直到下一秒开始, 周而复始。 因为每秒钟都产生2个这样需要构建缓存的查询,耗时较高,对比每秒几百次的查询量,换算成百分比就有点高了。

那么怎么解决这个问题? 对于大量含有从now-xxx TO now这样的range查询,实际上官方的文档有对应的加速技巧介绍:tune-for-search-speed.html#_search_rounded_dates 。也就是说,将查询时间的上下限round到整分钟,或者整小时,让range filter可以缓存得更久,避免出现这种过于频繁重建cache的情况。
{
"range": {
"my_date": {
"gte": "now-1y/h",
"lte": "now-1y/h"
}
}
}

在原始Query里,将range filter写成上述形式,手动测试证实可行,range filter有效期延长到1小时,从而每个小时里,只需要为range filter重建2次Cache,至此问题解决。
 
 
总结:
  1. Cache并非建得越多越好,因为Cache的生成和Evict会带来额外的开销。特别是结果集非常大的filter,缓存的代价相对查询本身可能非常高。
  2. ES 5.1.1开始取消了Terms filter Cache,因为Terms filter执行非常快,取消缓存多数情况下反而可以提高性能。
  3. 大量用到Now-xxxd To Now这样的Range filter时,可以借助round date技巧,提高Cache的有效期,减轻频繁重建Cache带来的性能问题。

继续阅读 »
【携程旅行网  吴晓刚】

注:本文是针对社区问题 https://elasticsearch.cn/question/2484 的分析和总结。

问题概述:
一个线上集群,执行的Query DSL都是一样的,只是参数不同。 统计数据显示98-99%的查询响应速度都很快,只需要4 -6ms, 但有1%左右的查询响应时间在100ms - 200ms。 集群硬件配置较高,使用的SSD,系统可用内存远高于索引文件大小总和,并且线上已经运行有一段时间,数据应该已经充分预热。

诊断过程及结论:
比较巧的是,问题提出者刚好是我们自家公司的开发者,因此内部联系沟通了下,为问题的快速诊断提供了不少便利。

首先用公司的监控系统排查了一遍集群所有关键数据,未发现任何可能引起查询耗时高的性能瓶颈问题。 因此初步怀疑就是有查询本身比较慢。 幸好公司有应用埋点系统和日志系统,因此很方便的拿到了应用端发出的一些慢查询样例,包括请求体以及耗时。

以下是埋点系统里记录的一个耗时150ms的查询 (隐去了敏感信息,去掉了非关键部分):
POST /xxxindex/xxxdb/_search?routing=Mxxxxxxx
{
"from": 0,
"size": 100,
"query": {
"bool": {
"filter": [
{
"bool": {
"must": [
{
"bool": {
"must": [
{
"bool": {
"should": [
{
"match_phrase": {
"ord_orders_uid": {
"query": "Mxxxxxxx",
"slop": 0,
"boost": 1
}
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
},
{
"range": {
"ord_orders_orderdate": {
"from": "1405032032",
"to": "1504014193",
"include_lower": true,
"include_upper": true,
"boost": 1
}
}
},
{
"term": {
"ord_orders_ispackageorder": {
"value": 0,
"boost": 1
}
}
},
{
"bool": {
"must_not": [
{
"exists": {
"field": "ord_hideorder_orderid",
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
}
拿到查询后,自己手动执行了一下,0 hits,耗时1ms。  心里明白,命中了Query Cache,所以才会这么快。 
 
于是用clear api清掉Query Cache,然后再执行几次,有以下发现:
  1. 头两次查询耗时38ms左右。 这是因为没有cache,需要访问倒排索引,耗时符合预期。 之所以两次同样耗时,是因为索引有1个复制片,两次查询分别分配到主和副片上。
  2. 接下来两次查询耗时150ms左右。  这里要打一个大大的问号???
  3. 之后不管再查询多少次, 耗时全部是1ms,因为又开始命中Cache。

 
至此,大致明白,埋点系统里记录到的高耗时查询,是步骤2的两次操作。 什么操作耗时这么久呢? 根据经验,我判断主要是用于为range filter生成缓存,也就生成生成文档列表的bitmap,然后存放到Query Cache里。 

这个集群版本是5.1.1, 而我记得ES某个5版本开始,去掉了对term filter的cache,理由是term filter速度足够快,缓存term filter往往得不偿失。 查了官方release notes,证实这个改变正好是从5.1.1开始的(#21566),因此上面查询里的term filters被排除掉,注意力集中到了查询里唯一的一个range filter。 

单独执行了一下这个range filter,match的文档是千万数量级的。 询问用户,为何这个range filter会hit这么多文档,得知用户主要就是查询从当前时间开始至过去1年的数据,类似于做了一个now-1y  TO now这样的过滤。至此初步得出结论,因为这个range filter匹配的文档太多了,在Query Cache里为这个filter构建bitmap耗时会有些高,应该就是它带来了那额外的100多个ms。

但是还有一个待解释的问题,这种高耗时查询比例为何这么高? 再仔细想想也就明白了:因为这个集群的搜索并发量还是有点高,300 -400/s的样子,加上时间字段的精度是秒,所以,在某一秒刚开始的时候,头2次查询因为没有cache,耗时可能在38ms左右,之后会有2次查询因为需要缓存range filter,耗时会增加到150-200ms的样子,之后这1秒里剩余的查询都会命中cache,全部是几个ms, 直到下一秒开始, 周而复始。 因为每秒钟都产生2个这样需要构建缓存的查询,耗时较高,对比每秒几百次的查询量,换算成百分比就有点高了。

那么怎么解决这个问题? 对于大量含有从now-xxx TO now这样的range查询,实际上官方的文档有对应的加速技巧介绍:tune-for-search-speed.html#_search_rounded_dates 。也就是说,将查询时间的上下限round到整分钟,或者整小时,让range filter可以缓存得更久,避免出现这种过于频繁重建cache的情况。
{
"range": {
"my_date": {
"gte": "now-1y/h",
"lte": "now-1y/h"
}
}
}

在原始Query里,将range filter写成上述形式,手动测试证实可行,range filter有效期延长到1小时,从而每个小时里,只需要为range filter重建2次Cache,至此问题解决。
 
 
总结:
  1. Cache并非建得越多越好,因为Cache的生成和Evict会带来额外的开销。特别是结果集非常大的filter,缓存的代价相对查询本身可能非常高。
  2. ES 5.1.1开始取消了Terms filter Cache,因为Terms filter执行非常快,取消缓存多数情况下反而可以提高性能。
  3. 大量用到Now-xxxd To Now这样的Range filter时,可以借助round date技巧,提高Cache的有效期,减轻频繁重建Cache带来的性能问题。

收起阅读 »

nginx和kibana/es集成

利用elk搞了一个日志平台,随着日志越来越多,使用的人反应kibana上查询比较慢。kibana虽然有日志,但记录的信息不全,无法分析到底是什么样的查询比较慢。因此考虑在kibana和elk之间加一个nginx。主要作用有两个:
1、记录kibana的每个请求日志
2、kibana通过nginx连到es,可以实现负载均衡的请求es。
集成方法比较简单,在任意一台机器上安装nginx,nginx里配置es相关信息,kibana配置文件中的elasticsearch.url改成nginx相应的ip和监听端口即可。
nginx配置文件的主要内容如下:
    upstream elasticsearch {
        server 10.10.10.1:9200;
        server 10.10.10.2:9200;
        server 10.10.10.3:9200;
        keepalive 10;
    }

    server {
        listen       8888;
        server_name  hostname;

        location / {
            proxy_pass http://elasticsearch;

            access_log_bypass_if ($request = 'HEAD / HTTP/1.1');
            access_log_bypass_if ($request = 'GET /_nodes?filter_path=nodes.*.version%2Cnodes.*.http.publish_address%2Cnodes.*.ip HTTP/1.1');
            access_log_bypass_if ($request = 'GET /_nodes/_local?filter_path=nodes.*.settings.tribe HTTP/1.1');
            access_log_bypass_if ($request_body = '{\"docs\":[{\"_index\":\".kibana\",\"_type\":\"config\",\"_id\":\"5.5.1\"}]}');
            access_log_bypass_if ($request = 'GET /_cluster/health/.kibana?timeout=5s HTTP/1.1');
            access_log_bypass_if ($request = 'POST /.kibana/config/_search HTTP/1.1');
            access_log_bypass_if ($request = 'GET /_cluster/settings?include_defaults=true&filter_path=**.script.engine.*.inline HTTP/1.1');
            access_log_bypass_if ($request = 'GET /_aliases HTTP/1.1');
            access_log_bypass_if ($request = 'GET /_mapping HTTP/1.1');
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }
 
upstream定义了es有哪些节点。另外,nginx加了日志过滤模块ngx_log_if,用来过滤kibana和es之间的心跳请求日志,这个模块可以在github上下载
继续阅读 »
利用elk搞了一个日志平台,随着日志越来越多,使用的人反应kibana上查询比较慢。kibana虽然有日志,但记录的信息不全,无法分析到底是什么样的查询比较慢。因此考虑在kibana和elk之间加一个nginx。主要作用有两个:
1、记录kibana的每个请求日志
2、kibana通过nginx连到es,可以实现负载均衡的请求es。
集成方法比较简单,在任意一台机器上安装nginx,nginx里配置es相关信息,kibana配置文件中的elasticsearch.url改成nginx相应的ip和监听端口即可。
nginx配置文件的主要内容如下:
    upstream elasticsearch {
        server 10.10.10.1:9200;
        server 10.10.10.2:9200;
        server 10.10.10.3:9200;
        keepalive 10;
    }

    server {
        listen       8888;
        server_name  hostname;

        location / {
            proxy_pass http://elasticsearch;

            access_log_bypass_if ($request = 'HEAD / HTTP/1.1');
            access_log_bypass_if ($request = 'GET /_nodes?filter_path=nodes.*.version%2Cnodes.*.http.publish_address%2Cnodes.*.ip HTTP/1.1');
            access_log_bypass_if ($request = 'GET /_nodes/_local?filter_path=nodes.*.settings.tribe HTTP/1.1');
            access_log_bypass_if ($request_body = '{\"docs\":[{\"_index\":\".kibana\",\"_type\":\"config\",\"_id\":\"5.5.1\"}]}');
            access_log_bypass_if ($request = 'GET /_cluster/health/.kibana?timeout=5s HTTP/1.1');
            access_log_bypass_if ($request = 'POST /.kibana/config/_search HTTP/1.1');
            access_log_bypass_if ($request = 'GET /_cluster/settings?include_defaults=true&filter_path=**.script.engine.*.inline HTTP/1.1');
            access_log_bypass_if ($request = 'GET /_aliases HTTP/1.1');
            access_log_bypass_if ($request = 'GET /_mapping HTTP/1.1');
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }
 
upstream定义了es有哪些节点。另外,nginx加了日志过滤模块ngx_log_if,用来过滤kibana和es之间的心跳请求日志,这个模块可以在github上下载 收起阅读 »

elasticsearch index、create和update的源码分析

查看更好的排版
社区里面有人问了如下一个问题:


执行 bulk 索引文档的时候,用 index 或者 create 类型并且自定义 doc id 的情况下,是否会像 update 一样每次都要去 get 一遍原始文档? 比如下面的这条命令:


POST _bulk

{ "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } }
{ "field1" : "value1" }
{ "create" : { "_index" : "test", "_type" : "type1", "_id" : "3" } }
{ "field1" : "value3" }

问题出现的原因是他们在 bulk 测试的时候遇到了写性能的问题,而正巧社区里面前几天有这么一个类似的帖子,说的是 es 5.x 版本里面做 update 操作的性能问题。虽然和这个问题不完全一致,但都涉及到 es 索引数据的部分。

侯捷老师说:“源码面前,了无秘密”,那我们就来简单看下 es 这部分的相关代码,以便回答开篇提出的问题。

准备工作
我是用 IntelliJ IDEA 来阅读 elasticsearch 源码的,操作也简单。操作步骤如下:

1.下载 es 源码,由于 es 的commit信息比较多,可以增加 --depth=1 只下载最近的commit,减少下载时间。


git clone https://github.com/elastic/elasticsearch.git --depth=1


 
2.安装 gradle,确保版本在 3.3 及以上,然后在源码目录下执行以下命令准备导入 IntelliJ IDEA 需要的文件


gradle idea
 


3.下载安装 IntelliJ IDEA,确保版本为 2017.2 及以上版本。安装完成后,将 elasticsearch 以 gradle 形式导入即可。

大家可以参考 elasticsearch 文档说明 和 elasticsearch 文档说明 这两篇文章,细节我这里就不赘述了。

另外我是分析的 5.5.0 分支,大家记得 checkout,防止行数对应不起来。另外由于 es 代码结构有些复杂,先不在这篇文章里面梳理整个流程了,直接说核心代码。

Index/Create 源码分析

es index 和 create 最终都会调用 org/elasticsearch/index/engine/InternalEngine.java 中下面的方法:


457 public IndexResult index(Index index) throws IOException


注意这里的 index 中包含有要写入的 doc, 简单画下该方法的执行流程图,代码这里就不贴了,刚兴趣的自己去看。


es_index_create1.jpg



请结合上面的流程图来看相应的代码,整个逻辑应该还是很清晰的,接下来我们看 planIndexingAsPrimary 的逻辑。


558 private IndexingStrategy planIndexingAsPrimary(Index index) throws IOException {
 


这个方法最终返回一个 IndexingStrategy,即一个索引的策略,总共有如下几个策略:
  • optimizedAppendOnly
  • skipDueToVersionConflict
  • processNormally
  • overrideExistingAsIfNotThere
  • skipAsStale

不同的策略对应了不同的处理逻辑,前面3个是常用的,我们来看下流程图。

es_index_create2.jpg



这里的第一步判断 是否是自定义 doc id?这一步就是 es 对于日志类非自定义 doc id的优化,感兴趣的可以自己去看下代码,简单讲就是在非自定义 id 的情况下,直接将文档 add ,否则需要 update,而 update 比 add 成本高很多。

而第二个判断 检查版本号是否冲突? 涉及到是如何根据文档版本号来确认文档可写入,代码都在index.versionType().isVersionConflictForWrites方法里,逻辑也比较简单,不展开讲了,感兴趣的自己去看吧。

上面的流程图也比较清晰地列出了策略选择的逻辑,除去 optimizedAppendOnly 策略,其他都需要根据待写入文档的版本号来做出决策。接下来我们就看下获取文档版本号的方法。


389 private VersionValue resolveDocVersion(final Operation op) throws IOException {


该方法逻辑比较简单,主要分为2步:
  1. 尝试从 versionMap 中读取待写入文档的 version,也即从内存中读取。versionMap 会暂存还没有 commit 到磁盘的文档版本信息。
  2. 如果第 1 步中没有读到,则从 index 中读取,也即从文件中读取。

看到这里,开篇问题便有了答案。es 在 index 或者 create 的时候并不会 get 整个文档,而是只会获取文档的版本号做对比,而这个开销不会很大。

Update 源码分析
es update 的核心代码在 org/elasticsearch/action/update/UpdateHelper.java 中,具体方法如下:
    public Result prepare(UpdateRequest request, IndexShard indexShard, LongSupplier nowInMillis) {
final GetResult getResult = indexShard.getService().get(request.type(), request.id(),
new String[]{RoutingFieldMapper.NAME, ParentFieldMapper.NAME, TTLFieldMapper.NAME, TimestampFieldMapper.NAME},
true, request.version(), request.versionType(), FetchSourceContext.FETCH_SOURCE);
return prepare(indexShard.shardId(), request, getResult, nowInMillis);
}


代码逻辑很清晰,分两步走:
  1. 获取待更新文档的数据
  2. 执行更新文档的操作

第 1 步最终会调用 InternalEngine 中的 get 方法,如下:


350 public GetResult get(Get get, Function<String, Searcher> searcherFactory, LongConsumer onRefresh) throws EngineException {


这里就接上开篇提到的社区问题中的源码分析了。代码就不展开讲了,感兴趣的自己去看吧。

update 操作需要先获取原始文档的原因也很简单,因为这里是允许用户做部分更新的,而 es 底层每次更新时要求必须是完整的文档(因为 lucene 的更新实际是删除老文档,新增新文档),如果不拿到原始数据的话,就不能组装出更新后的完整文档了。

因此,比较看重效率的业务,最好还是不要用 update 这种操作,直接用上面的 index 会更好一些。

总结

本文通过源码分析的方式解决了开篇提到的问题,答案简单总结在下面。
es 在 index 和 create 操作的时候,如果没有自定义 doc id,那么会使用 append 优化模式,否则会获取待写入文档的版本号,进行版本检查后再决定是否写入lucene。所以这里不会去做一个 get 操作,即获取完整的文档信息。
最后,记住侯捷老师的话:
源码面前,了无秘密!

查看更好的排版

 
继续阅读 »
查看更好的排版
社区里面有人问了如下一个问题:


执行 bulk 索引文档的时候,用 index 或者 create 类型并且自定义 doc id 的情况下,是否会像 update 一样每次都要去 get 一遍原始文档? 比如下面的这条命令:


POST _bulk

{ "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } }
{ "field1" : "value1" }
{ "create" : { "_index" : "test", "_type" : "type1", "_id" : "3" } }
{ "field1" : "value3" }

问题出现的原因是他们在 bulk 测试的时候遇到了写性能的问题,而正巧社区里面前几天有这么一个类似的帖子,说的是 es 5.x 版本里面做 update 操作的性能问题。虽然和这个问题不完全一致,但都涉及到 es 索引数据的部分。

侯捷老师说:“源码面前,了无秘密”,那我们就来简单看下 es 这部分的相关代码,以便回答开篇提出的问题。

准备工作
我是用 IntelliJ IDEA 来阅读 elasticsearch 源码的,操作也简单。操作步骤如下:

1.下载 es 源码,由于 es 的commit信息比较多,可以增加 --depth=1 只下载最近的commit,减少下载时间。


git clone https://github.com/elastic/elasticsearch.git --depth=1


 
2.安装 gradle,确保版本在 3.3 及以上,然后在源码目录下执行以下命令准备导入 IntelliJ IDEA 需要的文件


gradle idea
 


3.下载安装 IntelliJ IDEA,确保版本为 2017.2 及以上版本。安装完成后,将 elasticsearch 以 gradle 形式导入即可。

大家可以参考 elasticsearch 文档说明 和 elasticsearch 文档说明 这两篇文章,细节我这里就不赘述了。

另外我是分析的 5.5.0 分支,大家记得 checkout,防止行数对应不起来。另外由于 es 代码结构有些复杂,先不在这篇文章里面梳理整个流程了,直接说核心代码。

Index/Create 源码分析

es index 和 create 最终都会调用 org/elasticsearch/index/engine/InternalEngine.java 中下面的方法:


457 public IndexResult index(Index index) throws IOException


注意这里的 index 中包含有要写入的 doc, 简单画下该方法的执行流程图,代码这里就不贴了,刚兴趣的自己去看。


es_index_create1.jpg



请结合上面的流程图来看相应的代码,整个逻辑应该还是很清晰的,接下来我们看 planIndexingAsPrimary 的逻辑。


558 private IndexingStrategy planIndexingAsPrimary(Index index) throws IOException {
 


这个方法最终返回一个 IndexingStrategy,即一个索引的策略,总共有如下几个策略:
  • optimizedAppendOnly
  • skipDueToVersionConflict
  • processNormally
  • overrideExistingAsIfNotThere
  • skipAsStale

不同的策略对应了不同的处理逻辑,前面3个是常用的,我们来看下流程图。

es_index_create2.jpg



这里的第一步判断 是否是自定义 doc id?这一步就是 es 对于日志类非自定义 doc id的优化,感兴趣的可以自己去看下代码,简单讲就是在非自定义 id 的情况下,直接将文档 add ,否则需要 update,而 update 比 add 成本高很多。

而第二个判断 检查版本号是否冲突? 涉及到是如何根据文档版本号来确认文档可写入,代码都在index.versionType().isVersionConflictForWrites方法里,逻辑也比较简单,不展开讲了,感兴趣的自己去看吧。

上面的流程图也比较清晰地列出了策略选择的逻辑,除去 optimizedAppendOnly 策略,其他都需要根据待写入文档的版本号来做出决策。接下来我们就看下获取文档版本号的方法。


389 private VersionValue resolveDocVersion(final Operation op) throws IOException {


该方法逻辑比较简单,主要分为2步:
  1. 尝试从 versionMap 中读取待写入文档的 version,也即从内存中读取。versionMap 会暂存还没有 commit 到磁盘的文档版本信息。
  2. 如果第 1 步中没有读到,则从 index 中读取,也即从文件中读取。

看到这里,开篇问题便有了答案。es 在 index 或者 create 的时候并不会 get 整个文档,而是只会获取文档的版本号做对比,而这个开销不会很大。

Update 源码分析
es update 的核心代码在 org/elasticsearch/action/update/UpdateHelper.java 中,具体方法如下:
    public Result prepare(UpdateRequest request, IndexShard indexShard, LongSupplier nowInMillis) {
final GetResult getResult = indexShard.getService().get(request.type(), request.id(),
new String[]{RoutingFieldMapper.NAME, ParentFieldMapper.NAME, TTLFieldMapper.NAME, TimestampFieldMapper.NAME},
true, request.version(), request.versionType(), FetchSourceContext.FETCH_SOURCE);
return prepare(indexShard.shardId(), request, getResult, nowInMillis);
}


代码逻辑很清晰,分两步走:
  1. 获取待更新文档的数据
  2. 执行更新文档的操作

第 1 步最终会调用 InternalEngine 中的 get 方法,如下:


350 public GetResult get(Get get, Function<String, Searcher> searcherFactory, LongConsumer onRefresh) throws EngineException {


这里就接上开篇提到的社区问题中的源码分析了。代码就不展开讲了,感兴趣的自己去看吧。

update 操作需要先获取原始文档的原因也很简单,因为这里是允许用户做部分更新的,而 es 底层每次更新时要求必须是完整的文档(因为 lucene 的更新实际是删除老文档,新增新文档),如果不拿到原始数据的话,就不能组装出更新后的完整文档了。

因此,比较看重效率的业务,最好还是不要用 update 这种操作,直接用上面的 index 会更好一些。

总结

本文通过源码分析的方式解决了开篇提到的问题,答案简单总结在下面。
es 在 index 和 create 操作的时候,如果没有自定义 doc id,那么会使用 append 优化模式,否则会获取待写入文档的版本号,进行版本检查后再决定是否写入lucene。所以这里不会去做一个 get 操作,即获取完整的文档信息。
最后,记住侯捷老师的话:
源码面前,了无秘密!

查看更好的排版

  收起阅读 »

Elasticsearch大文件检索性能提升20倍实践(干货)

http://mp.weixin.qq.com/s/qOYg4wOK-ShIPYEHXcQK0Q
Elasticsearch大文件检索性能提升20倍实践(干货)

感谢群内各位大神的帮助!现将遇到问题、问题排查、问题定位、优化处理分享给大家。
欢迎拍砖!
http://mp.weixin.qq.com/s/qOYg4wOK-ShIPYEHXcQK0Q
Elasticsearch大文件检索性能提升20倍实践(干货)

感谢群内各位大神的帮助!现将遇到问题、问题排查、问题定位、优化处理分享给大家。
欢迎拍砖!

ES中可否控制字段输出的长度,只返回前300个字节的内容?


举例:content字段是一篇正文,很长。我这边只需要前300个字节。
我可以通过_source控制输出content,
有没有办法控制content,只返回前300个字节的内容。

返回完再裁剪,我知道通过程序处理。
想知道有没有参数控制,直接返回给定长度的串内容?
继续阅读 »

举例:content字段是一篇正文,很长。我这边只需要前300个字节。
我可以通过_source控制输出content,
有没有办法控制content,只返回前300个字节的内容。

返回完再裁剪,我知道通过程序处理。
想知道有没有参数控制,直接返回给定长度的串内容? 收起阅读 »

为何要避免往ES里写入稀疏数据

转几篇文章,让大家知晓,当前版本(<=5.x) 为何要避免将稀疏的数据写入ES。 随着ES/Lucene编码的改进,这个问题未来版本可能会得到改善,特别是ES6.0/Lucene7.0优化了doc_values对稀疏数据的编码方式。
 
https://www.elastic.co/guide/e ... rsity  


Avoid sparsityedit

The data-structures behind Lucene, which Elasticsearch relies on in order to index and store data, work best with dense data, ie. when all documents have the same fields. This is especially true for fields that have norms enabled (which is the case for text fields by default) or doc values enabled (which is the case for numerics, date, ip and keyword by default).

The reason is that Lucene internally identifies documents with so-called doc ids, which are integers between 0 and the total number of documents in the index. These doc ids are used for communication between the internal APIs of Lucene: for instance searching on a term with a matchquery produces an iterator of doc ids, and these doc ids are then used to retrieve the value of the norm in order to compute a score for these documents. The way this norm lookup is implemented currently is by reserving one byte for each document. The norm value for a given doc id can then be retrieved by reading the byte at index doc_id. While this is very efficient and helps Lucene quickly have access to the norm values of every document, this has the drawback that documents that do not have a value will also require one byte of storage.

In practice, this means that if an index has M documents, norms will require M bytes of storage per field, even for fields that only appear in a small fraction of the documents of the index. Although slightly more complex with doc values due to the fact that doc values have multiple ways that they can be encoded depending on the type of field and on the actual data that the field stores, the problem is very similar. In case you wonder: fielddata, which was used in Elasticsearch pre-2.0 before being replaced with doc values, also suffered from this issue, except that the impact was only on the memory footprint since fielddata was not explicitly materialized on disk.

Note that even though the most notable impact of sparsity is on storage requirements, it also has an impact on indexing speed and search speed since these bytes for documents that do not have a field still need to be written at index time and skipped over at search time.

It is totally fine to have a minority of sparse fields in an index. But beware that if sparsity becomes the rule rather than the exception, then the index will not be as efficient as it could be.

This section mostly focused on norms and doc values because those are the two features that are most affected by sparsity. Sparsity also affect the efficiency of the inverted index (used to index text/keyword fields) and dimensional points (used to index geo_point and numerics) but to a lesser extent.

Here are some recommendations that can help avoid sparsity:


https://www.elastic.co/blog/index-vs-type


Fields that exist in one type will also consume resources for documents of types where this field does not exist. This is a general issue with Lucene indices: they don’t like sparsity. Sparse postings lists can’t be compressed efficiently because of high deltas between consecutive matches. And the issue is even worse with doc values: for speed reasons, doc values often reserve a fixed amount of disk space for every document, so that values can be addressed efficiently. This means that if Lucene establishes that it needs one byte to store all value of a given numeric field, it will also consume one byte for documents that don’t have a value for this field. Future versions of Elasticsearch will have improvements in this area but I would still advise you to model your data in a way that will limit sparsity as much as possible.


https://www.elastic.co/blog/sp ... ucene
[url=https://issues.apache.org/jira/browse/LUCENE-6863]https://issues.apache.org/jira/browse/LUCENE-6863​[/url] 
https://www.elastic.co/blog/el ... eased

Screen_Shot_2017-09-15_at_11.29_.44_.png
继续阅读 »
转几篇文章,让大家知晓,当前版本(<=5.x) 为何要避免将稀疏的数据写入ES。 随着ES/Lucene编码的改进,这个问题未来版本可能会得到改善,特别是ES6.0/Lucene7.0优化了doc_values对稀疏数据的编码方式。
 
https://www.elastic.co/guide/e ... rsity  


Avoid sparsityedit

The data-structures behind Lucene, which Elasticsearch relies on in order to index and store data, work best with dense data, ie. when all documents have the same fields. This is especially true for fields that have norms enabled (which is the case for text fields by default) or doc values enabled (which is the case for numerics, date, ip and keyword by default).

The reason is that Lucene internally identifies documents with so-called doc ids, which are integers between 0 and the total number of documents in the index. These doc ids are used for communication between the internal APIs of Lucene: for instance searching on a term with a matchquery produces an iterator of doc ids, and these doc ids are then used to retrieve the value of the norm in order to compute a score for these documents. The way this norm lookup is implemented currently is by reserving one byte for each document. The norm value for a given doc id can then be retrieved by reading the byte at index doc_id. While this is very efficient and helps Lucene quickly have access to the norm values of every document, this has the drawback that documents that do not have a value will also require one byte of storage.

In practice, this means that if an index has M documents, norms will require M bytes of storage per field, even for fields that only appear in a small fraction of the documents of the index. Although slightly more complex with doc values due to the fact that doc values have multiple ways that they can be encoded depending on the type of field and on the actual data that the field stores, the problem is very similar. In case you wonder: fielddata, which was used in Elasticsearch pre-2.0 before being replaced with doc values, also suffered from this issue, except that the impact was only on the memory footprint since fielddata was not explicitly materialized on disk.

Note that even though the most notable impact of sparsity is on storage requirements, it also has an impact on indexing speed and search speed since these bytes for documents that do not have a field still need to be written at index time and skipped over at search time.

It is totally fine to have a minority of sparse fields in an index. But beware that if sparsity becomes the rule rather than the exception, then the index will not be as efficient as it could be.

This section mostly focused on norms and doc values because those are the two features that are most affected by sparsity. Sparsity also affect the efficiency of the inverted index (used to index text/keyword fields) and dimensional points (used to index geo_point and numerics) but to a lesser extent.

Here are some recommendations that can help avoid sparsity:


https://www.elastic.co/blog/index-vs-type


Fields that exist in one type will also consume resources for documents of types where this field does not exist. This is a general issue with Lucene indices: they don’t like sparsity. Sparse postings lists can’t be compressed efficiently because of high deltas between consecutive matches. And the issue is even worse with doc values: for speed reasons, doc values often reserve a fixed amount of disk space for every document, so that values can be addressed efficiently. This means that if Lucene establishes that it needs one byte to store all value of a given numeric field, it will also consume one byte for documents that don’t have a value for this field. Future versions of Elasticsearch will have improvements in this area but I would still advise you to model your data in a way that will limit sparsity as much as possible.


https://www.elastic.co/blog/sp ... ucene
[url=https://issues.apache.org/jira/browse/LUCENE-6863]https://issues.apache.org/jira/browse/LUCENE-6863​[/url] 
https://www.elastic.co/blog/el ... eased

Screen_Shot_2017-09-15_at_11.29_.44_.png
收起阅读 »