如何高效地遍历 MongoDB 超大集合?
2019独角兽企业重金招聘Python工程师标准>>>
- GitHub 仓库:Fundebug/loop-mongodb-big-collection
本文使用的编程语言是 Node.js,连接 MongoDB 的模块用的是mongoose。但是,本文介绍的方法适用于其他编程语言及其对应的 MongoDB 模块。
错误方法:find()
也许,在遍历 MongoDB 集合时,我们会这样写:
const Promise = require("bluebird");function findAllMembers() {return Member.find();
}async function test() {const members = await findAllMembers();let N = 0;await Promise.mapSeries(members, member => {N++;console.log(`name of the ${N}th member: ${member.name}`);});console.log(`loop all ${N} members success`);
}test();
注意,我们使用的是 Bluebird 的mapSeries而非map,members 数组中的元素是一个一个处理的。这样就够了吗?
当 Member 集合中的 document 不多时,比如只有 1000 个时,那确实没有问题。但是当 Member 集合中有 1000 万个 document 时,会发生什么呢?如下:
<--- Last few GCs --->
rt of marking 1770 ms) (average mu = 0.168, current mu = 0.025) finalize [5887:0x43127d0] 33672 ms: Mark-sweep 1398.3 (1425.2) -> 1398.0 (1425.7) MB, 1772.0 / 0.0 ms (+ 0.1 ms in 12 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 1775 ms) (average mu = 0.088, current mu = 0.002) finalize [5887:0x43127d0] 35172 ms: Mark-sweep 1398.5 (1425.7) -> 1398.4 (1428.7) MB, 1496.7 / 0.0 ms (average mu = 0.049, current mu = 0.002) allocation failure scavenge might not succeed<--- JS stacktrace --->FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory1: 0x8c02c0 node::Abort() [node]2: 0x8c030c [node]3: 0xad15de v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]4: 0xad1814 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]5: 0xebe752 [node]6: 0xebe858 v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [node]7: 0xeca982 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [node]8: 0xecb2b4 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]9: 0xecba8a v8::internal::Heap::FinalizeIncrementalMarkingIfComplete(v8::internal::GarbageCollectionReason) [node]
10: 0xecf1b7 v8::internal::IncrementalMarkingJob::Task::RunInternal() [node]
11: 0xbc1796 v8::internal::CancelableTask::Run() [node]
12: 0x935018 node::PerIsolatePlatformData::FlushForegroundTasksInternal() [node]
13: 0x9fccff [node]
14: 0xa0dbd8 [node]
15: 0x9fd63b uv_run [node]
16: 0x8ca6c5 node::Start(v8::Isolate*, node::IsolateData*, int, char const* const*, int, char const* const*) [node]
17: 0x8c945f node::Start(int, char**) [node]
18: 0x7f84b6263f45 __libc_start_main [/lib/x86_64-linux-gnu/libc.so.6]
19: 0x885c55 [node]
Aborted (core dumped)
可知,内存不足了。
打印find()返回的 members 数组可知,集合中所有元素都返回了,哪个数组放得下 1000 万个 Object?
正确方法:find().cursor()与 eachAsync()
将整个集合 find()全部返回,这种操作应该避免,正确的方法应该是这样的:
function findAllMembersCursor() {return Member.find().cursor();
}async function test() {const membersCursor = await findAllMembersCursor();let N = 0;await membersCursor.eachAsync(member => {N++;console.log(`name of the ${N}th member: ${member.name}`);});console.log(`loop all ${N} members success`);
}test();
使用cursor()方法返回 QueryCursor,然后再使用eachAsync()就可以遍历整个集合了,而且不用担心内存不够。
QueryCursor是什么呢?不妨看一下 mongoose 文档:
A QueryCursor is a concurrency primitive for processing query results one document at a time. A QueryCursor fulfills the Node.js streams3 API, in addition to several other mechanisms for loading documents from MongoDB one at a time.
总之,QueryCursor 可以每次从 MongoDB 中取一个 document,这样显然极大地减少了内存使用。
如何测试?
这篇博客介绍的内容很简单,但是也很容易被忽视。如果大家测试一下,印象会更加深刻一些。
测试代码很简单,大家可以查看Fundebug/loop-mongodb-big-collection。
我的测试环境是这样的:
- ubuntu 14.04
- mongodb 3.2
- nodejs 10.9.0
1. 使用 Docker 运行 MongoDB
sudo docker run --net=host -d --name mongodb daocloud.io/library/mongo:3.2
2. 使用mgodatagen生成测试数据
使用 mgodatagen,1000 万个 document 可以在 1 分多钟生成!
下载 mgodatagen:https://github.com/feliixx/mgodatagen/releases/download/0.7.3/mgodatagen_linux_x86_64.tar.gz
解压之后,复制到/usr/local/bin 目录即可:
sudo mv mgodatagen /usr/local/bin
mgodatagen 的配置文件mgodatagen-config.json如下:
[{"database": "test","collection": "members","count": 10000000,"content": {"name": {"type": "string","minLength": 2,"maxLength": 8},"city": {"type": "string","minLength": 2,"maxLength": 8},"country": {"type": "string","minLength": 2,"maxLength": 8},"company": {"type": "string","minLength": 2,"maxLength": 8},"email": {"type": "string","minLength": 2,"maxLength": 8}}}
]
执行mgodatagen -f mgodatagen-config.json
命令,即可生成 10000 万测试数据。
mgodatagen -f mgodatagen-config.json
Connecting to mongodb://127.0.0.1:27017
MongoDB server version 3.2.13collection members: done [====================================================================] 100%+------------+----------+-----------------+----------------+
| COLLECTION | COUNT | AVG OBJECT SIZE | INDEXES |
+------------+----------+-----------------+----------------+
| members | 10000000 | 108 | _id_ 95368 kB |
+------------+----------+-----------------+----------------+run finished in 1m12.82s
查看 MongoDB,可知新生成的数据有 0.69GB,其实很小,但是使用 find()方法遍历会报错。
show dbs
local 0.000GB
test 0.690GB
3. 执行测试代码
两种不同遍历方法的代码分别位于test1.js和test2.js。
参考
- 如何使用 mongoose 对一个 100 万+的 mongodb 的表进行遍历操作
- Cursors in Mongoose 4.5
关于Fundebug
Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎大家免费试用!
版权声明
转载时请注明作者Fundebug以及本文地址: https://blog.fundebug.com/2019/03/21/how-to-visit-all-documents-in-a-big-collection-of-mongodb/
转载于:https://my.oschina.net/u/3375885/blog/3025477
如何高效地遍历 MongoDB 超大集合?相关推荐
- MongoDB 教程五: MongoDB固定集合和性能优化 (索引Indexes, 优化器, 慢查询profile)
mongodb索引详解(Indexes) 索引介绍 索引在mongodb中被支持,如果没有索引,mongodb必须扫描每一个文档集合选择匹配的查询记录.这样扫描集合效率并不高,因为它需要mongod进 ...
- MongoDB 教程五: MongoDB固定集合和性能优化
MongoDB 固定集合(Capped Collections) MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环 ...
- 技术瓶颈?如何解决MongoDB超大块数据问题?
目录 一.MongoDB服务器管理 1.添加服务器 2.修改分片中的服务器 3.删除分片 二.均衡器 三.修改块的大小 四.超大块 1.分发超大块 2.分发超大块步骤: 3.避免出现超大块 4.输出内 ...
- MongoDB大量集合启动加载优化原理
背景 启动数据加载时间对于很多数据库来说是一个不容忽视的因素,启动加载慢直接导致数据库恢复正常服务的RTO时间变长,影响服务可用性.比如Redis,启动时要加载RDB和AOF文件,把所有数据加载到内存 ...
- mongoDB 删除集合后,空间不释放的解决方法
mongoDB 删除集合后,空间不释放的解决方法 参考文章: (1)mongoDB 删除集合后,空间不释放的解决方法 (2)https://www.cnblogs.com/ddbear/p/71116 ...
- 【Groovy】map 集合 ( map 集合遍历 | 使用 map 集合的 each 方法遍历 map 集合 | 代码示例 )
文章目录 一.使用 map 集合的 each 方法遍历 map 集合 二.代码示例 一.使用 map 集合的 each 方法遍历 map 集合 遍历 map 集合 , 可以调用 map 集合的 eac ...
- Java16-day07【Map(概述、特点、功能、遍历)、HashMap集合练习、集合嵌套、Collections、模拟斗地主升级版】
视频+资料(工程源码.笔记)[链接:https://pan.baidu.com/s/1MdFNUADVSFf-lVw3SJRvtg 提取码:zjxs] Java基础--学习笔记(零起点打开java ...
- Pandas的学习(读取mongodb数据库集合到DataFrame,将DataFrame类型数据保存到mongodb数据库中)
1.读取mongodb数据库集合到DataFrame import pymongo import pandas as pdclient = pymongo.MongoClient("数据库连 ...
- mongoDB 删除集合后,空间不释放
mongoDB 删除集合后,空间不释放,添加新集合,没有重新利用之前删除集合所空出来的空间,也就是数据库大小只增不减. 方法有: 1.导出导入 dump & restore 2.修复数据库 r ...
最新文章
- PostgreSQL中的大容量空间探索时间序列数据存储
- netcore dapper mysql_.NET Core Dapper操作mysql数据库
- Python 69个内置函数分8类总结,这样记更方便!
- 大数据WEB阶段(九)Servlet+Request
- kettle 的表输出 table output
- HDU - 2732 Leapin' Lizards(最大流+思维建边)
- CVE-2019-0708漏洞补丁KB4499175
- linux 分区 flags,linux磁盘分区
- leetcode @python 130. Surrounded Regions
- java 内部类调用_Java内部类使用总结
- python就业方向-为什么这么多人喜欢Python?Python的就业方向是什么?
- JVM性能调优监控工具详解
- 写给初学者,一文搞懂大数据学习、岗位、面试及简历
- 雨滴win7计算机路径,win7雨滴桌面秀 Raindrop Desktop Show教程_计算机软件和应用程序_IT /计算机_信息...
- RK平台RAM和ROM容量计算
- linux 播放m4a 文件,M4A是什么文件?如何提取M4A音频?
- Conflux的自我进化:从DAG到树图| 对话伍鸣
- HTML中文字间距调整
- 腾讯系统测试一面+二面+HR面(面经)
- 抬杠APP获Donews“年度最佳运营创意”大奖,跨界营销引瞩目
热门文章
- Ubuntu12.04配置JDK1.8
- 第十、十一周项目四 - 教师兼干部类
- 如何取消或定制当点击GridView 的时候出现的那个黄色背景
- Java学习笔记24
- 从自定义TagLayout看自定义布局的一般步骤[手动加精]
- (0068)iOS开发之AutoLayout框架Masonry使用心得
- 用python画出小人发射爱心_小人发射爱心biu简笔画表情-biu小人简笔画表情动态完整版-东坡下载...
- HEOI2016/TJOI2016 字符串问题
- 解读基本数据类型和内置方法(2)
- Uvalive 3523 - Knights of the Round Table (双连通分量+二分图)