elasticSearch高性能集群一

第 1-1 课:如何规划新集群

当有一个新的业务准备使用 Elasticsearch,尤其是业务首次建设 Elasticsearch 集群时,往往不知道该如何规划集群大小,应该使用什么样的服务器?规划多少个节点才够用?

集群规模当然是越大越好,但是出于成本考虑,还是希望集群规模规划的尽量准确,能够满足业务需求,又有一些余量,不建议规划一个规模“刚刚好”的集群,因为当负载出现波动,或者一些其他偶然的故障时,会影响到业务的可用性,因此留一些余量出来是必要的。

1 规划数据节点规模

Elasticsearch 节点有多种角色,例如主节点,数据节点,协调节点等,默认情况下,每个节点都同时具有全部的角色。对于节点角色的规划我们将在下一个小节讨论,首先考虑一下我们需要多少个数据节点存储我们的数据。规划数据节点数量需要参考很多因素,有一些原则可以帮助我们根据业务情况进行规划。

1.1 数据总量

数据总量是指集群需要存储的数据总大小,如果每天都有新数据入库,总量随之相应地增加。我们之所以要考虑数据总量,并非因为磁盘空间容量的限制,而是 JVM 内存的限制

为了加快搜索速度,Lucene 需要将每个段的倒排索引都加载到 JVM 内存中,因此每一个 open 状态的索引都会在 JVM 中占据一部分常驻内存,这些是 GC 不掉的,而且这部分空间占用的比较大,并且由于堆内存不建议超过 32G,在磁盘使用率达到极限之前,JVM 占用量会先到达极限。

按照我们的经验,Elasticsearch 中 1TB 的 index 大约占用 2GB 的 JVM 内存,具体和字段数据类型及样本相关,有些会更多。一般情况下,我们可以按照这个比例来做规划。如果想要精确计算,业务可以根据自己的样本数据入库 1TB,然后查看分段所占的内存大小(实际上会比 REST 接口返回的值高一些)

curl -X GET "localhost:9200/_cat/nodes?v&h=name,segments.memory"

以 1TB 数据占用 2GB 内存,JVM 堆内存配置 31G ,垃圾回收器 CMS 为例,新生代建议配置 10G,old 区 21G,这 21G 内存分配一半给分段内存就已经很多了,想想看还没有做任何读写操作时 old 区就占用了一半,其他几个内存大户例如 bulk 缓冲,Query 缓存,indexing buffer,聚合计算等都可能会使用到 old 区内存,因此为了保证节点稳定,分段内存不超过 10G 比较好,换算成索引数据量为5TB。

因此,我们可以按照单个节点打开的索引数据总量不超过 5TB 来进行规划,如果预计入库 Elasticsearch 中的数据总量有 100TB 的数据(包括副分片所占用空间),那么数据节点的数量至少应该是: 100/5=20,此外出于冗余方面的考虑,还要多加一些数据节点。冗余节点的数量则和日增数据量以及故障转移能力相关

可以看出这样规划出来的节点数是相对比较多的,带来比较高的成本预算。在新的 6.7 及以上的版本 Elasticsearch 增加了冻结索引的特性,这是一种冷索引机制,平时他可以不占内存,只有查询的时候才去加载到内存,虽然查询慢一些,但是节点可以持有更多的数据总量。

因此,如果你想要节点存储更多的数据量,在超出上述原则后,除了删除或 close 索引之外,一个新的选择是将它变成冻结状态。

1.2 单个节点持有的最大分片数量

单个节点可以持有的最大分片数量并没有明确的界限,但是过多的分片数量会造成比较大的管理压力,官方给出的建议是,单个节点上所持有的分片数按 JVM 内存来计算:每 GB 的内存乘以 20。例如 JVM 内存为 30GB,那么分片数最大为:30*20=600个。当然分片数越少会越稳定。

但是使用这个参考值也会有些问题,当分片大小为 40GB 时(单一分片一般大小不超过50GB),节点所持有的数据量为:40 * 600 = 24TB,按照 1TB 数据量占用 2GB JVM 内存来计算,所占用 JVM 内存为:24 * 2 = 48GB,已经远超 JVM 大小,因此我们认为一般情况下不必将单个节点可以持有的分片数量作为一个参考依据,只需要关心一个原则:让 JVM 占用率和 GC 时间保持在一个合理的范围。

