【译】理解 SolrCloud 的事务日志、软提交和硬提交

本贴最后更新于 2596 天前,其中的信息可能已经事过景迁

从 solr4.0 起,solr 多了一个软提交(softcommit)功能,而硬提交功能(hardcommit)多了一个参数-openSearcher。通常,软提交和硬提交的作用很容易混淆,尤其是它们对于事务日志(transation log)的意义。这两个功能的作用,虽然 solrconfig.xml 模板里面有提到,但是通常举例说明都有局限性,如果要完全说明白 solrconfig 的所有的选项,起码 10M 的文档才能说得明白,而且没有人会读完整个文档去弄明白所有的东西。因此,这篇文章更详细地阐述一下硬提交、软提交和 openSearcher 参数的效果。

准则

请记住,“硬提交是关于持久化的,软提交是关于可见性的”。硬提交和软提交是相关的概念,但是它们的目的是不一样的。这句话隐含了很多细节,我们将就其中一些进行阐明。首先是几个定义:

  • Transaction Log(Tlog): 记录了原始文档,用于索引恢复功能。在 SolrCloud 中,每个节点都有自己的 tlog。在更新的时候,整个文档会写入 tlog 中。在原子更新(Atomic update)时,仍然是整个文档写进来,包括了从老文档中读取出来的内容,换言之,原子更新时,写到 tlog 的不是增量数据。Tlog 是保证一致性的关键,有了它,就算索引段(segment)关闭前 JVM 崩溃了,索引也不会丢失。

    • 注意:一旦服务器没有正常关闭,则重新启动时,tlog 会进行回放。因此,如果你的 tlog 很大(我们见过 GB 级别的 tlog),则重启会非常慢,例如几个小时才启动成功。
  • Hard commit : 硬提交通过 solrconfig.xml 的选项来实现,或者客户端显式调用。硬提交会结束当前索引段的构建,并开启新的索引段的构建。

  • openSearcher:选项的子属性,用来控制新提交的数据是否能被后来的搜索操作检索到(是否可见)。

  • **Soft commit:**软提交是比硬提交(openSearcher=true)使文档可见的更轻量级的操作,而且软提交不会结束当前索引段的构建。

    • 重要:虽然软提交是轻量级的操作,但是也不是完全没有代价的。你应该使软提交的时间间隔尽可能长来保证更好的性能。
  • **fsynch:**底层 IO 命令,当 fsynch 调用返回后,数据必定已写到了磁盘中。这个和 java 程序的 flush 是不一样的。java 的 flush 只是保证已经向操作系统提交了数据,操作系统并没有立刻写到磁盘中,而是在适当的时候才会真正写入磁盘。

  • **flush:**java 程序把数据提交给操作系统。这个操作返回后,数据并没有真正落盘,如果操作系统崩溃了,数据有可能会丢失掉。

    • 请注意,特别是在 solrcloud,多于一个副本的情况下,所有副本同时挂掉,并且数据都没有落盘,才会发生数据丢失,这种情况是很少见的。

    • 操作系统在 flush 命令后大概 10-50 毫秒就会把数据写入磁盘。如果 jvm 崩溃了,操作系统仍然会把数据写到磁盘,但是,如果是操作系统崩溃,而 IO 子系统又没来得及把数据刷到磁盘,则会丢失数据。这通常不是你需要关注的,只有当你需要绝对确保没有数据丢失时,这才很重要。

Transaction Logs

事务日志是 solr4 中数据完整性的保证,但也引入了很多的麻烦,我们详细聊一下。索引构建的流程如下:

  • 写入的数据被一个节点接收,然后转交到正确的 leader 节点。

  • 从那个 leader 节点发送到所有相关分片的所有副本。

  • 所有副本索引完后回应 leader 节点。

  • leader 节点回应一开始的接收节点。

  • 当所有的 leader 节点都回应之后,接收节点回应客户端,在这个点上,所有的数据都已经 flush 到了集群中的所有相关节点的 tlog 中

  • 如果 jvm 崩溃了,文档也已经安全地写到了 tlog 中,但是,如果是操作系统奔溃,那就不一定了。

    • 如果 jvm 崩溃(或者 killed -9 杀掉),然后重启,tlog 会回放。

    • 你可以修改 solrconfig.xml 里面的配置,在返回前用 fsynch 而不是 flush,但这样是没有必要的。所有 leaders 和 replicas 同时因为硬件挂掉而丢失数据的几率是很小的。有些场景,就算存在细微的几率丢失数据也是不允许的,则可以采用这种牺牲吞吐量的方式。

