一.空洞碎片
当mongodb出现大量删除的时候,就容易产生空洞碎片,一般来说,mongodb的机制上,可以不管空洞碎片,因为数据持续增长的话,迟早还是会重新占用的.而nosql本身是随机存储和读写的,没有顺序批量读写的概念,可以忽略关系型数据库的一些概念.不过有时候实在需要回收空洞碎片,就有以下的方式可以操作.
回收空洞碎片总共有3种方法:
1.compact
官方命令,会阻塞其他操作,所以在主库执行时要慎用.在主库执行要加上force:true,不然执行不了.只能在mongod实例上执行,不能在mongos实例上运行.原理是将重写集合和索引,且释放未使用的空间.最大优点是按集合操作,不影响其他集合.缺点是效果有时候并不明显,因为可能判断不出那些是真碎片.
示例:
    db.runCommand ({compact:"inn_rate",force:true})
2.db.repairDatabase()
通过丢弃无效或损坏的数据来重建数据库和索引.类似于文件系统修复命令fsck.所以此命令主要是用于修复数据.会完全阻塞数据库的读写,主库要谨慎操作,且消耗时间比较长.
示例:
    db.repairDatabase()
3.secondary节点重同步
等于重建副本集,能够最大化回收逻辑空间和物理空间,而且重建期间数据库还能正常使用,没有任何阻塞,但是需要客户端支持多地址模式.如果直连单库,就要考虑停服时间问题了.
示例:
    先切换走需要回收空洞碎片的primary节点
    rs.stepdown()
    然后关闭当前切换为secondary节点的旧主节点,就是需要回收空洞碎片的mongodb程序
    db.shutdownServer()
    删除当前切换为secondary节点的旧主节点,就是需要回收空洞碎片的mongodb程序数据目录下的所有数据文件
    rm -rf /data/mongodb/data/*
    重新启动mongodb程序
    sudo su - mongodb -s /bin/bash -c "numactl --interleave=all /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/mongod_data_40001.conf"
    此时将会自动开始重建副本集,需要一定的时间,如果后面还需要将这个程序重新切换为primary节点,那就等完全重建完成后,再运行一次主从切换命令就可以了
    rs.stepdown()
注意:如果库特别大,或者说负载特别高的时候,同步时间过长可能会造成一些奇葩情况,所以要多考虑一下.

二.孤儿文档(orphaned document)
问题原因:
orphaned document是指在分片集群模式环境下,一些同时存在于不同shard上的同一数据值的document.在mongodb分片集群中,理论上一个document只能出现在一个shard上,document与shard的映射关系维护在config server中.官方文档指出了可能产生orphaned document的情况:在balancer执行moveChunk迁移的过程中,mongod实例异常切换甚至宕机,导致迁移过程失败或者部分完成,就会残留同一数据值的document.而为了加速chunk 迁移的速度,因此delete phase不会立刻执行,而是放入一个队列,异步执行,此时如果crash,就可能产生孤儿文档,文档中还指出,可以使用 cleanupOrphaned 来删除orphaned document.
表象:
孤儿文档在mongodb集群的简单表象就是count数据不准,因为它会把孤儿文档也算进去,但是你find是始终只有一条记录,孤儿文档是看不到的.
orphaned document的影响在于某些查询会多出一些记录,多出来的这些就是孤儿文档,比如上面说的count操作,事实上只有3条记录,但返回的是4条记录.如果查询的时候没用使用sharding key(例如_id)精确匹配,也会返回多余的记录,另外,即使使用了sharding key,但是如果使用了$in,或者范围查询,都可能出错.
注意:通常在一些反复删除重建的集合会容易出现孤儿文档
1.查找孤儿文档数
    db.collections.find().explain(true)
    从结果中的chunkSkips可以知道某个shard的孤立文档数
2.使用cleanupOrphaned解决孤儿文档
注意:cleanupOrphaned要在shard的primary上执行
db.runCommand( {
   cleanupOrphaned: "<database>.<collection>",
   startingFromKey: <minimumShardKeyValue>,
   secondaryThrottle: <boolean>,
   writeConcern: <document>
} )
示例:
db.adminCommand( {
   "cleanupOrphaned": "test.info",
   "startingFromKey": { x: 10 },
   "secondaryThrottle": true
} )
由于孤儿文档删除以后,cleanupOrphaned命令即执行结束.这里我们使用循环删除,来删除所有孤立文档.
use admin
var nextKey={ };
var result;
while ( nextKey != null ) {
  result=db.runCommand( { cleanupOrphaned: "mydb.mycol1", startingFromKey: nextKey } );
  if (result.ok != 1)
     print("Unable to complete at this time: failure or timeout.")
  printjson(result);
  nextKey=result.stoppedAtKey;
}
3.假如孤儿文档不算很多,也是可以考虑通过手动删除孤儿文档.
    a.去shard的primary上执行find,对比和mongos查找的数据是不是一致,孤儿文档通常和正确的文档数据有差异.
    b.如果一致,和sh.status()信息也对得上,证明是真正的数据,如果不一致,sh.status()等信息也显示有问题,那就是孤儿文档.
    c.然后,无论数据对还是错,先备份一下,再手动remove()掉,最后记得再验证下孤儿文档是不是少了.