MongoDB 使用 WiredTiger 在 4.2 中的功能测试设置和拆卸速度慢 10 倍

MongoDB functional test setup and teardown 10x slower in 4.2 with WiredTiger

我正在将我们的 MongoDB 从 3.4(使用 MMAPv1 存储引擎)升级到 4.2(使用 WiredTiger)。我遇到的一件事在这一点上几乎是一个障碍,那就是我们的测试速度严重减慢。

长话短说(下面有更多详细信息)- MongoDB 4.2 WiredTiger 在测试中需要更长的时间来处理重复的数据库 setup/teardown。 减速幅度大约为 10。测试过去 运行 大约 10 分钟,4.2 运行 将近 90 分钟。即使只进行一小部分测试,这种减速也会重现,并且似乎来自测试的 setup/teardown 阶段。


环境

简单介绍一下我们的环境——我们正在使用 PHP 和 Doctrine ODM 与 MongoDB 对话。我们有大约 3000 个测试,一些纯单元测试,一些(很多)功能测试,实际使用数据库。测试 运行 在 Docker 化环境中进行 - 我们为每个管道启动一个新的 MongoDB Docker 容器,但是 我已经确认即使在类似生产的裸机设置中也会出现同样的减速。下面的实验是在裸机上进行的,以限制来自其他地方的问题。

每个功能测试首先删除数据库,然后将 fixture 加载到其中(+ 创建索引),然后执行实际的测试场景。

分析PHP

运行 一小部分测试和测量时间,我得到这些结果:

3.4:
    real    0m12.478s
    user    0m7.054s
    sys     0m2.247s

4.2:
    real    0m56.669s
    user    0m7.488s
    sys     0m2.334s

如您所见,测试的实际 CPU 时间大致相同,没有显着差异。但实际时间非常不同,这表明需要等待很多时间(在这种情况下是 I/O?)。

我进一步剖析了 PHP 代码,从结果中可以看出,在这个函数中花费的时间增加了 9-10 倍:

MongoDB\Driver\Manager::executeWriteCommand()

该函数的 documentation 表示:

This method will apply logic that is specific to commands that write (e.g. » drop)

这让我认为 setup/teardown 的数量(即删除集合、创建索引)将在这里发挥作用。

分析Mongo数据库

分析 PHP 指出 MongoDB 速度放缓,所以我也分析了这一点。我 运行 导致的测试子集

这些数字之间的大部分差异可以归因于这样一个事实,即在 4.2 中没有 createIndexes 的文档(也许它们被添加到分析 post-3.4?我没有知道)。

我过滤了分析文档,只显示那些花费 至少 1 毫秒 (>0) 的文档。有:

正如我之前提到的,Mongo 3.4 似乎没有在分析中报告 createIndexes。但是让我们假设所有这些命令都将花费与 4.2 中一样长的时间(不过,根据其余的分析结果,它们可能会花费更短的时间)。

然后所有那些 drop 命令在 4.2 中每个操作最多需要 15 毫秒。在3.4中也有209个drop命令,但几乎所有的命令都被报告持续了0毫秒。

只有极少量的插入和查询,并且当这些发生时集合的大小只有少数文档(每个集合少于 10 个,实际查询和插入的集合少于 5 个)。这种减速不是缺少缓存或索引的结果。在此设置下,即使是全面扫描也会很快。

内存和硬件

我发现的大多数关于此的讨论都是围绕为工作集设置适当的缓存大小。我 运行 在具有默认缓存大小(应为可用内存的 50%,即 2GB)的单核和 4GB RAM 的小型服务器上进行测试。对于 all 测试可能创建的数据来说,这绝对足够大。它们确实微不足道,大部分时间都花在了数据库状态的 setup/teardown 上。

结论

这是我第一次分析我们的测试及其与数据库的交互。 drop-and-index-creation 与实际工作的比率肯定可以提高,但到目前为止它已经与 MMAPv1 和 MongoDB 3.4 一起工作。 WiredTiger 会出现这种类型的减速吗?我可以做些什么来缓解这种情况吗?

我现在害怕升级生产 Mongo 数据库实例,因为我不知道它们的行为方式。如果这主要与索引创建和数据库删除有关,那么我认为生产工作负载应该没问题,但我不想冒险。遗憾的是,我们是一家相当小的公司,没有对生产环境进行任何 performance/stress 测试。


编辑

使用tmpfs

因为我运行在 Docker 和 Docker supports tmpfs volumes out-of-the-box 中进行测试,所以我试了一下。当使用 RAM-backed tmpfs 作为 MongoDB 数据的装载时,我设法将测试时间减少了大约一半:

4.2:
    real    0m56.669s
    user    0m7.488s
    sys     0m2.334s

4.2 - tmpfs:
    real    0m30.951s
    user    0m7.697s
    sys     0m2.279s

这更好了,但与在 MMAPv1 上 运行 所需的 12 秒相比仍然相去甚远。有趣的是,将 tmpfs 与 MMAPv1 一起使用并没有产生明显不同的结果。

测试速度下降的真正原因 - 指数

事实证明,我们的测试框架和夹具加载器在每次数据库清除时为所有托管集合创建了索引。这导致每个测试用例 创建了大约 100 个索引 ,这就是导致速度下降的原因。我没有直接从 Mongo 找到具体的证据,但似乎使用 WiredTiger 创建索引比使用 MMAPv1 慢 显着 。从测试设置代码中删除索引创建显着加快了测试速度,让我们回到升级前的时间。

我们的绝大多数测试都不需要索引,而且它们的创建时间比它们提供的查询加速时间长得多。我实现了一个选项来强制为开发人员知道他们将需要它们的测试用例创建索引。这是我们可以接受的解决方案。

将数据库的数据放入内存。在Linux,我推荐zram

根据我的经验,zram 在 raid 0 中的速度是顶级 nvme ssd(我认为是三星 860 pro)的 2 倍,我认为几乎是单个消费级笔记本电脑 ssd 的 10 倍。对于旋转磁盘或通过网络访问的存储,差异应该更大。

MongoDB 有各种其他存储引擎(我相信有一个叫做“临时测试”)但它们不支持事务,所以如果您的应用程序使用 4.2(或我认为甚至是 4.0)功能。

在生产中,您很可能不会在每次请求时都删除集合,因此 3.x 和 4.2 之间的实际性能差异应该更小。

使用ephemeralForTest引擎!

尽管@d-sm 在他们的回答中提到了这一点,但我错过了它所以让我为未来的读者强调一下。

如果您只需要一个快速存储引擎来 运行 您的单元测试针对 ,并且您需要将 MongoDB 更新到 v4.2+(所以 mmapv1 引擎不再是一个选项),您可以改用 ephemeralForTest 引擎。 不要将其与仅限企业的 In-Memory 引擎混淆,它是在 v3.2 中悄悄添加的(参见 changelog)。

该引擎在生产环境中不受官方支持,并且有一些限制(例如缺乏事务支持),但它在单元测试性能方面非常接近 mmapv1(也缺乏这些功能) .

所以它可能不适合所有用例,但我相信它对大多数用例来说已经足够了,所以在尝试 tmpfs 或其他解决方案之前先试一试,因为它们仍然会不授予相同的表演。