文档刷新-刷写-合并
# 近实时搜索
随着按段(per-segment)搜索的发展,一个新的文档从索引到可被搜索的延迟显著降低了。新文档在几分钟之内即可被检索,但这样还是不够快。磁盘在这里成为了瓶颈。提交(Commiting)一个新的段到磁盘需要一个fsync来确保段被物理性地写入磁盘,这样在断电的时候就不会丢失数据。但是fsync操作代价很大;如果每次索引一个文档都去执行一次的话会造成很大的性能问题
需要的是一个更轻量的方式来使一个文档可被搜索,这意味着fsync要从整个过程中被移除,如何做?
- 在Elasticsearch和磁盘之间使用文件系统缓存
- 在内存索引缓冲区中的文档会被写入到一个新的段中
- 新段会被先写入到文件系统缓存
- 这一步代价比较低
- 写入系统缓存的数据可以被读取(只要文件已经在缓存中,就可以像其它文件一样被打开和读取了)
- 稍后再被刷新到磁盘
- 这一步代价比较高
- 新段会被先写入到文件系统缓存
Lucene允许新段被写入和打开,使其包含的文档在未进行一次完整提交时便对搜索可见。这种方式比进行一次提交代价要小得多,并且在不影响性能的前提下可以被频繁地执行
# refresh
在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做refresh
默认情况下每个分片会每秒自动刷新一次,这就是为什么说 Elasticsearch是近实时搜索:文档的变化并不是立即对搜索可见,但会在一秒之内变为可见
关于手动刷新
- 使用场景索引了一个文档然后尝试搜索它,但却没有搜到
- 解决办法是用refresh API执行一次手动刷新:/users_refresh
尽管刷新是比提交轻量很多的操作,它还是会有性能开销。当写测试的时候,手动刷新很有用,但是不要在生产环境下每次索引一个文档都去手动刷新。相反,你的应用需要意识到Elasticsearch 的近实时的性质,并接受它的不足
# 设置刷新时间间隔
并不是所有的情况都需要每秒刷新,如日志场景
- 在使用Elasticsearch索引大量的日志文件,想优化索引速度而不是近实时搜索,可通过设置refresh_interval ,降低每个索引的刷新频率
{
"settings": {
"refresh_interval": "30s"
}
}
2
3
4
5
refresh_interval可以在既存索引上进行动态更新。在生产环境中,在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来
# 关闭自动刷新
PUT /users/_settings
{ "refresh_interval": -1 }
# 开启每一秒刷新
PUT /users/_settings
{ "refresh_interval": "1s" }
2
3
4
5
6
7
# flush 持久化变更
如果没有用fsync把数据从文件系统缓存刷(flush)到硬盘,则不能保证数据在断电甚至是程序正常退出之后依然存在。为了保证Elasticsearch 的可靠性,需要确保数据变化被持久化到磁盘。在动态更新索引,一次完整的提交会将段刷到磁盘,并写入一个包含所有段列表的提交点。Elasticsearch 在启动或重新打开一个索引的过程中使用这个提交点来判断哪些段隶属于当前分片
即使通过每秒刷新(refresh)实现了近实时搜索,仍然需要经常进行完整提交来确保能从失败中恢复。但在两次提交之间发生变化的文档怎么办? 不希望丢失掉这些数据
- Elasticsearch 增加了一个translog ,或者叫事务日志
- 先存在于内存中的translog,然后flush到磁盘上的translog
- 在每一次对Elasticsearch进行操作时均进行了日志记录
# translog触发flush流程
- 一个文档被索引之后,就会被添加到内存缓冲区,并且追加到了 translog
- 刷新(refresh)使分片每秒被刷新(refresh)一次到系统文件缓存
- 这些在内存缓冲区的文档被写入到一个新的段中,且没有进行 fsync 操作
- 这个段被打开,使其可被搜索
- 内存缓冲区被清空
- 事务日志不会被清空
- 这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志
- 每隔一段时间,如 translog 变得越来越大—>索引被刷新(flush);一个新的 translog被创建,并且一个全量提交被执行
- 所有在内存缓冲区的文档都被写入一个新的段
- 缓冲区被清空
- 一个提交点被写入硬盘
- 文件系统缓存通过 fsync 被刷新 flush
- 老的 translog 被删除
# 关于 translog fsync
translog 提供所有还没有被刷到磁盘的操作的一个持久化纪录,即上次commit point至今的索引操作记录
当 Elasticsearch 启动的时候,它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog 中所有在最后一次提交后发生的变更操作 translog 也被用来提供实时 CRUD 。当你试着通过 ID 查询、更新、删除一个文档,它会在尝试从相应的段中检索之前, 首先检查 translog 任何最近的变更。这意味着它总是能够实时地获取到文档的最新版本
执行一个提交并且截断translog 的行为在 Elasticsearch被称作一次flush
- 分片每30分钟被自动刷写 flush
- 或在 translog 太大的时候也会 flush
很少需要自己手动执行flush操作,通常情况下,自动刷新就足够了。这就是说,在重启节点或关闭索引之前执行 flush有益于你的索引。当Elasticsearch尝试恢复或重新打开一个索引,会重放translog中所有的操作,所以日志越短,恢复越快
translog 的目的是保证操作不会丢失,在文件被fsync到磁盘前,那么被写入的文档在重启之后就会丢失
关于translog 的 fsync 到磁盘
默认每5秒被fsync刷新到硬盘
或在每次写请求完成之后执行(e.g. index, delete, update, bulk)
- 在整个请求被fsync到主分片和复制分片的translog之前,客户端不会得到一个200 OK响应
- 默认开启,参数
{ "index.translog.durability" : "request" }
1
2
3- 在每次请求后都执行一个fsync会带来一些性能损失,尽管实践表明这种损失相对较小(特别是 bulk 导入,它在一次请求中平摊了大量文档的开销)
这个过程在主分片和复制分片都会发生
如果只使用异步 fsync 磁盘,而关闭每次请求后执行 fsync
- 对于一些大容量的偶尔丢失几秒数据问题也并不严重的集群,使用异步的 fsync还是比较有益的
- 决定使用异步translog ,需要保证在发生 crash 时,对丢失掉 sync_interval时间段的数据不敏感
# 段合并
由于自动刷新流程每秒会创建一个新的段 ,导致短时间内的段数量暴增。段数量太大带来的问题
- 每一个段都会消耗文件句柄、内存和 cpu 运行周期
- 每个搜索请求都必须轮流检查每个段
- 段越多,搜索也就越慢
Elasticsearch 通过在后台进行段合并来解决这个问题
- 小的段被合并到大的段,然后大的段再被合并到更大的段
- 段合并的时候会将那些旧的已删除文档从文件系统中清除
- 被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中
- 启动段合并不需要你做任何事;进行索引和搜索时会自动进行
# 流程
- 当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用
- 合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中
- 这并不会中断索引和搜索
- 一旦合并结束,老的段被删除
- 新的段被刷写(flush)到了磁盘
- 写入一个包含新段且排除旧的和较小的段的新提交点
- 新的段被打开用来搜索
- 老的段被删除
合并大的段需要消耗大量的 I/O 和 CPU 资源,如果任其发展会影响搜索性能,Elasticsearch在默认情况下会对合并流程进行资源限制,所以搜索仍然有足够的资源很好地执行