Elasticsearch的性能优化

时间:2023-01-27 20:13:36

 Elasticsearch的默认配置项是比较全面的,在不做太多配置的情况下可以使用es的全文检索,高亮显示,聚合,和数据的索引。但是在比较了解es的情况下,可以对很对配置进行优化。

一、一般建议

 1、不要返回太大的结果集

   Es的本质是搜索引擎,所以它的工作机制是查询文件的匹配度,而不是像数据库那些的完全匹配,若需要使用类似于此的查询方式,请使用Scroll API。


 2 、避免过大的文档

   Es索引的单个文档默认最大值为100M,但是可以使用http.max_context_length进行调整,只是es底层的lucene的最大限制为2G。即使不考虑es或lucene的限制,但是大文档的索引查询等代价都是比较大的,可以考虑使用分解的方式进行索引(比如不需要讲一整本书的内容进行索引,而是将其拆分为章节索引)。


 3 、尽量避免稀疏字段(出现次数少)

   Es底层的lucene数据结构最适合于密集的数据(即所有文档都具有相同的字段,反之称为稀疏字段),特别是默认的text类型和doc value的numerics(数字)、date、ip、keyword类型。原因在于lucene内部为每一个document都会创建一个整数的doc id,例如使用match进行查询时会产生迭代器,并使用该id检索并计算文档的得分。即若某一索引中有n条数据,则即使某一字段出现的次数非常少也会创建n个(若为空,默认需要预留1个字节)的存储空间。虽然稀疏的字段对于存储影响是最大的,但是也会影响到索引和查询的速度,因素索引时也会有写操作,查询时候需要跳过。并且对于norms 和doc values的影响最大。基于该原因,应该从以下几点尽量避免:


   1)、避免将不相关的数据放在同一个索引中

   综上所述,尽量不要把数据结构不同的数据放到同一索引中,并且可以考虑将数据量较少的集合创建较少的分片存储。当然若数据之间存在父子关系则例外,毕竟父子关系的数据不能存储在不同的索引当中。


   2)、规范化文档结构

 即使由于各种原因需要将不同的数据存储到同一索引中,也可以采用某些手段减少稀疏字段的可能。比如同一Index的两个type中都包含日期字段而名称各不相同,那么完全可以将名称进行统一。


   3)、避免同一索引中的Type的字段相似度较低

 尽管同一索引中对于type的字段相似度没有要求,但是由于以上原因,尽量避免将字段相似度(或者说映射相似度)较低的Type放到同一索引中。


   4)、在稀疏的字段中禁用norms 和doc_values

   在避免了以上的情况后,需要考虑,若字段不需要用分数计算匹配的程度,而仅仅是使用filter(filter本身还可以进行缓存)查询是否匹配,则可以禁用稀疏字段(或非稀疏字段,但是毕竟对稀疏字段的影响最大)。若不需要进行排序或聚合的字段可以禁用doc_value。但是需要注意的是若需要取消禁用则只能重新创建映射和索引数据。