考虑另一个极端情况,每个分片的数据量都很小,同样不必关心每个节点可以持有多少,对大量分片的管理属于主节点的压力。一般情况下,建议整个集群的分片数量建议不超过 10 万

2 规划集群节点角色

一个 Elasticsearch 节点默认拥有所有的角色,分离节点角色可以让集群更加稳定,尤其是更加注重稳定性和可用性的在线业务上,分离节点角色是必要的。

2.1 使用独立的主节点

集群有唯一的活跃主节点,他负责分片管理和集群管理等操作,如果主节点同时作为数据节点的角色,当活跃主节点失效的时候,例如网络故障,硬件故障,新的主节点当选后需要重新分配原主节点上持有的分片数据,导致集群在一段时间内处于 RED 和 YELLOW 状态。而独立部署的主节点不需要这个过程,新节点当选后集群可以迅速 GREEN。

另外,由于数据节点通常有较大的内存占用,GC 的影响也会导致混合部署的工作受到影响。因此如果集群很在意稳定性和可用性,我们建议数据节点有 3 个及以上时,应该独立部署 3 个独立的主节点,共 6 个节点

node.master:false 
 
node.data:true 
 
node.ingest:false 
 
search.remote.connectfalse 
 

2.2 使用独立的协调节点

有时候,你无法预知客户端会发送什么样的查询请求过来,也许他会包括一个深度聚合,这种操作很容易导致节点 OOM,而数据节点离线,或者长时间 GC,都会对业务带来明显影响。

亦或者客户端需要进行许多占用内存很多的聚合操作,虽然不会导致节点 OOM,但也会导致节点 GC 压力较大,如果数据节点长时间 GC,查询延迟就会有明显抖动,影响查询体验。

此时最好的方式就是让客户端所有的请求都发到某些节点,这种节点不存储数据,也不作为主节点,即使 OOM 了也不会影响集群的稳定性,这就是仅查询节点(Coordinating only node)

node.master:false 
 
node.data:false 
 
node.ingest:false 
 
search.remote.connect:false 
 

禁用node.master角色(默认情况下启用)。

禁用node.data角色(默认情况下启用)。

禁用node.ingest角色(默认情况下启用)。

禁用跨群集搜索(默认情况下启用)。

 

2.3 使用独立的预处理节点

Elasticsearch 支持在将数据写入索引之前对数据进行预处理、内容富化等操作,这通过内部的 processor 和 pipeline 实现,如果你在使用这个特性,为了避免对数据节点的影响, 我们同样建议将他独立出来,让写请求发送到仅预处理节点

node.master:false 
 
node.data:false 
 
node.ingest:true 
 
search.remote.connectfalse 
 

独立节点的各个角色后的集群结构如下图所示,其中数据节点也是独立的。

如果没有使用预处理功能,可以将读写请求都发送到协调节点。另外数据写入过程最好先进入 Kafka 之类的 MQ,来缓冲一下对集群的写入压力,同时也便于对集群的后期维护。

总结

本文介绍了如何规划集群节点数和集群节点角色,依据这些原则进行规划可以较好的保证集群的稳定性,可以适用于组件新集群时评估集群规模,以及在现有集群接入新业务时对集群资源的评估。

关于索引分片的规划将在后续章节中介绍。

第 1-2 课:Elasticsearch 索引设计

Elasticsearch 开箱即用,上手十分容易。安装、启动、创建索引、索引数据、查询结果,整个过程,无需修改任何配置,无需了解 mapping,运作起来,一切都很容易。

这种容易是建立在 Elasticsearch 在幕后悄悄为你设置了很多默认值,但正是这种容易、这种默认的设置可能会给以后带来痛苦。

例如不但想对 field 做精确查询,还想对同一字段进行全文检索怎么办?shard 数不合理导致无法水平扩展怎么办?出现这些状况,大部分情况下需要通过修改默认的 mapping,然后 reindex 你的所有数据。

这是一个很重的操作,需要很多的资源。索引设计是否合理,会影响以后集群运行的效率和稳定性

1 分析业务

当我们决定引入 Elasticsearch 技术到业务中时,根据其本身的技术特点和应用的经验,梳理出需要预先明确的需求,包括物理需求、性能需求。

在初期应用时,由于对这两方面的需求比较模糊,导致后期性能和扩展性方面无法满足业务需求,浪费了很多资源进行调整。

希望我总结的需求方面的经验能给将要使用 Elasticsearch 的同学提供一些帮助,少走一些弯路。下面分别详细描述。

