MongoDB数据空洞解决方法

本文链接:http://ifujun.com/mongodbshu-ju-kong-dong-jie-jue-fang-fa/

背景

某一段时间,来了点兴趣,看了点Mongodb的书,所以写下了这篇博客,也算是一点总结吧。

很多时候,我们项目上线时间比较久了,我们积累了一些无用的数据,而由于MongoDB顺序写的原因,在我们删除部分无用数据后,它的storageSize和fileSize并不会变小,这就造成了大量的数据空洞。

这些数据空洞除了占用磁盘之外,也会加载到内存中,这会降低内存效率。

所以这个时候,我们一般要对这些数据空洞进行处理,一般有下面几种处理方式。 一种是使用MongoDB自带的compact命令:

  • db.collectionName.runCommand(“compact”)

这种方式是collection级别的压缩,只能去除collection内的碎片,但是MongoDB的数据分配是DB级别的,效果并不一定多好,其次呢,这个压缩是线上压缩,肯定会影响服务的,磁盘IO会比较高,这么干容易出事。

还有一种方式,也就是我们下面要详细说明的方式,过程大概类似于下面:

  • 先预热从库
  • 提升从库为主库,原主库降为从库
  • 移除原主库的DB数据,直接remove掉
  • 重新同步
  • 完成后,预热,然后将此库提升为主库,原从库依然降为从库

这样做的好处是

  • DB级别,不会有碎片,毕竟是新写的,收缩效率高
  • 基本不会影响线上服务(当然,你不能只有一个从,你也不能挑服务高负荷的时候来干这个事情)

下面详细介绍一下步骤。

搭建简易集群

这里搭建的简易集群只有一主一从(这里不包含仲裁节点,只是为了简单),只作实验之用,并不能用于生产环境。

  1. 配置.conf文件
    shard11.conf

    dbpath = /Users/fujun/data/mongodb/shard11
    shardsvr = true
    replSet = shard1
    port = 28017
    oplogSize = 2048
    logpath = /Users/fujun/data/mongodb/log/shard11.log
    logappend = true
    fork = true
    nojournal = true
    

    shard12.conf

    dbpath = /Users/fujun/data/mongodb/shard12
    shardsvr = true
    replSet = shard1
    port = 28018
    oplogSize = 2048
    logpath = /Users/fujun/data/mongodb/log/shard12.log
    logappend = true
    fork = true
    nojournal = true
    
  2. 启动mongod

    mongod --config shard11.conf
    mongod --config shard12.conf 
    

    如果没有创建相应的文件夹,这里可能出错,注意一下。 成功之后会显示类似于这样的文字:

    about to fork child process, waiting until server is ready for connections.
    forked process: 878
    child process started successfully, parent exiting
    
  3. 初始化副本集

    使用mongo连接

    tn-30-20130009:data fujun$ mongo --port 28017
    MongoDB shell version: 3.0.7
    connecting to: 127.0.0.1:28017/test
    Server has startup warnings: 
    2015-11-10T09:17:54.230+0800 I CONTROL  [initandlisten] 
    2015-11-10T09:17:54.230+0800 I CONTROL  [initandlisten] ** WARNING: soft rlimits too low. Number of files is 256, should be at least 1000
    

    输入:

    cfg = {"_id":"shard1","members":[ {"_id":0, "host":"127.0.0.1:28017"}, {"_id":1, "host":"127.0.0.1:28018"}]};
    

  4. 手动降权

    这里没有仲裁节点,我们需要手动降权。我们将shard11降为从节点:

    rs.stepDown()
    

    显示已经从Other变为从节点

    查看副本集状态

插入数据

这里我们使用脚本插入2000w条数据(连接primary节点插入)。

mongo_insert.py

#!/usr/bin/python
import random
from pymongo import MongoClient

client = MongoClient('localhost', 28018)

test = client.test
students = test.students
students_count = students.count()
print "student count is ", students_count

for i in xrange(0,5000000):
    classid = random.randint(1,4)
    age = random.randint(10, 30)
    student = {"classid":classid, "age":age,
"name":"fujun"}
    students.insert_one(student)
    print i

students_count = students.count()
print "student count is ", students_count

脚本为插入500w个,建议大家开四个终端,一起执行,基本能达到比较高的负荷。

执行方式:

python ~/Desktop/mongo_insert.py 

前提条件是你要安装pippymongo

查看数据

每秒插入为4k-6k条(副本集在单台机器上,也就是说本地是有两份写的,可见如果只是单写,插入速度会更快)。

OK,2000w条数据插入成功!

storageSize和fileSize:

storageSize是dataSize+删除的文档的总大小(文档删除后,storageSize并不会变小)。

fileSize是文件空间的占用、索引等所有内容的总和。所以它会比storageSize大。

现在的fileSize由于Mongodb避免碎片,采用预分配机制,大小比较规律,类似于这样:

我们可以看到,基本上是在翻倍。

生成空洞数据

删除所有classid<4的数据。

db.students.remove({"classid":{"$lt":4}})

删除了1500w条,剩余500w条。

再次查看storageSize和fileSize,没有变化:

降权、停止

原shard12降为从节点,原shard11升为主节点。

停止shard12

删除数据

rm -rf ~/data/mongodb/shard12/*

shard11还在,shard12已经空了。

同步数据

重启shard11,查看log。

由于数据变的很少,mongodb并没有创建test.5,也就是那个2GB的文件。

再次查看storageSize和fileSize

我们发现,不管是storageSize还是fileSize都下降非常明显。

升权

将shard12升为主库,shard11降为从库,这里和上面类似,不再重复。

Article by 付军