二、索引速度

 1、尽量使用批量索引

   批量索引的效率会比单个索引高很多(这一点试一下就知道很显著),知道批量索引效果显著,那多少是效率最高的呢。可以从200开始翻倍的往上叠加,以存在最佳数据。


 2、客户端尽量使用多线程批量索引

   使用单线程的批量索引并不能最大限度的使用es的资源,所有最好使用多线程或多进程(内的单个或多个线程)索引数据。而使用多少线程数合适及每个线程内的buik的数量也需要测试,递增测试知道IO或者CPU饱和。而当java api报EsRejectedExecutionException错TOO_MANY_REQUESTS (429)时,说明需要重试并且需要指数级的递减。


 3、增加刷新机制的间隔

   Es从数据索引到能不查询整个过程默认为1s,使用index.refresh_interval参数控制。若对数据的实时性要求不高的话,可适当调整该参数到业务系统可接受的范围。在该间隔时间内es会强制创建一个新的segment(段),时间间隔越大则创建的段也会越大,也减小了后续字段合并段的压力(段其实的lucene底层的数据结构,详细可查询lucene与segment的关系)。


 4、数据初始索引时禁用刷新和副本机制

   若有一大批数据需要索引的时候(前提条件),由于刷新和副本机制对数据索引性能影响较大,可以将index.refresh_interval设置为-1,将index.number_of_replicas设置为0以禁用该两机制。直到本次数据全部索引完成后再将这两个参数调整至合理的值,然而应该明白性能与数据安全总是不能同时得到满足,完全看业务数据的重要性。


 5、禁止内存交换

   什么是内存交换,怎样禁止(或设置一个较低的值),可以参见生产环境配置一节。这对于es来说非常重要,必须完全禁用。


 6、文件系统缓存的内存不能低于服务器的一半

   文件系统缓存将被用来缓冲输入/输出操作,这对于es来说非常的重要,要求内存不能少于服务器内存的一半,即es的jvm heap的值设置应该小于服务器内存的一半。


 7、尽量使用es自动生成的id

   作为es的document id应该知道其作用第一是确定文档的唯一性,第二默认情况下使用id作为route值计算文档应该被分配到哪个shard,同时也是生成uid的成员。若索引时制定id的情况下,会先检测其唯一性,其代价是比较大的,并且随着索引文档数的增加消耗会越来越大,若没有业务需要最好使用自动生成的id(会跳过检查过程)。


 8、磁盘选择

   在保证文件系统缓存需要的内存外持久化(存储)的硬件选择也是很重要的,若本地存储的话可以选择ssd替换普通的磁盘。并且尽量避免使用远程文件系统存储,如NFS 或 SMB(当然一般会选择本地的ssd存储)。若可以活需要选择多个ssd进行存储的情况下,需要知道其个数越多则不可用的风险越大,需要做好es的灾备和恢复机制。


 9、索引缓存的大小

   当jvm.options通过-Xms4g -Xmx4g为es设置heap(堆)内存后,默认情况下es会为所有活跃的节点配置堆内存的10%分配给索引缓存。但是在重索引的情况下每个分片的索引缓存应该不小于512M,可以使用indices.memory.index_buffer_size进行设置。所以一定要清楚自己的节点数据,设置的节点堆内存大小,以及节点的分片数。


 10、其他控制项

   除了以上的因素,很多为磁盘设置的配置项对于数据的索引也有影响,了解更多需要参见下面的磁盘使用。


三、查询速度

 1、文件系统缓存的内存

   文件系统缓存的内存设置要求不能少于服务器内存的一半,这不仅影响索引的效率,同时也会影响存放在内存中的热数据,直接影响到查询的速度。


 2、硬件配置

   查询从消耗上来说可分为io型和Cpu型,若io型查询与磁盘是否SSD或者是否远程文件系统有关,而cpu型查询则可以适当增加其配置。


 3、文档建模

   适当的情况下应该为文档建模,以提高查询。并且应该尽量避免nested(嵌套)的join查询,会比正常查询慢几倍。而使用父子关系则会慢数百倍。


 4、根据查询索引数据结构

   应该尽量根据查询预判对数据结构进行合理的索引。比如需要根据某一只值按照分段统计(比如价格分为几个段统计)则完全可以在索引合适的数据结构。比如我们项目中会按照用户行为数据发生小时进行统计,可以基于Script使用es时间字段的doc['orderTime'].date.hourOfDay进行处理,当然最好在索引时单独索引一个字段。


 5、Mapping(映射)

   并不是索引的数组都应该被映射为es的numeric类型,而应该根据情况决定。这就应该考虑mapping中是否开启"numeric_detection": true配置。


 6、尽量不要使用Script(脚本)

   一般情况下尽量不要使用Script,若需要则尽量使用painless或expressions语言。


 7、关于date类型查询

   Es的date类型由于查询时间的不确定性,一般情况下是不会进行缓存的,但是若在需求运行的范围内可以将查询的时间设置为整数的日志,则可以进行缓存操作,如下:


"range": {

    "my_date": {

        "gte": "now-1h",

        "lte": "now"

    }

}

可优化为:


"range": {

   "my_date": {

       "gte": "now-1h/m",

       "lte": "now/m"

   }

}

 8、将只读的索引进行强制合并

   将所有只读的索引强制合并到一个统一的megment(段)的索引中。若一个索引是按照时间进行添加,则只有最近时间段的数据需要进行修改,之前的历史数据一般为只读状态可以将移动到一个只读管理的索引中。


 9、global ordinals(全局顺序)的设置

   全局序数是一种数据结构,用于keyword字段terms聚合。但是由于es不清晰那些需要进行聚合操作,所以会将所以的字段加载进内存中(有些字段永远也不需要进行terms聚合),所以可以在mapping设置时通过eager_global_ordinals属性告知es,mapping如下:


PUT index

{

 "mappings": {

   "type": {

     "properties": {

       "foo": {

         "type": "keyword",

         "eager_global_ordinals": true

       }

     }

   }

 }

}

 10、预加载文件系统缓存

   若对es进行重启操作(前提条件),则文件系统缓存中将是空的。而操作系统将索引中是热区域数据加载到内存需要一定的时间,则可以使用index.store.preload显示的告知es需要预加载的内容。


四、磁盘使用

 1、禁用某些不需要使用的es特性

   一般情况下es索引的同时会将数据添加到doc values中,以便进行查询和聚合。但是若某一numeric字段只需要用于histograms而不需要使用filter进行操作,则可以在mapping时候设置如下:


PUT index

{

 "mappings": {

   "type": {

     "properties": {

       "foo": {

         "type": "integer",

         "index": false

       }

     }

   }

 }

}

   默认清下es会将text类型的字段存储norms 操作,以便使用得分匹配查询的程度。但是若后续只需要对其进行是否匹配的操作而不需要使用得分则可以进行如下mapping设置:


PUT index

{

 "mappings": {

   "type": {

     "properties": {

       "foo": {

         "type": "text",

         "norms": false

       }

     }

   }

 }

}

   并且text类型字段还会存储frequencies和positions ,以便可以进行phrase (短语)查询操作,若不需要进行phrase 查询操作可以进行以下mapping操作:


PUT index

{

 "mappings": {

   "type": {

     "properties": {

       "foo": {

         "type": "text",

         "index_options": "freqs"

       }

     }

   }

 }

}

   若确定只需要进行是否匹配的查询操作而不会关心score(即不需要查询的匹配程度),则可以进行如下mapping操作(可以进行查询操作但是使用phrase查询操作会报错,score只能设置一次):


PUT index

{

 "mappings": {

   "type": {

     "properties": {

       "foo": {

         "type": "text",

         "norms": false,

         "index_options": "freqs"

       }

     }

   }

 }

}

 2、关闭String类型的动态映射

   String类型的字段使用动态映射会创建text和keyword两种类型的字段(具体查询两种字段类型的作用),而在很多情况下这是浪费的,则需要使用动态映射模板进行映射,或直接对确认的字段进行mapping设置。如下模板只允许String类型动态设置为keyword:


PUT index

{

 "mappings": {

   "type": {

     "dynamic_templates": [

       {

         "strings": {

           "match_mapping_type": "string",

           "mapping": {

             "type": "keyword"

           }

         }

       }

     ]

   }

 }

}


 3、禁用_all字段

   默认情况下es会将所有的字段的值全部映射到一个叫_all的字段中,若不需要对所有字段同时进行查询或全文检索,则完全可以禁用该字段。


 4、使用best_compression压缩

   默认情况es对数据进行_source的存储操作,并且一般情况下这是有必要的,具体需要很清楚其作用。但是我们可以使用best_compression对数据进行压缩,在方便功能使用的同时尽量少的占用磁盘空间。


 5、使用合适的numeric类型

   整数和小数类型在使用的使用尽量使用合适的字段类型以节省存储空间。整数类型可以使用byte, short, integer 或long;小数类型可以使用float优于double, half_float优于float。