1.1 物理需求

根据我们的经验,在设计 Elasticsearch 索引之前,首先要合理地估算自己的物理需求,物理需求指数据本身的物理特性,包括如下几方面。

  • 数据总量

业务所涉及的领域对象预期有多少条记录,对 Elasticsearch 来说就是有多少 documents 需要索引到集群中。

  • 单条数据大小

每条数据的各个属性的物理大小是多少,比如 1k 还是 10k。

  • 长文本

明确数据集中是否有长文本,明确长文本是否需要检索,是否可以启用压缩。Elasticsearch 建索引的过程是极其消耗 CPU 的,尤其对长文本更是如此。

明确了长文本的用途并合理地进行相关设置可以提高 CPU、磁盘、内存利用率。我们曾遇见过不合理的长文本处理方式导致的问题,此处在 mapping 设计时会专门讨论

  • 物理总大小

根据上面估算的数据总量和单条数据大小,就可以估算出预期的存储空间大小。

  • 数据增量方式

这里主要明确数据是以何种方式纳入 Elasticsearch 的管理,比如平稳增加、定期全量索引、周期性批量导入。针对不同的数据增量方式,结合 Elasticsearch 提供的灵活设置,可以最大化地提高系统的性能。

  • 数据生命周期

数据生命周期指进入到系统的数据保留周期,是永久保留、还是随着时间推移进行老化处理?老化的周期是多久?既有数据是否会更新?更新率是多少?根据不同的生命周期,合理地组织索引,会达到更好的性能和资源利用率。

1.2 性能需求

使用任何一种技术,都要确保性能能够满足业务的需求,根据上面提到的业务场景,对于 Elasticssearch 来说,核心的两个性能指标就是索引性能和查询性能

索引性能需求

Elasticsearch 索引过程需要对待索引数据进行文本分析,之后建立倒排索引,是个十分消耗 CPU 资源的过程。

对于索引性能来说,我们认为需要明确两个指标,一个是吞吐量,即单位时间内索引的数据记录数;另一个关键的指标是延时,即索引完的数据多久能够被检索到。

Elasticsearch 在索引过程中,数据是先写入 buffer(数据内存缓冲区) 的,需要 refresh 操作后才能被检索到,所以从数据被索引到能被检索到之间有一个延迟时间,这个时间是可配置的,默认值是 1s。这两个指标互相影响:减少延迟,会降低索引的吞吐量;反之会增加索引的吞吐量。

查询性能需求

数据索引存储后的最终目的是查询,对于查询性能需求。Elasticsearch 支持几种类型的查询,包括:

  • 1. 结构化查询

结构查询主要是回答 yes/no,结构化查询不会对结果进行相关性排序。如 terms 查询、bool 查询、range 查询等。

  • 2. 全文检索

全文检索查询主要回答数据与查询的相关程度。如 match 查询、query_string 查询。

  • 3. 聚合查询

无论结构化查询和全文检索查询,目的都是找到某些满足条件的结果,聚合查询则不然,主要是对满足条件的查询结果进行统计分析,例如平均年龄是多少、两个 IP 之间的通信情况是什么样的。

对不同的查询来说,底层的查询过程和对资源的消耗是不同的,我们建议根据不同的查询设定不同的性能需求

2 索引设计

此处索引设计指宏观方面的索引组织方式,即怎样把数据组织到不同的索引,需要以什么粒度建立索引,不涉及如何设计索引的 mapping。(mapping 后文单独讲)

2.1 按照时间周期组织索引

如果查询中有大量的关于时间范围的查询,分析下自己的查询时间周期,尽量按照周期(小时、日、周、月)去组织索引,一般的日志系统和监控系统都符合此场景。

按照日期组织索引,不但可以减少查询时参与的 shard 数量,而且对于按照周期的数据老化备份删除的处理也很方便,基本上相当于文件级的操作性能。

这里有必要提一下 delete_by_query这种数据老化方式性能慢,而且执行后,底层并不一定会释放磁盘空间,后期 merge 也会有很大的性能损耗,对正常业务影响巨大。

2.2 拆分索引

检查查询语句的 filter 情况,如果业务上有大量的查询是基于一个字段 filter,比如 protocol,而该字段的值是有限的几个值,比如 HTTP、DNS、TCP、UDP 等,最好把这个索引拆成多个索引。

