MongoDB 3.6 版本构建索引支持前台构建和后台构建,后台构建索引:
// 举例 db.people.createIndex( { zipcode: 1}, {background: true} )
默认地,MongoDB 索引创建的 background 是 false,即前台创建。
对比如下:
方式 |
使用方法 |
构建速度和索引文件 |
对读写影响 |
前台构建 |
createIndex默认为前台构建 |
快,索引文件的数据结构更高效 |
阻塞该集合所在数据库上的所有操作 |
后台构建 |
background参数,指定为true |
慢,索引文件的数据结构较低效 |
增量处理过程,期间允许读写操作 |
针对3.6版本,在大数据量集合构建索引,并且有业务读写的情况下,慎用前台构建索引的方式
MongoDB 4.2 版本忽略了background参数。在该版本上构建索引,只会在构建索引的开始和结束,获取集合锁,获取锁期间,会阻塞该集合上的读写操作,中间扫描集合文档、构建索引的过程中,不会阻塞读写请求。官方解释的性能对比:
MongoDB 4.2 索引构建性能至少与后台索引构建相当。对于在构建过程中工作负载较低的情况下,4.2 索引构建构建的速度可以与基于相同数据量的前台索引构建一样快构建索引对于数据库性能的影响
在目标集合处于高写入负载的时间段内构建索引可能会导致写入性能降低,并且构建索引的时间更久。建议在业务写入低峰期操作。可以参考下以下方式进行评估:
1、实例所在机器运行内存仍有1GB以上的剩余。
2、在过去一段时间cpu利用率无明显激增到100%的情况。
副本集或者分片集群上构建索引的过程在副本集的Primary节点上构建索引,或者在分片集群的mongos节点上构建索引,过程如下:
1、首先在副本集的Primary节点上构建索引。
2、之后重放在Secondary节点上。
查看索引进度在构建索引的过程中,可以通过currentOp来查看构建索引的进度
// 输入 db.currentOp(true).inprog.forEach(function(op){ if(op.msg!==undefined) print(op.msg) }) //输出结果 Index Build (background) Index Build (background): 89391121/204453376 43%
展示更多信息的搜索方式(在构建多个索引的情况下)
db.adminCommand( { currentOp: true, $or: [ { op: "command", "command.createIndexes": { $exists: true } }, { op: "none", "msg" : /^Index Build/ } ] } )
这个输出的结果是在 Primary 节点上的进度,未体现 Secondary 节点上构建索引的进度,可以理解为“即使这里进度显示100%,也只是代表 Primary 节点已完成索引构建,并不能代表整个副本集或者整个分片集群完成了索引构建”,所以最好通过日志确认下Secondary 节点的索引进度,特别是在需要构建多个索引的情况下,避免出现 secondary 节点索引构建未完成,而下一个索引又开始构建了的情况,这样 secondary 节点的io压力会增加,从而影响整个集群的性能。
Secondary 重放构建索引的过程,是插入索引的过程,日志如下:
终止索引构建
当在 Primary 节点上构建索引时,如要终止,可以使用db.killOp()方法,kill操作不是马上生效,可能会在大部分索引构建完成后才会终止。
而构建索引在 Secondary 节点上重放时,则无法终止该过程。首先必须在 Primary 节点上执行删除索引 dropIndex(),在 Secondary 节点索引构建完成后,再重放删除索引操作。
构建索引过程中节点挂掉 单节点挂掉单节点 mongod 在构建索引期间被关闭,索引构建任务和进度都将丢失。重新启动 mongod 不会重新启动索引构建。必须重新发出 createIndex() 操作以启动索引构建。
Primary 节点挂掉如果 Primary 节点在索引构建期间被关闭或停止运行,索引构建任务和进度都将丢失。重新启动 mongod 不会重新启动索引构建。必须重新发出 createIndex() 操作以启动索引构建。
Secondary 节点挂掉如果 Secondary 节点在索引构建期间被关闭,则索引构建任务将保留。重启 mongod 将会从头开始恢复索引构建。而在索引构建完成之前,mongod 将会一直停留在启动阶段。如下图所示:
如果数据量较大,这个启动过程会持续较久,而在这期间,所有操作都要等到索引构建完成,如果 oplog 无法覆盖完成索引构建所需的时间,该节点就会与 Primary 节点丢失同步,变为 Recovering 节点,那么需要全量同步来恢复。所以此刻你需要在 Primary 节点上查看 oplog 信息,预估下 oplog 能够覆盖的时间。
需要注意的是,这个时间体现的是当前写入速度下 oplog 覆盖的时间,如业务之后的写入速度出现增加或下降,则 oplog 能够覆盖的时间也会对应的降低或升高。所以只能参考这个信息进行预估,如果你预计 oplog 无法覆盖,最好临时调整下oplog size。跳过索引构建并启动
可以看到构建索引过程中 secondary 节点挂掉,重新拉起,启动过程可能会非常的长,同时对于客户端来说,该实例也是不可用的,可通过 --noIndexBuildRetry 命令行来跳过启动时的索引构建。
需要注意的是,在 MongoDB 4.0版本之后,副本集节点不再支持该命令行。所以对于 4.0 版本及以上,需要提前评估下集群负载,最好在业务低峰或者停写阶段创建索引,避免出现索引创建过程中,Secondary 节点挂掉的情况。否则你只能等待索引构建完成,而在这期间,实例将处于不可用的状态。
离线构建索引为了减少构建索引的影响,可以使用“离线”构建索引的方式,这里的“离线”指的是 在副本集或者分片集群,通过脱离副本集的同步,进行索引的构建,构建完成后再加入副本集。"离线"构建索引,需要从中取出一个 secondary 节点来操作,每次只会影响副本集中的一个节点,而不是全部的 secondary 节点。相较于直接在副本集上构建索引,影响范围更小,速度更快,且可控。
副本集离线构建索引 注意事项 唯一索引如果要使用该流程创建唯一索引,必须在该过程中停止对该集合的写入,否则可能会在副本集成员之间得到不一致的数据。如果不能停止写入,不要使用该流程,仍需要在 Primary 节点使用 createIndex() 创建索引。
预估oplog并提前调整确保 oplog 足够大,以允许索引构建操作完成而不会落后太多,避免因无法赶上而出现recovering 导致全量同步的情况。
操作流程 1、停止一个 Secondary 节点,以单实例模式启动选择一个 Secondary 节点,通过 shutdownServer() 停止。如果启动实例,使用的是配置文件的话,则修改其配置文件的内容。修改如下:
net: port: 27218 # port: 27017 #replication: # replSetName: myRepl setParameter: disableLogicalSessionCacheRefresh: true
其他配置保持不变,之后重启该实例。
mongod -f configfile
如果启动实例使用的命令行配置,则使用以下命令行启动即可。
mongod --port 27217 --setParameter disableLogicalSessionCacheRefresh=true
更换端口是为了确保在构建索引时副本集的其他成员和所有客户端不会联系该成员2、在单节点上构建索引
连接上该 mongod 实例,使用 createIndex() 创建索引,不必指定 background ,使用前台创建即可。
3、以副本集节点重启构建索引完成后,停止该节点,恢复配置文件到修改前,重启。启动后该节点会拉取 oplog 进行同步。直到追赶上。
4、对其他的 secondary 节点重复以上操作 5、在 Primary 节点上构建索引通过 rs.stepDown() 或者修改 priority 优先级的方式,进行主从切换。主从切换成功,原 primary 节点此时变为 secondary 节点,重复 1-2-3 步骤。之后对于 多az集群,可能仍需要切主到该节点。
分片集群离线构建索引 注意事项同副本集的注意事项,另外针对唯一索引,除了停止写入之外,还要注意:
在创建索引之前,验证集合中没有任何文档违反唯一索引约束(当然数据量较多的情况下,也没什么特别高效率的方式来验证,所以在大数据量集合中创建唯一索引,通常比较棘手)。如果集合分布在分片上并且有分片包含具有重复文档,则构建索引可能导致 在没有重复文档的分片上成功,但不会在有重复的分片上成功,这样就会出现分片之前索引不一致的情况。为避免跨分片不一致的索引,可以从 mongos 发出 db.collection.dropIndex() 以从集合中删除索引,这样会在存在索引的分片进行删除。操作流程
如果分片集群只有一个分片,则直接跳过1、2步骤。
1、关闭均衡器登录 mongos 节点执行以下命令关闭均衡器
sh.stopBalancer()
如果迁移正在进行中,系统将在停止平衡器之前完成正在进行的迁移。
要验证平衡器是否已停止,执行 sh.getBalancerState(),如果平衡器已停止,则返回 false。
2、确定集合在各分片的分布
登录 mongos 节点,刷新该 mongos 节点缓存的路由表,避免返回旧的路由信息。刷新之后,通过 db.collection.getShardDistribution() 命令获取集合的分布。
// 刷新路由表 db.adminCommand( { flushRouterConfig: "test.records" } ); // 获取集合分布 use test; db.records.getShardDistribution();
集合分布的信息举例如下:
Shard shardA at shardA/s1-mongo1.example.net:27018,s1-mongo2.example.net:27018,s1-mongo3.example.net:27018 data : 1KiB docs : 50 chunks : 1 estimated data per chunk : 1KiB estimated docs per chunk : 50 Shard shardC at shardC/s3-mongo1.example.net:27018,s3-mongo2.example.net:27018,s3-mongo3.example.net:27018 data : 1KiB docs : 50 chunks : 1 estimated data per chunk : 1KiB estimated docs per chunk : 50 Totals data : 3KiB docs : 100 chunks : 2 Shard shardA contains 50% data, 50% docs in cluster, avg obj size on shard : 40B Shard shardC contains 50% data, 50% docs in cluster, avg obj size on shard : 40B
从输出可以看到集合数据分布在 shadA 和 shardC上。
3、在包含集合数据的分片构建索引在每一个分片上执行以下操作,如上所示,只需要在 shadA 和 shardC上执行构建索引。
A.停止一个 Secondary 节点,以单实例模式启动
选择一个 Secondary 节点,通过 shutdownServer() 停止。如果启动实例,使用的是配置文件的话,则修改其配置文件的内容。修改如下:
net: port: 27218 # port: 27018 #replication: # replSetName: shardA #sharding: # clusterRole: shardsvr setParameter: skipShardingConfigurationChecks: true disableLogicalSessionCacheRefresh: true
其他配置保持不变,之后重启该实例
mongod -f configfile
如果启动实例使用的命令行配置,则使用以下命令行启动即可
mongod --port 27218 --setParameter skipShardingConfigurationChecks=true --setParameter disableLogicalSessionCacheRefresh=true
B.构建索引
连接上该 mongod 实例,使用 createIndex() 创建索引,不必指定 background ,使用前台创建即可。
C.以副本集节点重启
构建索引完成后,停止该节点,恢复配置文件到修改前,重启。启动后该节点会拉取 oplog 进行同步。直到追赶上。
D.对其他的 secondary 节点重复以上操作
E.在 Primary 节点上构建索引
通过 rs.stepDown() 或者修改 priority 优先级的方式,进行主从切换。主从切换成功,原 primary 节点此时变为 secondary 节点,重复 1-2-3 步骤。之后对于 多az集群,可能仍需要切主到该节点。
4、开启均衡器连接 mongos 节点,执行以下命令开启均衡器。
sh.startBalancer()参考文档
Index Builds on Populated Collections