背景
准备一个脚本将历史预约记录同步至另一个 Service 并对每条记录回写一个新的状态字段。
生产环境中需要同步的数据大约在500万条左右。
使用官方MongoDB Drivers: mongodb@3.5.4
使用 limit()
一开始虽然考虑到了生产环境数据量非常大,直接find
存在打爆内存问题,但是只想到了简单的limit(),每1000条预约记录同步并回写一次,不断递归。
虽然能满足需求但是转念一想总感觉有点复杂不优雅。因为同样是IO,fs
模块提供了stream
读写大文件来避免内存溢出,难道 MongoDB 没有提供查询大量数据的类似方案吗?
使用 Cursor
这不河里啊,所以去翻了下官方文档发现原来find
方法默认返回的Cursor
就是一个实现了Iterator
接口的异步可迭代对象。
推荐读一下官方Corsur文档挺通俗易懂的
Cursor
的工作原理与fs
模块的stream
类似。简单讲就是建立一个管道后,分批次的返回结果文档。
不使用Cursor
使用Cursor
根据官方文档的解释,返回的批次大小是16MB,第一次返回的批次的文档数量不超过101条。以下是原文
The MongoDB server returns the query results in batches. The amount of data in the batch will not exceed the maximum BSON document size. To override the default size of the batch, see batchSize() and limit().
Operations of type find(), aggregate(), listIndexes, and listCollections return a maximum of 16 megabytes per batch. batchSize() can enforce a smaller limit, but not a larger one.
find() and aggregate() operations have an initial batch size of 101 documents by default. Subsequent getMore operations issued against the resulting cursor have no default batch size, so they are limited only by the 16 megabyte message size.
可以通过batchSize()
和limit()
设置批次大小。batchSize()
可以往小了调但不能超过16MB。
事情发展到了这里,不出意外的话就要出意外了
使用Cursor
+while
来改造limt
代码
数据库中有4条数据,limit(4)
期望的输出结果应该是遍历4条数据,但是现在只输出了2条数据。。。
我新增了cursor.count()
来验证获取的数据
count
结果是符合预期的,也就是说cursor
的遍历次数少了一半
我尝试取消limit
输出又符合预期了,是limit
影响了next
迭代?
此时我去谷歌了一番,没有找到相同问题,但是发现了另一种迭代方式,使用for await(of)
,抱着侥幸心理尝试了下
同样limit(4)
这次居然正常了。好家伙,这样我就怀疑是mongodb@3.5.4
的问题了,逐换成mongodb@4.2.2
果然是正确的,也就是说mongodb@3.5.4
下使用corsur.hasNext()
方法遍历时会发生问题。
总结
经过网友提醒奇数版本的mongodb
是开发版,就不应该使用。这次吃到教训了,虽然主要责任不在我,因为我是半路接手的项目,为什么会使用mongodb@3.5.4
已经无法追究了,但是既然接手了项目还是应该过一遍依赖包的,检查依赖包以后会列入接手项目的准备工作之一。
当时只
后来我也尝试阅读mongodb@3.5.4
的corsur.hasNext()
源码试图找到病因,但是没有研究出个所以然,还是太菜了。。。
喜欢记得点赞哟
网友反馈关于使用了cursor
为什么还要使用limit
|batchSize
的问题
在这次的案例中是因为代码改动时没有删除,遗留下来的。
限制每个批次返回的结果数量,其实是有意义的。
思考一个案例,当拿到数据后业务流程非常耗时,极端情况是一个批次的数据处理时间超过游标的timeout
游标自动关闭了,整个任务就终止了。所以是否要使用限制,要结合自己的业务代码来平衡。
如果想深入了解cursor
,可以看下这篇文章写给服务端 Node.js 开发者的 MongoDB 数据查询游标使用指南(里面的群友就是我)