这样每次查询语句中就可以去掉 filter 条件,只针对相对较小的索引,查询性能会有很大提高。同时,如果需要查询跨协议的数据,也可以在查询中指定多个索引来实现。

2.3 使用 routing

如果查询语句中有比较固定的 filter 字段,但是该字段的值又不是固定的,我们建议在创建索引时,启用 routing 功能。这样,数据就可以按照 filter 字段的值分布到集群中不同的 shard,使参与到查询中的 shard 数减少很多,极大提高 CPU 的利用率。

2.4 给索引设置别名

我们强烈建议在任何业务中都使用别名,绝不在业务中直接引用具体索引

别名是什么

索引别名就像一个快捷方式,可以指向一个或者多个索引,我个人更愿意把别名理解成一个逻辑名称。

别名的好处

  • 方便扩展

对于无法预估集群规模的场景,在初期可以创建单个分片的索引 index-1,用别名 alias 指向该索引,随着业务的发展,单个分片的性能无法满足业务的需求,可以很容易地创建一个两个分片的索引 index-2,在不停业务的情况下,用 alise 指向 index-2,扩展简单至极。

  • 修改 mapping

业务中难免会出现需要修改索引 mapping 的情况,修改 mapping 后历史数据只能进行 reindex 到不同名称的索引,如果业务直接使用具体索引,则不得不在 reindex 完成后修改业务索引的配置,并重启服务。业务端只使用别名,就可以在线无缝将 alias 切换到新的索引。

2.5 使用 Rollover index API 管理索引生命周期

对于像日志等滚动生成索引的数据,业务经常以天为单位创建和删除索引。在早期的版本中,由业务层自己管理索引的生命周期。

在 Rollover index API 出现之后,我们可以更方便更准确地进行管理:索引的创建和删除操作在 Elasticsearch 内部实现,业务层先定义好模板和别名,再定期调用一下 API 即可自动完成,索引的切分可以按时间、或者 DOC 数量来进行。

总结

正式接入业务数据之前进行合理的索引设计是一个必要的环节,如果偷懒图方便用最简单的方式进行业务数据接入,问题就会在后期暴露出来,那时再想解决就困难许多。

下一节我们开始介绍索引层面之下的分片设计

第 1-3 课:Elasticsearch 分片设计

Elasticsearch 的一个分片对应 Lucene 的一个索引,Elasticsearch 的核心就是将这些 Lucene 索引分布式化,提供索引和检索服务。可见,如何设计分片是至关重要的。

一个索引到底该设置几个主分片呢?由於单个分片只能处于 Elasticsearch 集群中的单个节点,分片太少,影响索引入库的并发度,以及以后的横向扩展性,如果分片过大会引发查询、更新、迁移、恢复、平衡等性能问题

1 主分片数量确定

我们建议综合考虑分片物理大小因素、查询压力因素、索引压力因素,来设计分片数量。

1.1 物理大小因素

建议单个分片的物理大小不大于 50GB(30G~50G都是可以的,而一台节点上的分配的合理索引总大小不超过5TB,也就是能存储分片102~170个分片,集群分片一般不超过10万个),之所以这样建议,基于如下几个因素:

  • 更快的恢复速度

集群故障后,更小的分片相对大分片来讲,更容易使集群恢复到 Green 状态。

  • merge 过程中需要的资源更少

Lucene 的 segment merge 过程需要两倍的磁盘空间,如果分片过大,势必需要更大的临时磁盘空间用于 merge,同时,分片过大 merge 过程持续时间更长,将对 IO 产生持续的压力。

  • 集群分片分布更容易均衡

分片过大,Elasticsearch 内部的平衡机制需要更多的时间。

  • 提高 update 操作的性能

对 Elasticsearch 索引进行 update 操作,底层 Lucene 采用的是先查找,再删除,最后 index 的过程。如果在分片比较大的索引上有比较多的 update 操作,将会对性能产生很大的影响。

  • 影响缓存

节点的物理内存是有限的,如果分片过大,节点不能缓存分片必要的数据,对一些数据的访问将从物理磁盘加载,可想而知,对性能会产生多大的影响。

1.2 查询压力因素

单个 shard 位于一个节点,如果索引只有一个 shard,则只有一个节点执行查询操作。如果有多个 shard 分部在不同的节点,多个节点可以并行执行,最后归并。

但是过多的分片会增加归并的执行时间,所以考虑这个因素,需要根据业务的数据特点,以贴近真实业务的查询去测试,不断加大分片数量,直到查询性能开始降低。

