调整 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调整成正常值。