维持一个超长时间的mongodb游标

Posted by ngtmuzi on 2020-06-17
班门弄斧

本文使用的是node.js版本的mongodb连接库

写这篇的时候才发现我根本没写过tag#mongodb 的博客,足以见得知识浅薄,甚至这篇也没有什么技术含量

有个有趣的工作是遍历某mongo库的索引,对其上符合要求的文档做某些操作,数据量大到可能要跑十天半个月,中途可能还要避开业务高峰期,而每次都用find({_id:{$gt:lastid}}).sort().skip().limit()感觉又不够好玩

于是计划用一个游标从头遍历到尾,这样的好处是不会重复发出多次op,在网络请求层面上还是要好一点的(游标有一个batchSize控制每次读取的数据量,可以视情况调整),而我又可以使用游标的流特性,来做一些下游打包、管道之类的好玩操作(见《Stream研究笔记II》

延长查询超时时间

官方文档-MongoClient

MongoClient建立实例时传递的socketTimeoutMS选项默认是6分钟,当查询耗时非常长时(比如没命中索引的count()),客户端将不再等待服务端返回并抛错,此选项只是以防万一,如果没有耗时很长的单次查询,这个可以保持默认

设置游标不超时

官方文档-Collection.find()

find()的参数内传递noCursorTimeout,避免闲置的游标被服务端主动释放

设置session活跃

然而只靠上面的选项还不够,官方文档-cursor.noCursorTimeout 又说了,服务端还是会清理闲置30分钟以上的session(每个op都包含在session中,不主动指定的话会生成一个隐式的),除非你主动地定期刷新session的活跃状态,连代码都附出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var session = db.getMongo().startSession()
var sessionId = session.getSessionId().id

var cursor = session.getDatabase("examples").getCollection("data").find().noCursorTimeout()
var refreshTimestamp = new Date() // take note of time at operation start

while (cursor.hasNext()) {

// Check if more than 5 minutes have passed since the last refresh
if ( (new Date()-refreshTimestamp)/1000 > 300 ) {
print("refreshing session")
db.adminCommand({"refreshSessions" : [sessionId]})
refreshTimestamp = new Date()
}

// process cursor normally

}

需要自己显示声明一个session,在它之上发起游标查询,然后定期刷新session,这样游标就不会被服务器给释放掉了