简介
Elasticsearch 是基于 Lucene 实现的,Lucene 中引入了按段搜索的概念,每一个段本身就是一个倒排索引。索引在 Lucene 中除表示所有段的集合外,还增加了提交点(Commit point)的概念,一个列出了所有已知段的文件的描述如下图:一个 Lucene 索引包含一个提交点和三个段。
生成
新的文档首先被添加到内存索引缓存中(In-memory buffer)。
Elasticsearch 的自动刷新每隔 1s 进行一次,一个新的段被添加到提交点并清空内存索引缓存。
当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。
删除&更新
段是不可改变的,所以既不能把文档从旧的段中移除,也不能修改旧的段来对文档进行更新。取而代之的是,每个提交点会包含一个 .del 文件,文件中会列出这些被删除文档的段信息。
当一个文档被 “删除” 时,它实际上只是在 .del 文件中被标记删除。一个被标记删除的文档仍然可以被查询匹配到,但它会在最终结果被返回前从结果集中移除。
文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。
合并
自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。每一个段都会消耗文件句柄、内存和 cpu 运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。
Elasticsearch 通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。
段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。
启动段合并不需要用户做任何事。进行索引和搜索时会自动进行。段合并的流程:
- 当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用。
- 合并进程选择一小部分大小相似的段,在后台将它们合并到更大的段中且不会中断索引和搜索。
两个已提交(存在于磁盘上)的段和一个未提交(存在于文件系统缓存中)的段合并到一个更大段,如图描述:
合并完成后,生成的大段(已排除被标记删除的文档)被打开用来搜索,较小的段被删除,如图描述:
性能优化
段合并的计算量庞大,可能会吃掉大量磁盘 I/O。段合并可能要很长时间才能完成,尤其是比较大的段,所以合并在后台定期操作。一般情况下段合并都不会有什么影响,因为大规模段合并的概率是很小的。
不过有时候合并会拖累写入速率。如果这个真的发生了,Elasticsearch 会自动限制索引请求到单个线程里。单线程可以减少段文件的生成速度,防止出现数以百计的段在被合并之前就生成出来。如果 Elasticsearch 发现合并拖累索引了,它会记录一个声明有 now throttling indexing 的 INFO 级别信息。
Elasticsearch 的默认设置在这块比较保守:不希望搜索性能被后台合并影响。不过有时候(尤其是 SSD,或者日志场景)限流阈值太低了。
这个限流阈值默认值是 20 MB/s,对机械磁盘应该是个不错的设置。如果使用的是 SSD,可以考虑提高到 100–200 MB/s。测试验证对你的系统哪个值合适:
PUT /_cluster/settings
{
"persistent" : {
"indices.store.throttle.max_bytes_per_sec" : "100mb"
}
}
如果在做批量导入,完全不在意搜索,可以彻底关掉合并限流。这样让索引速度跑到你磁盘允许的极限:
PUT /_cluster/settings
{
"transient" : {
"indices.store.throttle.type" : "none"
}
}
设置限流类型为 none 彻底关闭合并限流。等完成了导入,需要改回 merge 重新打开限流。
通过修改 index.translog.flush_threshold_size 设置,从默认的 512 MB 到更大一些的值,比如 1 GB。这可以在一次清空触发的时候在事务日志里积累出更大的段。
这样以来,清空的频率变低,大段合并的频率也变低(生成了更大的段也就意味着减少了小段合并成大段的频率)。这样会有更少的磁盘 I/O 开销和更好的索引速率。当然,修改这个配置需要对应量级的 heap 内存用以积累更大的缓冲空间,调整这个设置的时候需要关注这点。
optimize API
强制合并 API 。会将一个分片强制合并到 max_num_segments 参数指定大小的段数目。这样做的意图是减少段的数量(通常减少到一个),来提升搜索性能。
值得注意的是,optimize API 不应该被用在一个活跃的索引————一个更新频繁索引。后台合并流程已经可以很好地完成工作。optimizing 会阻碍这个进程。
在特定情况下,使用 optimize API 颇有益处。例如在日志这种用例下,每天、每周、每月的日志被存储在一个索引中。老的索引实质上是只读的;它们也并不太可能会发生变化。
在这种情况下,使用 optimize 优化老的索引,将每一个分片合并为一个单独的段就很有用了;这样既可以节省资源,也可以使搜索更加快速:
POST /logstash-2014-10/_optimize?max_num_segments=1
表示合并索引中的每个分片为一个单独的段
最后需要了解下,使用 optimize API 触发段合并的操作不会受到任何资源上的限制。这可能会消耗掉你节点上全部的I/O资源, 使其没有余裕来处理搜索请求,从而有可能使集群失去响应。
如果想要对索引执行 optimize,最好先使用分片分配把索引移到一个安全的节点,再执行。
参考
https://www.elastic.co/guide/...
https://www.elastic.co/guide/...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。