1.3 索引压力因素

单个 shard 只能位于一块单个节点上,索引过程是 CPU 密集型操作,单个节点的入库性能是有限的,所以需要把入库的压力分散到多个节点来满足写入性能。单纯考虑索引性能,可以根据单个节点的索引性能和需要索引的总性能来估算分片数量。

2 副本数量

副本是主分片的拷贝,可以响应查询请求、防止数据丢失、提高集群可用性等,但是副本不是“免费”的,需要占用与主分片一样的资源,包括 CPU、内存、磁盘,副本数量的确定等涉及多方面的因素。

PUT fieldpower-2017/_settings

{

      "index.number_of_replicas":"1" //修改副本的数量,可以动态的修改

}

2.1 数据可靠性

明确自己的业务需要多高的可靠性和可用性。依据 Elasticsearch 的内部分片分布规则,同一索引相同编号的分片不会处于同一个 node,多一份副本就多一份数据安全性保障。

2.2 索引性能

副本和主分片在索引过程中执行和主分片一样的操作,如果副本过多,有多少副本就会有几倍的 CPU 资源消耗在索引上,会拖累整个集群的索引吞吐量,对于索引密集型的业务场景影响巨大。所以要在数据安全型和索引性能上做权衡处理来确定副本的数量。

2.3 查询性能

副本可以减轻对主分片的查询压力,这里可能说查询次数更为合理。节点加载副本以提供查询服务和加载主分片消耗的内存资源是完全相同的,增加副本的数量势必增加每个 node 所管理的分片数,因此会消耗更多的内存资源,Elasticsearch 的高速运行严重依赖于操作系统的 Cache。

如果节点本身内存不充足,副本数量的增加会导致节点对内存的需求的增加,从而降低 Lucene 索引文件的缓存效率,使 OS 产生大量的换页,最终影响到查询性能。当然,在资源充足的情况下,扩大副本数是肯定可以提高集群整体的 QPS。

3 分片分布

ELasticsearch 提供的关于 shard 平衡的两个参数是 cluster.routing.allocation.balance.shard 

 cluster.routing.allocation.balance.index

第一个参数的意思是让每个节点维护的分片总数尽量平衡,第二个参数的意思是让每个索引的的分片尽量平均的分散到不同的节点。

如果集群中有不同类型的索引,而且每个类型的索引的索引方式、物理大小不一致,很容易造成节点间磁盘占用不均衡、不同节点间堆内存占用差异大的问题,从而导致集群不稳定。

所以我们建议尽量保证不同索引的 shard 大小尽量相近,以获得实质意义上的均衡分片分布

4 集群分片总数控制

由于 Elasticsearch 的集群管理方式还是中心化的,分片元信息的维护在选举出来的 master 节点上,分片过多会增加查询结果合并的时间,同时增加集群管理的负担。

根据我们的经验,单个集群的分片超过 10 万时,集群维护相关操作例如创建索引、删除索引等就会出现缓慢的情况。所以在实践中,尽量控制单个集群的分片总数在 10 万以内

5 【案例分析】大分片数据更新引发的 IO 100% 异常

分片大小对某些业务类型来讲会产生致命的影响,这里介绍一个我们遇到的一个案例,由于分片不合理导致了很严重的性能问题。

5.1 问题背景

有一个集群由很多业务部门公用,该集群为 10 个节点,单节点 128G 内存,CPU 为 40 线程,硬盘为 4T*12 的配置,存储使用总量在 20%。业务 A 反应他的 bulk 操作很慢,需要分钟级才能完成。

经过与业务沟通后,了解到他们单次 bulk 1000 条数据,单条数据大小为 1k,这种 bulk 并不大,因此速度慢肯定是不正常的现象。

5.2 问题分析

有了以上的背景信息,开始问题排查。该索引大小为 1.2T,5 个主分片(一个分片的大小为200多G)。登录到集群服务器后台,在业务运行过程中,通过 top 查看,CPU 利用率在 30%,在正常范围内。但是 iowait 一直持续在 20% 左右,问题初步原因应该在 IO 上。

随即通过 iostat 观察磁盘的状况,发现有一块盘的 IO 持续 100%。登录其他几个服务器,发现每个服务器都有一块盘的 IO 处于 100% 状态。

之后通过分析该索引的分布情况,发现 IO 利用率高的磁盘都有这个索引对应的 shard,难道是这个索引导致的?

