一、写数据底层原理
当用户向一个节点提交了一个索引新文档的请求,节点会计算新文档应该加入到哪个分片(shard)中。每个节点都存储有每个分片存储在哪个节点的信息,因此协调节点会将请求发送给对应的节点。注意这个请求会发送给主分片,等主分片完成索引,会并行将请求发送到其所有副本分片,保证每个分片都持有最新数据。
- 新的document先写入内存buffer中,同时追加写入translog日志文件(transaction log),此时如果执行搜索操作,这个新文档还不能被索引到。
- ES产生的分片每秒被刷新(refresh)一次,这些之前在内存缓冲区的文档将被写入到一个新的段中;
此时这个segment里的文档可以被打开,使其可被搜索到,且没有进行 fsync 操作。(如果此时发生断电,则这些文档可能会丢失);
内存缓冲区被清空,但是translog日志文件不会。
- 如果2步骤这个进程继续工作,更多的文档被添加到内存缓冲区和追加到translog日志,这是translog日志文件越来越大
- 每隔30分钟或者translog文件变得越来越大,则执行一次fsync操作,把所有在文件系统缓存中的segment将被刷(flush)磁盘,而老translog将被删除(此后会生成新的translog)。
二、Translog
translog本身也是文件,存在于内存当中,如果发生断电一样会丢失。
所以官方文档介绍如下:
translog 的目的是保证操作不会丢失。这引出了这个问题: Translog 有多安全?
在文件被 fsync 到磁盘前,被写入的文件在重启之后就会丢失。默认 translog 是每 5 秒被 fsync 刷新到硬盘, 或者在每次写请求完成之后执行(e.g. index, delete, update, bulk)。这个过程在主分片和复制分片都会发生。最终, 基本上,这意味着在整个请求被 fsync 到主分片和复制分片的translog之前,你的客户端不会得到一个 200 OK 响应。因此只有在当前操作记录被写入磁盘,ES才会将操作成功的结果返回发送此操作请求的客户端。
在每次请求后都执行一个 fsync 会带来一些性能损失,尽管实践表明这种损失相对较小(特别是bulk导入,它在一次请求中平摊了大量文档的开销)。
但是对于一些大容量的偶尔丢失几秒数据问题也并不严重的集群,使用异步的 fsync 还是比较有益的。比如,写入的数据被缓存到内存中,再每5秒执行一次 fsync 。
这个行为可以通过设置 durability 参数为 async 来启用:
PUT /my_index/_settings
{
"index.translog.durability": "async",
"index.translog.sync_interval": "5s"
}
这个选项可以针对索引单独设置,并且可以动态进行修改。如果你决定使用异步 translog 的话,你需要 保证 在发生crash时,丢失掉 sync_interval 时间段的数据也无所谓。请在决定前知晓这个特性。
如果你不确定这个行为的后果,最好是使用默认的参数( “index.translog.durability”: “request” )来避免数据丢失。
三、段合并
由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。
Elasticsearch通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。
段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。
启动段合并不需要你做任何事。进行索引和搜索时会自动进行。这个流程如下图, “两个提交了的段和一个未提交的段正在被合并到一个更大的段” 中提到的一样工作:
1、 当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用。
2、 合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中。这并不会中断索引和搜索。
3、如下图当 “一旦合并结束,老的段被删除” 说明合并完成时的活动:
新的段被刷新(flush)到了磁盘。 ** 写入一个包含新段且排除旧的和较小的段的新提交点。
新的段被打开用来搜索。
老的段被删除。
段合并性能问题
合并大的段需要消耗大量的I/O和CPU资源,如果任其发展会影响搜索性能。Elasticsearch在默认情况下会对合并流程进行资源限制,所以搜索仍然 有足够的资源很好地执行。
有时候合并会拖累写入速率。如果这个真的发生了,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 重新打开限流。
如果你使用的是机械磁盘而非 SSD,你需要添加下面这个配置到你的 elasticsearch.yml 里:
index.merge.scheduler.max_thread_count: 1
机械磁盘在并发 I/O 支持方面比较差,所以我们需要降低每个索引并发访问磁盘的线程数。这个设置允许 max_thread_count + 2 个线程同时进行磁盘操作,也就是设置为 1 允许三个线程。
对于 SSD,你可以忽略这个设置,单个分片上可能一次合并的最大线程数。默认情况下 Math.max(1, Math.min(4, <<node.processors, node.processors>> / 2)),对于良好的固态磁盘(SSD)而言,效果很好。
最后,你可以增加 index.translog.flush_threshold_size 设置,从默认的 512 MB 到更大一些的值,比如 1 GB。这可以在一次清空触发的时候在事务日志里积累出更大的段。而通过构建更大的段,清空的频率变低,大段合并的频率也变低。这一切合起来导致更少的磁盘 I/O 开销和更好的索引速率。当然,你会需要对应量级的 heap 内存用以积累更大的缓冲空间,调整这个设置的时候请记住这点。
其他性能
最后,还有一些其他值得考虑的东西需要记住:
-
如果你的搜索结果不需要近实时的准确度,考虑把每个索引的 index.refresh_interval改到 30s 。如果你是在做大批量导入,导入期间你可以通过设置这个值为 -1 关掉刷新。别忘记在完工的时候重新开启它。
-
如果你在做大批量导入,考虑通过设置 index.number_of_replicas: 0关闭副本。文档在复制的时候,整个文档内容都被发往副本节点,然后逐字的把索引过程重复一遍。这意味着每个副本也会执行分析、索引以及可能的合并过程。
相反,如果你的索引是零副本,然后在写入完成后再开启副本,恢复过程本质上只是一个字节到字节的网络传输。相比重复索引过程,这个算是相当高效的了。 -
如果你没有给每个文档自带 ID,使用 Elasticsearch 的自动 ID 功能。这个为避免版本查找做了优化,因为自动生成的 ID 是唯一的。
-
如果你在使用自己的 ID,尝试使用一种 Lucene 友好的 ID。包括零填充序列 ID、UUID-1 和纳秒;这些 ID 都是有一致的,压缩良好的序列模式。相反的,像 UUID-4 这样的 ID,本质上是随机的,压缩比很低,会明显拖慢 Lucene。
四、删除和更新
段是不可改变的,所以既不能从把文档从旧的段中移除,也不能修改旧的段来进行反映文档的更新。 取而代之的是,每个提交点会包含一个 .del 文件,文件中会列出这些被删除文档的段信息。
当一个文档被 “删除” 时,它实际上只是在 .del 文件中被 标记 删除。一个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除。
文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。 可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。
总结
参考文档:
https://www.elastic.co/guide/cn/elasticsearch/guide/current/translog.html
https://www.elastic.co/guide/cn/elasticsearch/guide/current/merge-process.html#merge-process
https://www.elastic.co/guide/cn/elasticsearch/guide/current/indexing-performance.html#segments-and-m