ES 的默认配置已经提供了良好的开箱即用的体验,但是仍有一些优化手段去继续提升它的使用性能。
一
General recommendations
通用建议。
01
Don't return large result sets
不要返回大量的结果集。ES 是一个搜索引擎,擅长于返回匹配度较高的几个文档(默认 10 个,取决于 size 参数),而不擅长于数据库领域的工作,例如返回一个查询条件匹配的所有文档,如果你一定要实现这个功能,建议使用 scroll API。
这个问题其实是与深度分页相关联的,ES 中的配置项 index.max_result_window 默认是 10000 ,这就是说最多只支持返回前一万条数据,如果想返回更多的数据,一方面可以增大此配置项,另一方面就是使用 scroll API ,scroll API 的原理就是记录上一次的结果标记,基于此标记再继续往下查询。
02
Avoid large documents
避免大文档。配置项 http.max_content_length 默认是 100 MB,ES 将会拒绝索引超过此大小的文档,你也可以提高这项配置,但是最大不得超过 2 GB,因为 Lucene 的限制为 2 GB。
大文档会给网络、内存、磁盘、文件系统缓存等带来更大的压力。
为了解决这个问题,我们需要重新考虑信息的基本单元,例如想要去索引一本书的内容,这并不意味着我们要把整本书都塞进一个文档中去,按照章节或者段落去划分文档显然是更好的选择。
二
Recipes
解决一些常见问题的方式。
01
Mixing exact search with stemming
精确搜索混合词干搜索。
在英文场景下,词干搜索如 skiing 将会匹配包含有 ski 或 skis 的文档,但是如果用户想要实现 skiing 的精确匹配呢?最典型的解决方法就是将同样的内容索引为 multi-field 多个不同的字段,这样就能在不同的字段上分别使用词干搜索和精确搜索了。
除此之外,query_string 和 simple_query_string 的 quote_field_suffix 也可以解决这种问题。
02
Getting consistent scoring
1、Scores are not reproducible
即使同样的查询同时执行两次,文档的匹配分数也并不一致。这是因为副本存在的原因,副本的配置项是 index.number_of_replicas ,ES 进行查询时会以 round-robin 的方式轮询到不同的 shard 分片,而删除或更新文档时(在 ES 中,更新分为两步,第一步标记旧文档为删除,第二步写入新文档),旧文档并不会立刻被删除,而是等待下一个 refresh 周期此文档从属的 segment (shard 分片会被分割为多个 segment)被合并,有时候主分片刚刚完成合并操作并移除了大量标记为删除的文档,而从分片还未来得及同步此项操作,这就导致了主从索引统计信息的不同,也就影响到了匹配分数的不同。
解决方法是在查询时使用 preference 参数,此参数决定了将查询路由到哪个分片中去执行,只要 preference 一致则一定会使用相同的分片。例如你可以使用用户ID 或者 session id 作为 preference ,这样就能保证同一个用户或者同一个会话查询的一致性。
2、Relevancy looks wrong
如果你注意到两个相同内容文档的分数不同或者精确匹配的未排序在第一位,这也可能与分片有关。默认情况下,每个分片各自评分,文档也会被均匀的路由到不同的分片中,分片中的索引统计信息也会是相似的,评分将按照预期工作,但是如果你进行了下列操作之一,那么很有可能搜索请求涉及到的分片没有类似的索引统计信息,相关性可能很差:
- use routing at index time (索引时自定义路由规则导致分片不均匀)
- query multiple indices (查询跨越了多个索引)
- have too little data in your index (数据量少得可怜)
如果你的数据集很小,那么最简单的方法就是只使用一个分片( index.number_of_shards : 1 )。
其余情况建议的方式是使用 dfs_query_then_fetch 搜索类型,这种方式将会查询所有关联分片的索引统计信息然后合并,这样评分时使用的就是全局的索引统计信息而不是某个分片的,显然这样增加了额外的成本,然而大多数情况下,这些额外成本是很低廉的,但是如果查询中包含有大量的 fields/terms 或 fuzzy 模糊查询,增加的额外成本可能并不低。
三
Tune for indexing speed
加速构建索引。
01
Use bulk requests
尽量使用 bulk 请求。
02
Use multiple workers/threads to send data to ES
其实就是提高客户端的并发数。
03
Increase the refresh interval
配置项 index.refresh_interval 默认是 1s ,这个时间指的是创建新 segment 合并旧 segment 的周期,增加此间隔时间有助于减轻 segment 合并的压力。
04
Disable refresh and replicas for initial loads
禁用 refresh 和备份可以提升不少的索引构建速度,但是正常情况下 refresh 和备份都是必须的,所以一般只在初始化导入数据如重建索引等特殊情况才使用。配置项为 index.refresh_interval : -1 和 index_number_of_repicas : 0 。
05
Disable swapping
禁用宿主机操作系统的 swap 。
06
Give memory to the filesystem cache
将宿主机至少一半的内存分配给 filesystem cache 文件系统缓存。
07
Use auto-generated ids
使用用户自定义的文档 id ,ES 将会检查其是否冲突,而使用 ES 自动生成的 id 则会跳过此步骤。
08
Use faster hardware
使用更好的硬件。
09
Indexing buffer size
确保 indices.memory.index_buffer_size 足够大,能为每个分片提供最大 512 MB 的索引缓冲区,超过这个值也不会有更高的性能。默认是 10%,即 JVM 有 10 GB 内存,那么 1 GB 将会用于索引缓存。
10
Disable _field_names
在 mapping 设置中禁用 _field_names ,但会导致 exists 查询无法使用。
11
Additional optimizations
其余一些额外的优化项与下文中的 Tune for disk usage 优化磁盘使用相关联。
四
Tune for search speed
加速搜索。
01
Give memory to the filesystem cache
给 filesystem cache 分配更多内存。
02
Use faster hardware
使用更好的硬件。
03
Document modeling
文档模块化,避免 join 操作,nested 和 parent-child 关联查询都会比较慢。
04
Search as few fields as possible
在 query_string 和 multi-match 查询中,fields 越多查询越慢。你可以新增一个联合字段,在 mapping 中设置 copy_to 将多个 fields 字段自动复制到这个联合 field 字段中,这样就能把多字段查询变为单字段查询。
05
Pre-index data
预索引数据。在进行 range aggregation 范围聚合查询时,我们可以新增一个字段以在索引时标记其范围,这样 range aggregation 就变成了 term aggregation 。例如,要查询 price 在 10-100 范围内的文档数据,那么可以在构建索引时新增一个 price_range 字段标记此文档为 10-100 ,这样就可以直接根据 price_range 进行查询了。
06
Consider mapping identifiers as keyword
数字不一定要映射为数字类型字段,也可以是 keyword ,索引数字类型对于 range 查询进行了优化,而 keyword 在 term 查询时更有利。
07
Avoid scripts
避免使用 scripts,如果一定要用,优先使用 painless 和 expressions 引擎。
08
Search rounded dates
放宽日期类型的精度,由于 now 是实时变动的,因此无法缓存,而如果使用诸如 now-1h/m ,这是可以进行缓存的,相应的精度也就成了一分钟。
09
Force-merge read-only indices
强制合并只读索引为单一的 segment 更有利于搜索。使用场景常常是例如基于时间的索引,历史日期的数据不再改变,因此是只读的,而对于存在写入操作的索引不得进行此项操作。
10
Warm up global ordinals
Global ordinals 是一种数据结构,用于 keyword 字段上进行 terms aggregations,可以在 mapping 中设置 eager_global_ordinals : true 提前告诉 ES 这个字段将会用于聚合查询。
11
Warm up the filesystem cache
ES 重启后,filesystem cache 是空的,可以通过 index.store.preload 提前导入指定文件到内存中进行预热,但是如果文件系统缓存不够大,将会导致所有数据被 hold 住,一定要小心使用。
12
Use index sorting to speed up conjunctions
使用 index sorting 索引排序可以使连接更快(组织 Lucene 文档 id,使连接如 a AND b AND ... 更高效),但代价是构建索引将变慢。
13
Use preference to optimize cache utilization
缓存包括 filesystem cache、request cache、query cache 等都是基于 node 节点的,使用 preference 更够将同样的请求路由到同样的分片也就是同一个节点上,这样能够更好的利用缓存。
14
replicas might help with throughput, but not always
备份也会参与查询,这有助于提高吞吐量,但并非总是如此。
如何设置备份的数量?假设集群中有 num_nodes 个节点,num_primaries 个主分片,一次最多允许 max_failures 个节点故障,那么备份的数量应该设置为
max( max_failures, ceil( num_nodes/num_primaries ) - 1 )
15
Turn on adaptive replica selection
开启动态副本选择,ES 将会基于副本的状态动态选择以处理请求。
五
Tune for disk usage
优化磁盘使用。
01
Disable the features you do not need
不需要构建倒排索引的字段不建索引,index: false。
text 类型字段不需要评分的可以不写入 norms,norms: false (norms 是评分因子)。text 类型字段默认也会存储频率和位置信息,频率计算分数,位置用于短语查询,不需要短语查询可以不存储位置信息,index_options: freqs ,不关心评分可以设置 index_options: freqs 的同时设置 norms: false 。
02
Don't use default dynamic string mappings
默认的动态字符串映射会将 string 字段同时索引为 text 和 keyword ,这造成了空间的浪费,明确使用其中一个即可。
03
Watch your shard size
shard 分片越大,则存储数据越高效,缺点就是恢复需要的时间更久。
04
Disable _all
禁用 _all ,此字段索引了所有的字段, v6.0.0 版本已经将其移除。
05
Disable _source
禁用 _source ,此字段存储了原始的 json 文档数据,虽然禁用可以节省磁盘空间,但是我个人并不建议这么做,因为禁用后将无法获取到此字段的内容,如 update 和 reindex 等 API 都将无法使用。
06
Use best_compression
通过 index.codec 设置压缩方式为 best_compression 。
07
Force merge
每个 shard 分片有多个 segments,segment 越大存储数据越高效。可以通过 _forcemerge API 减少每个分片的 segments 数量,通过 max_num_segments = 1 即可设置每个分片一个 segment 。
08
Shrink index
可以通过 shrink API 减少 shard 分片的数量,可以与 _forcemerge API 一起使用。
09
Use the smallest numeric type that is sufficient
使用合适的数字类型,数字类型越小占用磁盘空间越少。
10
Use index sorting to colocate similar documents
默认情况下,文档按照添加到索引的顺序进行压缩,如果启用了 index sorting 则按照索引排序顺序进行压缩,对具有相似结构、字段和值的文档进行排序可以提高压缩效率。
11
Put fields in the same order in documents
压缩是将多个文档压缩成块,如果字段始终以相同的顺序出现,则更有可能在这些 _source 文档中找到更长的重复字符串,从而压缩效率更高。
其实从实际情况来看,磁盘的成本往往是比较低廉的,我们常常更关注的是搜索和索引性能的提升。了解优化相关的部分内容有助于我们更好的理解和使用 ES