MongoDB 创建大量集合测试问题

问题背景

对使用 wiredtiger 引擎的 mongod 进行如下测试,不断的『创建集合、创建索引,插入一条记录』,然后统计这3个动作的耗时。

var db = db.getSiblingDB("testdb");
for (var i = 0; i < 100000; i++) {
    var start = (new Date()).getTime();
    var collName = "test" + i;
    var doc = {name: "name" +i, seq: i};
    db.createCollection(collName);        // 创建集合
    db[collName].createIndex({name: 1});  // 创建索引
    db[collName].insert(doc);             // 插入一条记录
    var end = (new Date()).getTime();     // 统计耗时
    print("cost: " + (end - start));
}

随着集合数越来越多,测试过程中发现2个问题

  1. 偶尔会出现耗时很长的请求(1s、2s、3s..不断上升),统计了下频率,大约1分钟左右出现一次。
  2. 平均耗时不断增加,从最开始平均10ms 不到,一直到20ms、30ms、40ms...

测试问题1

因为耗时很长的请求频率大概1分钟一次,跟 wiredtiger 默认的60scheckpoint 很接近,怀疑问题跟 checkpoint 有关,从运行慢日志看,耗时长是因为 createIndex 的原因。

通过当时的 pstack 发现,创建索引的线程正在等锁,只有 checkpoint 线程在干活

Thread 4 (Thread 0x7f80c3c72700 (LWP 70891)):
#0  0x00007f80c2ddc054 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x00007f80c2dd7388 in _L_lock_854 () from /lib64/libpthread.so.0
#2  0x00007f80c2dd7257 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x00000000019f3f95 in __wt_curfile_open ()
#4  0x0000000001a580a5 in __session_open_cursor_int ()
#5  0x0000000001a09e13 in __wt_curtable_open ()
#6  0x0000000001a57f29 in __session_open_cursor_int ()
#7  0x0000000001a584b9 in __session_open_cursor ()
#8  0x000000000108cfe9 in mongo::WiredTigerIndex::BulkBuilder::openBulkCursor(mongo::WiredTigerIndex*) ()
#9  0x000000000108841e in mongo::WiredTigerIndexStandard::getBulkBuilder(mongo::OperationContext*, bool) ()
#10 0x0000000000cb09e9 in mongo::IndexAccessMethod::commitBulk(mongo::OperationContext, std::unique_ptr<mongo::IndexAccessMethod::BulkBuilder, std::default_delete<mongo::IndexAccessMethod::BulkBuilder> >, bool, bool, std::set<mongo::RecordId, std::less<mongo::RecordId>, std::allocator<mongo::RecordId> >) ()
#11 0x0000000000b07410 in mongo::MultiIndexBlock::doneInserting(std::set<mongo::RecordId, std::less<mongo::RecordId>, std::allocator<mongo::RecordId> >*) ()
#12 0x0000000000b0797d in mongo::MultiIndexBlock::insertAllDocumentsInCollection(std::set<mongo::RecordId, std::less<mongo::RecordId>, std::allocator<mongo::RecordId> >*) ()

Thread 68 (Thread 0x7f80b9336700 (LWP 37085)):
#0  0x00000000019db9e0 in __config_next ()
#1  0x00000000019dc106 in __config_getraw.isra.0 ()
#2  0x00000000019dc5a6 in __wt_config_getones ()
#3  0x0000000001a2437d in __wt_meta_ckptlist_get ()
#4  0x0000000001a65218 in __checkpoint_worker.isra.10 ()
#5  0x0000000001a64888 in __checkpoint_apply ()
#6  0x0000000001a6657a in __txn_checkpoint ()
#7  0x0000000001a66e17 in __wt_txn_checkpoint ()
#8  0x0000000001a57854 in __session_checkpoint ()
#9  0x00000000019e4f8f in __ckpt_server ()
#10 0x00007f80c2dd5851 in start_thread () from /lib64/libpthread.so.0
#11 0x0000003403ee767d in clone () from /lib64/libc.so.6

