代码改变世界

调整 MongoDB 以适应批量加载

2024-08-28 09:04  abce  阅读(19)  评论(0编辑  收藏  举报

将几十亿条记录加载到 MongoDB 中,开始时加载速度还不错,但一段时间后就开始明显放缓。通过观察指标进行了一些研究,发现随着时间的推移,WiredTiger 的检查点时间越来越长。检查点时间从最初的几秒到后面的几分钟。在检查点期间,性能基本上是直线下降:

 

WiredTiger检查点

从 MongoDB 4.2 开始,WiredTiger 引擎每 60 秒进行一次全面检查点。这意味着 WiredTiger 缓存中的所有脏页面必须每 60 秒刷新一次到磁盘。请记住,WiredTiger 缓存的默认值是可用内存的 50%,因此我们必须以某种方式限制脏页面的数量,否则就会遭受损失(稍后详述)。

完全检查点会导致性能 “骤降”,有一些方法可以减轻这种影响,但不能完全消除。

顺便说一句,你可能想知道为什么 WiredTiger 的默认缓存值仅为可用内存的 50%,而不是 80-90%。原因是 MongoDB 利用了操作系统的缓存。在 WiredTiger 缓存中,只保留未压缩的页面,而操作系统会在页面(压缩的)写入数据库文件时对其进行缓存。通过为操作系统留出足够的可用内存,我们增加了从操作系统缓冲区获取页面的机会,而不是在页面错误时进行磁盘读取。

 

驱逐过程

驱逐主要是从 WiredTiger 缓存中移除最近使用最少的页面,以便为其他需要尽快访问的页腾出空间。与大多数数据库一样,有专门的后台线程来执行这项工作。让我们看看接下来要调整的可用参数。

 

控制WiredTiger 缓存大小

eviction_trigger=95,eviction_target=80

两个参数表示占 WiredTiger 缓存总量的百分比,并控制整个缓存的使用量。使用量是指干净页和脏页的总和。eviction_trigger默认值是95,即cache_size的95%;eviction_target默认值是80,即cache_size的80%。

 

我们来看一个例子:

假设服务器内存为 200 Gb,WiredTiger 缓存设置为 100 Gb。驱逐线程会尽量将内存使用量控制在 80 Gb 左右(eviction_target)。如果压力过大,缓存使用量增加到高达 95 Gb(eviction_trigger),那么应用程序/客户端线程将被限流。它们会被要求帮助后台线程执行驱逐任务,然后才被允许执行自己的工作,帮助缓解部分压力,代价是增加客户端的延迟。如果这样还不够,缓存达到了配置缓存大小的 100%,操作就会停滞。

 

限制脏页数量

eviction_dirty_trigger=20,eviction_dirty_target=5

这两个参数控制缓存中的脏数据数量。基本上,当脏页的数量达到或超过缓存总大小的 5%,驱逐线程就会介入。脏页面的数量增长到 20%,就会对应用限流,从而增加客户端的延迟。

 

在尖峰或完全检查点中,所有脏页都必须刷新到磁盘上。这将尽可能的耗尽所有磁盘写入能力。这就是为什么会给这些参数设置低默认值,因为我们希望限制数据库在每次检查点时的工作量。

最低可以设置为1%(不支持浮点数值)。在内存较大的服务器上,1% 的使用率还是很高的!现在 256G 的缓存并不罕见,1% 就是 2.56G。每分钟刷新一次。对于磁盘来说,这可能是太多了,这取决于你拥有什么样的硬件。要想进一步减少这一容量,唯一的办法就是减小 WiredTiger 缓存的大小,而这又会带来其他后果。

 

驱逐线程的数量

eviction=(threads_min=4,threads_max=4)

默认情况下,MongoDB 会分配四个后台线程来执行驱逐操作。我们可以选择指定最小和最大线程数,但并不清楚有效线程数是如何确定的。此外,由于某些原因,最大线程数被硬编码为 20。

 

在特殊情况下,默认的 4 个线程不足以跟上脏页的生成速度,Percona 监控和管理 (PMM) 图形就证明了这一点:

 

因此,为了尽量减少夯住,我们需要做的是控制脏页的数量,使检查点所需的时间 “合理”(比方说小于 10 秒)。要知道,更多的线程意味着更多的 IO 带宽和更多的 CPU 资源(由于压缩)。

驱逐调优

在对现有硬件进行了一些实验后,我们决定将驱逐线程数增加到最大 20 个,将脏页阈值降低到 1%到 5%的范围,同时设置一个 1 Gb 的小型 WiredTiger 缓存,这样就能将脏页面数限制在 10-50 Mb。

 

要即时更改设置,我们可以运行以下命令:

db.adminCommand( { "setParameter": 1, "wiredTigerEngineRuntimeConfig": "eviction=(threads_min=20,threads_max=20),checkpoint=(wait=60),eviction_dirty_trigger=5,eviction_dirty_target=1,eviction_trigger=95,eviction_target=80"})

请注意,该命令完成前客户端线程会被阻塞。根据我的经验,通常只需几秒钟,但我也见过在非常繁忙的服务器上需要几分钟的情况。为了安全起见,请计划在维护窗口部署此命令。

 

如果我们想让设置持久化,一种方法是编辑 systemd 单元文件(RH/Centos 上为 /usr/lib/systemd/system/mongod.service),并像下面的示例一样传递 wiredTigerEngineConfigString 参数:

OPTIONS='-f /etc/mongod.conf --wiredTigerEngineConfigString "eviction=(threads_min=20,threads_max=20),eviction_dirty_target=1"'

 

汇总一下

配置

默认值

原理

eviction_target

80

当cachesize已被使用内存超过总内存的百分比到达此值, 那么后台evict线程开始淘汰内存页

eviction_trigger

95

当用掉的内存超过总内存的eviction_trigger, 用户线程也会参数到淘汰内存页工作中

eviction_dirty_target

5

当cachesize中脏数据比例超过该值, 那么后台线程开始将脏数据刷盘

eviction_dirty_trigger

20

当cache中脏数据比例超过该值, 那么用户线程也会参数到淘汰内存页工作中

evict.threads_min|max

4

后台evict线程最小|大数量

 

 

注:这里只是针对单次批量加载做的设置。后期正常业务还是要将cachesize调整成正常值。