注意:tlog 文件会在 hard commit(不管 openSearcher 是 true 还是 false)时滚动(rolled over)。老的 tlog 文件会关闭,而打开一个新的 tlog 文件。保留足够的 tlog 来存放 100 个文档,然后其他 tlog 会删除掉。假设你每批 25 个文档来建索引,每批完成后执行 hard commit。这样,任何时候你都会保持 5 个 tlog 文件,最老的 4 个每个包括 25 个文档,共 100 个,加上当前的 tlog。当当前 tlog 关闭后,最老的那个就会删除掉,而一个新的 tlog 文件会打开。需要特别注意的是,solr 不会尝试只把 100 个文档存入特定的 tlog。只有你告诉 solr 要滚动了,它才会滚动,比如发出 commit 命令或 autoCommit 发生了。所以,在大量写入的情况下,比如 1000 个文档每秒,而你一个小时没有 commit,则单个 tlog 文件就包含了 3,600,000 个文档。如果 solr 发生了意外关闭,则重启后,这个 tlog 就会全部 replay,完成后才能提供搜索服务,这可能需要几个小时的时间。而且,你可能没有那么大的耐心来等待,觉得可能哪里出了问题了,然后又重启,这个 tlog 又重头 replay。就是这样,如果你有很大的 tlog,则肯定是有问题的,你需要修改你的 hard commit 配置。这个坑对于从 3.x 过来的人更容易踩到,3.x 的时代,hard commit 一般都设置得比较长,因为,那时候还没有 openSearcher=false 这个选项,hard commit 代价是昂贵的。

Soft commit

*Soft commit 是和可见性有关的,hard commit 是和持久性有关的。*对于 soft commit 的理解是,它能使文档可见,但是会有一些代价。尤其是在 solrconfig.xml 里定义的顶层的 cache(filterCache、queryResultCache 等)会失效,autowarning 会在顶层 cache 发生(比如 filterCache、queryResultCache)。这时候,newSearcher 的查询都会被执行,而且,fieldvaluecache 也会失效,因此,facet 查询也不得不等 cache 重新生成。在频繁的 soft commit 下,cache 基本上没什么效果,在某些场景下,最好去掉它。然而,索引段级别的 cache(译者注:比如 fieldcache),用于 function 查询,排序等,是基于索引段的,因此不会因为 soft commit 而失效,它们能继续被使用。

那么这意味着什么呢?

假设一个 softcommit 被执行了,则:

  • tlog 不会被截断,它会继续增长。

  • 新增的文档会可见。

  • 某些 cache 必须重新加载。

  • 顶层的 cache 会失效。

  • autowarming 会被触发。

  • 新的索引段会生成。

注意,我没有说任何关于索引段的事情,那是 hardcommit 做的。再次重申,soft commit 比 hard commit(openSearcher=true)代价小,但是并不是完全没有代价的,正如格言所说的“天下没有免费的午餐”。soft commit 用来支持近实时搜索,但是是有代价的,因此,设置 soft commit 的时间间隔尽可能长,来获取更好的性能。

Hard commit

*hard commit 是有关持久化的,softcommit 是有关可见性的。*这里,还要分两种情况,openSearcher=true 和 openSearcher=false。首先,我会解析一下这两种情况下都会发生的事情。不管 openSearcher=true 还是 openSearcher=false,都会出现以下的结果:

  • tlog 会截断:当前 tlog 会关闭,一个新的 tlog 会开始,在已关闭的 tlog 中,如果较新的 tlog 超过了 100 个文档,则老的 tlog 会删除掉。

  • 当前正在索引的索引段会关闭,并 flush。

  • 可能会触发后台的段合并。

这些就是 hardcommit 一定会发生的事情,无论 openSearcher 怎样设置。

  • openSearcher=true:Solr/Lucene searchers 被重新打开,所有的 cache 都失效(译者注:索引段级别的 cache 不会失效),autowarming 会执行。这是老版本唯一能看到新增加的文档的方法。

  • openSearcher=false:除了以上四点以外没有其他动作了。如果要搜索新的文档,需要执行一次 soft commit。

Recovery

我上面说了持久化的问题,那么我们再来深入探讨下。当机器宕机,jvm 崩溃,无论怎样,你的集群的状态是这样的。

  • 最后返回成功的更新调用已经将你的文档写到了集群中的 tlog,默认是你的 tlog 已经 flush,但是没有 fsync‘d,我们之前已经提到过,你可以修改这个默认行为,但是不推荐这样做。

  • 当重启机器,它会联系 leader 节点,然后会执行其中一个恢复动作

    • 如果 leader 节点接收到小于等于 100 个文档,则会从它自己的 tlog 回放文档(译者注:缺失的文档会从 leader 的 tlog 同步过来)。注意,在回放的过程中,新进来的文档会写到 tlog 的末尾,它们也会回放。

    • 如果从节点下线到现在,leader 节点接收了大于 100 个文档,则会采用传统的同步方式从 leader 节点同步整个索引。

恢复是需要一定的时间的,这是人们在使用 solrcloud 经常遇到的坑。他们会做各种实验,各种地方关闭、启动服务,用 kill -9 命令杀 solr 进程等。一方面,它是好的,演练了 solrcloud 的恢复过程。另一方面,它又没有什么效果,因为这是需要高度依赖人的经验的。如果你的某些节点一天中消失好多次,那么你应该去修复它,这个比 solr 恢复时间较长的问题要严重得多了。

建议