为什么建索引会跟 checkpoint 有冲突?分析索引代码发现,前台建索引时,mongod 会使用 wiredtiger 的 bulk cursor,而openBulkCursor是要竞争 checkpoint 锁的(个人理解是避免在 bulk insert 过程中出现 checkpoint),所以 createIndex 会阻塞等待 checkpoint 完成。

// src/cursor/cur_file.c:__wt_curfile_open
 / Bulk handles require exclusive access. /
    if (bulk)
        LF_SET(WT_BTREE_BULK | WT_DHANDLE_EXCLUSIVE);

    / Get the handle and lock it while the cursor is using it. /
    if (WT_PREFIX_MATCH(uri, "file:")) {
        /*
         * If we are opening exclusive, get the handle while holding
         * the checkpoint lock.  This prevents a bulk cursor open
         * failing with EBUSY due to a database-wide checkpoint.
         */
        if (LF_ISSET(WT_DHANDLE_EXCLUSIVE))
            WT_WITH_CHECKPOINT_LOCK(session, ret,
                ret = __wt_session_get_btree_ckpt(
                session, uri, cfg, flags));

另外从目前的实现看,后台建索引时并不是 bulk cursor,而是使用普通的 cursor 逐条插入,故不会去竞争 checkpoint 的锁,上述测试代码在createIndex 时加上{background: true}选项时问题解决。

建议用户在建立索引时,尽量选择后台建索引的方式,可能性能上不如前台方式,但后台建索引对业务的影响是最小的(前台建索引还会获取 db 的写锁,导致 db 上的读写都被阻塞),最好的方式是 DDL 和 DML 分离,在业务代码中不要出现建索引、建集合的逻辑,预先创建好,业务只做CRUD 操作。

测试问题2

这个问题主要跟文件系统机制相关,testdb 下创建了数万个集合,对应到 wiredtiger 的实现,会出现一个目录下数万个文件的情况(集合的每个索引也要对应一个文件),而从ext4文件系统层面上,在目录里创建文件,先要遍历整个目录下所有的文件项,文件越多效率越低。

上述问题通常的解决方法是『将扁平化的目录层次化』,对应到 mongodb,就是将数万个集合分散到多个 DB 里,具体方法如下。

  1. 配置 storage.directoryPerDB 选项为 true
  2. 业务上将集合分散到多个 DB 里(如100个,平均每个目录下就只有几百个文件)

总结

MongoDB 使用 wiredtiger 引擎时,大量集合的场景(通常业务设计上是有问题的),可能会遇到很多未知的问题,毕竟这不属于常见的应用场景,官方在这方面的测试支持也会相对弱些,比如上述提到的2个问题,还有之前分享的一个集合太多无法同步的问题,建议大家使用 MongoDB 时,合理设计数据模型,避免踩不必要的坑。

时间: 2024-09-19 00:43:41

MongoDB 创建大量集合测试问题的相关文章

数据-mongodb创建索引后过段时间就消失了?

问题描述 mongodb创建索引后过段时间就消失了? 对A,B两个集合同时创建两组复合索引,索引字段都才4个,创建成功后查询速度比原来快多了.过一天后查询看发现A集合又慢了,发现A集合的复合索引没有了,B集合的索引还在.(A集合数据比较大一大) 给A重新创建了几次索引,都是过段时间索引就自动消失了.要重新创建.求解啊 解决方案 这个应该不会主动消失 是不是倒入数据的时候又覆盖索引 解决方案二: 这个不应该把,索引创建了,只有有人删除了,才会没有. 不太可能说没人删除,就自动没有了,那mongod

mongodb创建数据库和配置用户方法详解

1.安装mongodb 这步就不说了,大家自己去看linux/47932.htm">Centos安装MongoDB. 2.创建数据库 use tt 这样就创建了一个数据库,如果什么都不操作离开的话,这个库就会被系统删除.所以我们还要执行下面的命令: db.usr.insert({'name':'tompig'}); db.usr.insert({'name':'tompig1','id':1}); 我是随便整了2个表,这个无所谓的,反正要导入表的话就删除掉这2个就可以了,目前我们只是想让数

pymongo给mongodb创建索引的简单实现方法

  这篇文章主要介绍了pymongo给mongodb创建索引的简单实现方法,涉及Python使用pymongo模块操作mongodb的技巧,需要的朋友可以参考下 下面的代码给user的user_name字段创建唯一索引 ? 1 2 3 4 import pymongo mongo = pymongo.Connection('localhost') collection = mongo['database']['user'] collection.ensure_index('user_name',

WindowsServer2012VDI标准部署之创建虚拟机集合

  本篇博文来介绍如何创建虚拟机集合,在创建集合之前,我们首先需要确认要创建那一种的集合类型,在远程桌面服 务中,提供两种虚拟机集合:个人和共用.另外,根据是否自动创建和管理虚拟机来区分,又分为托管和非托管.因 此创建虚拟机集合总共有以下四种组合方式: 1.托管共用虚拟桌面集合 所谓托管,就是自动创建和管理虚拟机,所以需要准备虚拟机模板;所谓共用集合,就是当用户连接到集合时,是向 用户分配一个临时的虚拟机,所以只要空闲的虚拟机都可以被分配使用,从而达到虚拟机共用的效果. 2.托管个人虚拟桌面集合

请问C#如何创建访问集合元素的表达式?是用linq动态创建的。

问题描述 如何创建访问集合元素的表达式?是用linq动态创建的.比如Expression.Constant(3);可以创建一个常量3,用Expression.ArrayAccess可以访问数组元素.但对于集合的元素如何访问呢?比如DataTable.Rows["element1"]=1;这样的表达式该如何创建呢,谢谢 解决方案 解决方案二:用Expression.MakeIndex解决方案三:引用1楼caozhy的回复: 用Expression.MakeIndex 你好,能不能举个例子

使用ReadOnlyCollection创建只读集合

转载:http://www.cnblogs.com/abatei/archive/2008/02/04/1064102.html 使用泛型创建只读集合 问题 您希望类中的一个集合里的信息可以被外界访问,但不希望用户改变这个集合. 解决方案 使用ReadOnlyCollection<T>包装就很容易实现只读的集合类.例子如,Lottery类包含了中奖号码,它可以被访问,但不允许被改变: public class Lottery    {        // 创建一个列表.        List

如何利用HTML5与MongoDB创建位置感知Web程序

在日常生活中,我们都离不开位置识别类应用程序.Foursquare.Facebook等应用程序帮助我们和我们的家人朋友分享当前位置(或者正在参观的景点).而像Google Local这样的应用则帮助我们找到当前位置附近有哪些自己需要的服务设施或业务场所.如此,如果我们需要找到一家离自己最近的咖啡厅,完全可以通过Google Local快速获取建议并立刻动身前往.这不仅大大方便了日常生活,还能够帮助企业将自己的产品推销给更理想的受众群体.无论是对消费者还是对企业,这都堪称完美的双赢局面. 要创建这

mongoDB 定长集合(capped collection)

大多数情况下,mongoDB中都是普通的集合,这些集合也称为动态集合,可以自动增长以容纳更多的数据.但这并不适合所有的场景.比如需要保存应用程序的某一个时间段日志,对于历史日志需要定期老化.这种情形下,定长集合就派上了用场.本文描述了定长集合的特性以及给出相关演示. 一.定长集合的特性 需要事先创建,创建时指定大小,即大小固定,后续不可以随意改变 新文档被插入到队列末尾 使用循环的方式老化最老的文档,即不支持从定长集合手动删除文档 数据被顺序写入到磁盘上的固定空间 固定集合不能被分片 由于覆盖特

Laravel 创建 Service Provider 测试实例

1.定义服务类 有了上一节有关服务容器的讲述,理解起服务提供者来很简单.我们这里先定义一个绑定到容器的测试类TestService,为了对类的定义加以约束,我们同时还定义一个契约接口TestContract. 定义TestContract如下: <?php namespace App\Contracts; interface TestContract {     public function callMe($controller); } 定义TestService如下: <?php name