因为这些磁盘上还有其他的索引,我们现在也只是推测。打开 iotop,发现有些线程的 io 持续有大量读取。

将线程 tid 转换成 16 进制,通过 jtack 查询对应的线程,发现是 Lucene 的 refresh 操作触发的。但是只通过线程堆栈扔无法确认是由该索引的 bulk 操作引起。之后通过跟踪系统调用:

strace -t -T -y -p $tid

发现每秒有数百次的 pread 系统调用,而且读取的目录全部为该索引所在目录,使得磁盘 IO 一直处于满负荷状态。bulk 是写操作,不会引起大量的读。

有一种情况是,如果待索引的数据的 id 是应用程序自己生成的,底层 Lucene 在索引时,要去查找对应的文档是否存在。

跟业务人员沟通后,id 确实是由应用程序自己控制。这样问题就清晰了,在这个索引的单个分片上 bulk 200 条数据,底层 Lucene 要进行 200 次查找以确定对应的数据是否存在。

5.3 问题解决

通过以上分析,在目前的情况下,紧急缩小单个分片的物理大小,增加该索引的 shard 数到 200(将每个分片的大小变为50G左右),使数据均衡到更多的磁盘,对旧索引的数据进行 reindex。迁移完成后,同样的 bulk 操作在 1s 左右执行完成。

总结

没有不好的技术,只有不合理的使用。通过减少单个分片的物理大小,将数据分散到更多的 shard,从而将 IO 压力分散到了集群内的所有磁盘上,暂时可以解决目前 bulk 慢的问题。

但是考虑到 Lucene 的技术特点,并不适用于有大量更新的业务场景,还是需要重构业务,以追加的方式写入新数据,通过数据的版本或者时间戳来查询最新的数据,并定期对数据进行整理,以达到最优的性能。

第 1-4 课:如何设计映射

1 映射 (mapping) 基础

mapping 定义了文档的各个字段如何被索引以及如何存储。我们可以把 Elasticsearch 的 mapping 看做 RDBMS 的 schema。

虽然 Elasticsearch 可以根据索引的数据动态的生成 mapping,我们仍然建议在创建索引时明确的定义自己的 mapping,不合理的 mapping 会引发索引和查询性能降低,磁盘占用空间变大。错误的 mapping 会导致与预期不符的查询结果。

2 选择合适的数据类型

2.1 分清 text 和 keyword

text 类型

  • 用于存储全文搜索数据,例如:邮箱内容、地址、代码块、博客文章内容等。
  • 默认结合 standard analyzer(标准解析器)对文本进行分词、倒排索引。
  • 默认结合标准分析器进行词命中、词频相关度打分。

keyword 类型

  • 用于存储需要精确匹配的数据。例如手机号码、主机名、状态码、邮政编码、标签、年龄、性别等数据。
  • 用于筛选数据(如 select * from x where status=‘open’)、排序、聚合(统计)。
  • 直接将完整的文本保存到倒排索引中,并不会对字段的数据进行分词。

如果 keyword 能满足需求,尽量使用 keyword 类型。

3 mapping 和 indexing

mapping 定义得是否合理,将直接影响 indexing 性能,也会影响磁盘空间的使用。

3.1 mapping 无法修改

Ealsticsearch 的 mapping 一旦创建,只能增加字段,不能修改已有字段的类型

3.2 几个重要的 meta field

1. _all

虽然在 Elasticsearch 6.x 中,_all 已经是 deprecated,但是考虑到 6.x 之前的版本创建的索引 _all 字段是默认启用的,这里有必要详细说说明下该字段的含义。

_all 字段是一个 text 字段,它将你索引的单个文档的所有字段连接成一个超级串,之后进行分词、索引。如果你不指定字段,query_string 查询和 simple_query_string 查询默认查询 _all 字段

_all 字段不是“免费”的,索引过程会占用额外的 CPU 资源,根据测试,在我们的数据集上,禁用 _all 字段后,索引性能可以提高 30%+!!,所以,如果您在没有明确 _all 含义的情况下,历史索引没有禁用 _all 字段,建议您重新审视该字段,看是否需要禁用,并 reindex,以获取更高的索引性能以及占用更少的磁盘空间。如果 _all 提供的功能对于您的业务必不可少,考虑使用 copy_to 参数代替 _all 字段

2. _source