说到建议,我常常觉得很为难,因为在某些场景下,任何建议都是错的。我首先建议的是不要把问题想的过了。一些聪明的人会更可靠地进行调优。先尝试一些简单的调整,只调整一些必要的配置。尤其是,先看一下 tlog 的大小,调整一下 hard commit 时间间隔大小,使 tlog 的大小比较合理。记住,如果 jvm 崩溃了,最常见的后果是 tlog 回放时间较长。15 分钟能忍受吗?如果不能,那么为什么不设置得小一些呢。我们见过 hard commit 比 soft commit 时间间隔小得多的设置,参考下面提到的大批量索引构建。

优雅地关闭 solr,换句话说,在索引构建的过程中,“kill -9”只会自找麻烦。

优雅地关闭,意味着:

  • 停止写入新的文档。

  • 发送一个 hardcommit 命令或等待 autoCommit 时间间隔过期。

  • 停止 solr 服务。

你可以调整一些配置,以适合你的场景。

大批量索引构建

假设你希望把大批量的数据尽可能快地写入系统,将来用于搜索。比如,数据源的初始化。

  • 设置 soft commit 时间间隔足够长,比如 10 分钟或更长(配置为-1 则不自动 soft commit)。soft commit 是有关可见性的,我想大批量索引构建应该不是为了满足实时搜索的,所以没必要额外地打开任何类型的 searcher。

  • 设置 hard commit 时间间隔为 15 秒,openSearcher=false。重申一下,这里假设你只是为了把大量数据导入 solr,而不是为了实时搜索。这样设置,就是最坏的情况也只是当你重启,你最多只需要从 tlog 回放 15 秒的数据。如果你的系统频繁重启,则先找到频繁重启的原因。

  • 只有当你已经尝试了简单的方案而没有解决问题,你才需要考虑进一步的改进方案,一般是一些不常见的做法,它们包括:

     + 在批量索引时完全关闭tlog。
    
     + 离线构建索引,比如mapreduce方式构建索引。
    
     + 在构建索引时,每个分片只有一个leader节点,没有副本,构建完成后,再开启副本,让它们进行传统的复制来保持一致。注意,这个是自动实现的,如果副本发现和leader相差太远,就会触发传统的复制。复制完成后,它会继续接受leader节点的文档,并保存到自己的tlog中。
    
     + 诸如此类
    

索引更新很频繁,检索请求量很少

比如,日志检索。这个场景下,每天有大量的日志要写进来,但是检索请求却很少,主要用作故障排除和日志分析。

  • 设置 soft commit 时间间隔足够长,设置为你能忍受新文档不可见的最长时间。这可能是几分钟或更长。甚至几个小时,直到执行一次 hard commit(openSearcher=true)或 soft commit。

  • 设置 hard commit(openSearcher=false)的时间间隔为 15 秒。

索引更新不频繁,检索请求量小或大

这是相对静止的索引,但时不时有一些更新操作。比如说 5-10 分钟有一个更新操作。

  • 除非近实时搜索是必需的功能,在这种场景下,我会去掉 soft commit,每 5 分钟提交一次 hard commit(openSearcher=true)。如果你是采用外部单线程来构建索引,由客户端来提交 hard commit 可能更合理。

索引更新很频繁,检索请求量大

这是近实时搜索的场景,是最复杂的场景,这需要有较多的经验,我是这样做的

  • 设置 soft commit 的时间间隔为你能忍受的最大长度。不要听信你的产品经理的话“我需要不超过 1 秒钟的延迟”。时间间隔从长到短,逐渐尝试,看看用户服务是否满足,是否用户注意到了延迟,直到找到一个合适的值。soft commit 和近实时搜索非常好用,但是并非没有代价的。

  • 设置 hardcommit 的时间间隔为 15 秒。

Solrj 和 http 和客户端索引

一般来说,所有的配置选项也能通过 solrj 和 http 实现。 _Late edit_说过,“从客户端进行提交要特别小心,实际上,最好不要这样做”。一般来说,客户端索引时不要发送提交命令到 solr,这样做经常会犯错,尤其是你有多个客户端同时进行索引的时候。提交命令并发地发送过来,并发地执行,你很可能会看到这样的警告“too many warming searchers”,或者你会看到无数的小段。还是在 solrconfig.xml 中配置自动提交(hard commit 或 soft commit)来控制提交频率吧。如果你一定要控制可见性,你想索引后马上能看到新的文档,而无法忍受等待自动提交触发。那么,只在最后执行一次就好了。实际上,只在只有一个客户端的情况下,我才会自己来提交。否则,我会在索引全部完成,我才手工提交一次,像这样

http://host:port/solr/collection/update?commit=true

你也可以用 solrj,在添加索引的时候,设置 commitWithin 参数,单位是毫秒。这种方式,无论多少客户端都没问题的。服务端 timer 在接受到第一个带有 commitWithin 参数的更新时启动,经过 commitWithin 时间之后,任何客户端发送的所有的文档都会提交掉,无论那些文档是否包含了 commitWithin 参数。下一个带有 commitWithin 参数的更新又会启动一个新的 timer。最后记住,优化索引(optimzing)是一般来说是没有必要的。

英文链接
https://lucidworks.com/2013/08/23/understanding-transaction-logs-softcommit-and-commit-in-sorlcloud/

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...