//
MongoDB 线上操作案例2例
//
上周五的时候,在线上执行了2个MongoDB的操作,这俩操作跟平时的相比,其实都还有一些特殊性,所以简单
记录一下。
01
线上一个大集合添加索引
在MongoDB中,给某个集合的某个字段添加索引,这个操作想必大家都有经历过,正常情况下,我们会使用createIndex来执行,基本语法如下:
代码语言:javascript复制db.collection.createIndex(keys, options)
其中:
1、keys代表创建的索引字段和类型,通常的模式为{key:1}、{key:-1}、{key:'hashed'}等
2、除此之外,createIndex的可以支持DBA指定一些选项来控制这个加索引的操作,其中,使用最多的就是background、expireAfterSeconds这2个选项。
background:布尔类型,设置为true,代表这个操作是后台执行的,不影响写入(其实有点类似MySQL的Online DDL);设置为false,代表这个操作前台执行,所有跟这个集合相关的操作都要等待当前加索引操作执行完成才可以接着执行;
expireAfterSeconds:整数类型,代表TTL索引的过期时间。当然,可以不设置,表示是一个普通索引。
本次操作的表的数据量有6个亿,而且表在频繁写入,所以在执行的时候,肯定要加上background:true的选项的。代表不阻塞业务写入的情况下去添加这个索引。
其实MongoDB对这个命令的实现并不算特别友好,因为即使你使用了background:true这个选项,mongo shell命令行还是会卡在那里,直到这个命令执行完毕,命令行才会返回结果,给人一种不安全的感觉。但是其实我测试过,加了这个选项之后,即使你退出shell,这个命令也会执行成功的。因为后台已经在执行了。
实际操作过程中,由于表有6个亿的数据,因此客户端迟迟没有返回,这个时候,我们可以通过下面的命令来查看这个加索引的进度:
代码语言:javascript复制db.currentOp({ op: "command", "query.createIndexes": { $exists: true } })
可以看到进度
{
"inprog" : [
{
"desc" : "conn1671147624",
"threadId" : "139824813692672",
"connectionId" : 1671147624,
"client" : "xx.xx.x.xxx:37062",
"active" : true,
"opid" : -591785073,
"secs_running" : 11637,
"microsecs_running" : NumberLong("11637555631"),
"op" : "command",
"ns" : "fs.$cmd",
"query" : {
"createIndexes" : "trace_info",
"indexes" : [
{
"key" : {
"url" : "hashed"
},
"name" : "url_hashed",
"background" : true
}
]
},
"msg" : "Index Build (background) Index Build (background): 406813780/630393144 64%",
"progress" : {
"done" : 406813780,
"total" : 630393144
},
"numYields" : 3204738,
"locks" : {
"Global" : "w",
"Database" : "w",
"Collection" : "w"
},
"waitingForLock" : false,
... # 省略
}
上面的命令可以用来查找已经执行了大于3600s的进程,最终输出的结果如上面的代码。可以将滚动条拉到最后侧看到有一个索引的添加进度,就能够发现这个加索引的进程还在执行。已经执行了4亿多条记录,占比64%。(406813780/630393144) 64%。
那么实际上,这个语句执行了多长时间呢?答案是6小时。
单独从结果看,显然,这种操作不应该经常发生,因为添加索引是一个比较消耗CPU的过程,有可能对服务器上的其他服务产生影响,而且集合数据已经6个亿了,才来加索引未免有点亡羊补牢的感觉。最好的方法是在设计表的时候,就考虑查询模型,从根源上杜绝这种情况。
02
修改TTL索引的保留时间
理想情况下,TTL索引在第一次设置的时候,就应该考虑到容量问题,确定好需要保存的过期时间,不应该频繁的发生过期时间。
但是实际运维工作中,这样的情况也会有发生,上次就处理了一个TTL索引的过期时间修改问题。通常情况下,这个需求可以通过2个办法实现:
1、删除该字段上旧的TTL索引,新增一个符合要求的TTL索引
2、直接使用collMod命令修改TTL索引的过期时间,命令如下:
代码语言:javascript复制db.runCommand({collMod: "coll_name",index: { keyPattern: {field_name: 1 },expireAfterSeconds: 2592000}})
其中,expireAfterSeconds选项后面跟新的过期时间就行,单位是s
其实最稳妥的方案是方案一,但是这个过程中,应用需要接受一段时间的索引不可用情况,查询性能可能会受到影响,但是影响的范围是当前这1个集合;
方案二的命令在官方文档中的描述如下:
The collMod
command obtains an exclusive lock on the parent database of the specified collection for the duration of the operation. All subsequent operations on the database and all its collections must wait until collMod
releases the lock.
从描述中不难看出来,这个命令会加一个数据库级别的锁,影响的范围可能是整个数据库。
实际操作过程中,我使用的方案二,因为在测试环境上进行了测试,60w的数据,持有数据库级别锁的时间还是很短的,于是就直接操作了。如果这个集合的并发程度很高,或者数据量很大呢?这个结果不得而知,所以还是需要持谨慎态度。