Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Jira
serverASF JIRA
serverId5aa69414-a9e9-3523-82ec-879b028fb15b
keyIOTDB-1881

分析

通过对日志和内存 dump 的分析,发现分布式 RaftLogManager 中的内存缓存超出了阈值。在探究内存缓存超出阈值的原因时,发现主要有以下四个问题:

  • 分布式进程中单机内存和 RaftLog 内存没有统一考虑管理,即单机认为自己得到了全部堆内存进行分配,而分布式又在一些地方进行了额外分配了内存并进行了内存控制,这导致分布式进程对自己拥有内存的认知一开始就大于实际的堆内存,从而使得内存一开始写入就比较紧张。
  • RaftLog 模块自身做了简单的内存控制策略,但只是针对已 commit 日志做的,未 commit 的日志没有通过阻写的方式来实现完备的内存控制。
  • RaftLog 模块的内存控制与单机的内存控制产生了正反馈导致系统崩盘。极端情况下,由于内存不够,单机状态机受单机内存控制模块影响导致 apply 日志过慢,然而 RaftLog 模块的清理内存线程只能清理 applyIndex 之后的日志,这导致 RaftLog 模块的内存控制几乎失效,无法清理内存中的日志。两者的双向作用共同导致了问题的进一步恶化。
  • RaftLog 模块的后台清理内存线程默认被关闭了,导致目前只有在 commit 的时候才会去检验内存,这可能使得内存释放不及时。当然,即使有它也不能完全避免前面提到的问题。

方案

需要在分布式进程中将单机内存和 RaftLog 内存统一管理并将单机极端情况阻写的内存控制策略移动至 RaftLog 上层,此外还可默认开启后台的内存清理线程以提高容错性。

Leader 内存控制策略:

的分析,发现其中一个节点 RaftLogManager 所保留的已提交日志占用的总内存超过配置值。该节点所配置的保留日志内存大小为每个 RaftLogManager 512MB,考虑到测试时用的负载,约可以保存720条日志,但实际上每个RaftLogManager保留了接近2000条日志。其原因在于:

  • 分布式模块的内存管理与单机模块的内存管理相互独立,使得单机模块的内存占用与分布式模块的内存占用相加可能超过JVM的总内存;
  • 在本次测试的开始阶段,尽管两者内存相加没有直接超过JVM总内存,但已经非常逼近该值,使得FullGC频繁触发;
  • 由于FullGC频繁触发,单机模块不能快速执行日志;由于日志得不到执行,分布式模块也无法及时清理日志,使得内存居高不下,FullGC也持续不断;
  • 此时Leader仍然在向该节点发送并提交日志,使得该节点保留的已提交日志数进一步增长,加剧了内存的紧张。
  • RaftLog 模块的后台清理内存线程默认被关闭了,导致目前只有在 日志提交的时候才会去检验内存,即便有日志被执行,也可能得不到清理,使得内存释放不及时。

其余发现的问题:

  • 一个Follower已经接收但尚未提交的日志被视作临时内存,没有进行管理
  • Leader正在向一个Follower发送以及等待被发送的日志被视作临时内存,没有进行管理


一个写入请求在分布式进程内存中的流动:

Image Added

当前的内存控制除了已 Apply 的日志外,对其余三种日志都没有进行内存控制。

方案

分布式模块与单机模块内存管理的联动

    分布式模块分配的内存不再作为一个单独的配置项,而是作为单机内存分配的一个分量。

    避免分布式模块与单机模块的总内存占用超过JVM内存。

Leader 内存控制策略:

  • 当前有太多未提交的日志时,阻塞写入一段时间。
  • Leader 采用 dispatcher 模式进行同步,对于每一个Follower,限制等待发送到该Follower的日志数量。当前有太多超过未提交的日志时,参照单机内存控制阻塞写入一段时间。

Follower 内存控制策略:

  • Leader 采用 dispatcher 模式(单独启一个线程)进行同步,对于每一个 Follower,如果其未 catchup,则先向其同步之前的日志再发送之后的日志,同时在发送日志时直接使用 RaftLogManager 的日志即可,不用提前 build request,此处可以参照 pipeline 的优化。模式进行同步,对于每一个Follower,限制同时向该Follower发送日志的线程数。
  • Follower 在遇到本地 applyIndex 与 Leader 的 commitIndex 相差过大时,可以暂时拒绝 leader 的写入,直到本地 applyIndex 跟上为止。 

...