_source 字段存储了你的原始 JSON 文档 _source 并不会被索引,也就是说你并不能直接查询 _source 字段,它主要用于查询结果展示。所以启用该字段并不会对索引性能造成很大的影响。

除了用于查询结果展示,Ealsticsearch 的很多功能诸如 Reindex API、高亮、update_by_query 都依赖该字段。

所以实践中,我们不建议禁用该字段。如果磁盘空间是瓶颈,我们建议优先考虑禁用 _all 字段,_all 禁用达不到预期后,考虑提高索引的压缩级别,并合理使用 _source 字段的 includes 和 excludes 属性。

3.3 几个重要的 mapping 参数

1. index 和 store

首先明确一下,index 属性关乎该字段是否可以用于查询,而 store 属性关乎该字段是否可以在查询结果中单独展示出来通过跟一些业务开发人员接触,发现经常有对这两个属性不明确的情况。

index 后的字段可以用于 search默认你定义到 mapping 里的字段该属性的值都为 “true”indexing 过程耗费 cpu 资源,不需要查询的字段请将该属性值设为 “false”在业务部门使用过程中,常见的一个问题是一半以上的字段不会用于查询,但是并没有明确设置该属性,导致索引性能下降。

由于 Elasticsearch 默认提供了 _source 字段,所以,大部分情况下,你无须关心 store 属性,默认的 “false” 能满足需求

enabled

对于不需要查询,也不需要单独 fetch 的字段,enable 可以简化 mapping 的定义。

例如:

"session_data": { 
    "enabled": false
}

等同于

"session_data": { 
    "type": "text",
    "index": false,
    "store": false
}

不要使用 fielddata

自从 Elasticsearch 加入 doc_value 特性后,fielddata 已经没有使用的必要了。

有两点原因,首先,对于非 text 字段,doc_value 可以实现同样的功能,但是占用的内存资源更少。

其次,对于 text 字段启用 fielddata,由于 text 字段会被分词,即使启用了 fielddata,在其上进行聚合、排序通常是没有意义的,得到的结果也并不是期望的结果。如果确实需要对 text 字段进行聚合,通常使用 fields 会得到你想要的结果。

PUT my_index
{
    "mappings": {
    "my_type": {
            "properties": {
                "city": {
                    "type": "text",
                    "fields": {
                        "raw": { 
                            "type""keyword"
                    }
                    }
                }
            }
        }
    }
}

之后通过 city 字段进行全文查询,通过 city.raw 字段进行聚合和排序。

2. doc_values

Elasticsearch 默认对所有支持 doc_values 的类型启用了该功能,对于不需要聚合、排序的字段,建议禁用以节省磁盘空间。

PUT my_index
{
  "mappings": {
    "_doc": {
      "properties": {
        "session_id": { 
          "type": "keyword",
          "doc_values": false
        }
      }
    }
  }
}

3.4 null_value

null 不能索引,也不能用于检索。null_value 可以让你明确的指定 null 用什么值代替,之后可以用该替代的值进行检索。

4 mapping 和 searching

4.1 预热 global ordinals

global ordinals(预热) 主要用于加快 keyword 类型的 terms 聚合,由于 global ordinals 占用内存空间比较大,Elasticsearch 并不知道要对哪些字段进行聚合,所以默认情况下,Elasticsearch 不会加载所有字段的 global ordinals。可以通过修改 mapping 进行预加载。如下所示:

PUT index
{
  "mappings": {
    "type": {
      "properties": {
        "foo": {
          "type": "keyword",
          "eager_global_ordinals": true
        }
      }
    }
  }
}

4.2 将数字标识映射成 keyword 类型

Elasticsearch 在索引过程中,numbers 类型对 range 查询进行了优化,keyword 类型对 terms 查询进行了优化,如果字段字面上看是 numbers 类型,但是并不会用于 range 查询,只用于 terms 查询,将字段映射成 keyword 类型。例如 isbn、邮政编码、数据库主键等。

总结

mapping 定义的是否合理,关系到索引性能、磁盘使用效率、查询性能,本章着重讨论了影响这些的关键 mapping 参数和 Elasticsearch 的 meta fileds,深入理解本章的内容后,mapping 应该不会成为系统瓶颈的主因。

第一部分的内容到此结束,这部分内容的目的介绍如何“合理地”使用 Elasticsearch,只要使用方式合理,Elasticsearch 本身问题并不多。很多时候遇到的问题都是初期的规划,使用不当造成的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章