美文网首页
【mongoDB】mongoDB空间预分配机制

【mongoDB】mongoDB空间预分配机制

作者: Bogon | 来源:发表于2022-09-24 20:27 被阅读0次

    为什么MongoDB的数据文件很大?
    MongoDB采用的预分配空间的方式来防止文件碎片。

    在创建数据库时,MongoDB会在磁盘上分配一组数据文件,所有集合、索引和数据库的其他元数据都保存在这些文件里。
    数据文件都被放置在启动mongod时指定的dbpath里,在未指定dbpath时,mongod会把文件全保存在/data/db里。

    garden.ns是第一个生成的文件,文件扩展名ns表示namespaces,意即命名空间。

    数据库中的每个集合和索引都有自己的命名空间,每个命名空间的元数据都存放在这个文件里。
    默认情况下,.ns文件大小固定在16 MB,大约可以存储24 000个命名空间,也就是说数据库中的索引和集合总数不能超过24 000。
    我们几乎不可能使用这么多集合与索引,但如果真有需要,可以使用--nssize服务器选项让该文件变得更大一点。

    MongoDB倾向于这种预分配的做法,这能让数据尽可能连续存储。
    如此一来,在查询和更新数据时,这些操作能更靠近一点,而不是分散在磁盘各处。

    在向数据库添加数据时,MongoDB会继续分配更多的数据文件。每个新数据文件的大小都是上一个已分配文件的两倍,直到达到预分配文件大小的上限——2 GB,即garden.2会是256 MB,garden.3是512 MB,以此类推。

    此处基于这样一个假设,如果总数据大小呈恒定速率增长,应该逐渐增加数据文件分配的空间,这是一种相当标准的分配策略。当然,这么做的后果之一就是分配的空间与实际使用的空间之间会存在很大的差距。

    MongoDB每个库逻辑上包含许多集合(collection),物理上存储为多个数据文件,数据文件的分配是预先分配的,预分配的方式可以减少碎片,程序申请磁盘空间的时候更高效,但MongoDB预分配的策略可能导致空间的浪费。

    默认的分配空间的策略是:随着数据库数据的增加,MongoDB会不断分配更多的数据文件。
    每个新数据文件的大小都是上一个已分配文件的两倍( 64M, 128M, 256M, 512M, 1G, 2G, 2G, 2G ),直到预分配文件大小的上限2G。
    虽然2G的阀值可以调整,但一般运维等时候往往也不会去调整,就这点来说,可能导致空间的浪费。(可以这样理解,原本一个collection大小为2M,增加了一个100K的数据后,现在该collection大小变为2M*2=4M,这种分配策略会浪费内存,但会避免产生碎片)

    对于磁盘的空间的分配效率,,如果本身有IO瓶颈,预分配一个2G的文件,将可能导致服务出现严重性能问题。
    预分配文件,可以减少碎片,提高程序申请空间的效率,但有无必要一次分配初始化一个巨大的文件,这点值得商榷。
    虽然预分配的机制,文档记载是可以关闭的,但一般使用NOSQL产品都是会使用默认配置,也建议使用默认的配置,因默认配置往往经历了长久的考验,没有那么多bug。

    MongoDB的文档在数据文件中是连续存储的,这点不同于一些关系数据库的做法(它们会把长记录拆分为两部分,溢出的那部分单独存放在另一处),如果没有预留足够的空间,那么更新可能导致原有空间放不下新的文档。当更新迫使引擎在BSON存储中移动文档时,存储碎片可以导致意外的延迟。对此MongoDB官方的解释是如下,

    “如果有足够的空间,在MongoDB中更新文档时,数据会在原地更新。如果更新后的文档大小大于已经分配的空间,那么文档会在一个新位置被重写。MongoDB最终会重用原来的空间,但这可能需要时间,而且空间可能会过度分配。

    在MongoDB 2.6中,默认的空间分配策略将是powerOf2Sizes,这个选项从MongoDB 2.2开始就已经提供了。该设置会将MongoDB分配的空间大小向上取整为2的幂(比如,2、4、6、8、16、32、64等等)。该设置会降低需要移动文档的几率,并使空间可以更高效地重用,结果是更少的空间过度分配和更可预测的性能。用户仍然可以使用精确匹配的分配策略,如果文档大小不增加,该策略更节省空间。” 显然,这种策略又将导致空间的浪费,特别是对于导入只读类型的数据。

    MongoDB不支持数据文件的压缩,也不能回收空间。
    它所使用的碎片整理的策略,可能是在一个新的地方重写,而不是对旧的碎片进行整理、合并。

    > use rundb
    
    > db.stats()
    {
            "db" : "rundb",
            "collections" : 5, 表示当前数据库有多少个collections.可以通过运行show collections查看
            "objects" : 22,    表示当前数据库所有collection总共有多少行数据。显示的数据是一个估计值
            "avgObjSize" : 100.36363636363636,表示每行数据是大小,也是估计值,单位是bytes
            "dataSize" : 2208,   示当前数据库所有数据的总大小,不是指占有磁盘大小。单位是bytes
            "storageSize" : 36864,  表示当前数据库占有磁盘大小,单位是bytes,因为mongodb有预分配空间机制,为了防止当有大量数据插入时对磁盘的压力,因此会事先多分配磁盘空间。
            "numExtents" : 5,
            "indexes" : 6,   表示system.indexes表 索引个数
            "indexSize" : 49056,  表示索引占有磁盘大小。单位是bytes
            "fileSize" : 67108864, 表示当前数据库预分配的文件大小,例如test.0,test.1,不包括test.ns。
            "nsSizeMB" : 16,
            "extentFreeList" : {
                    "num" : 0,
                    "totalSize" : 0
            },
            "dataFileVersion" : {
                    "major" : 4,
                    "minor" : 22
            },
            "ok" : 1
    }
    

    看某个库的统计数据:

    > use testDB
    
    switched to db testDB
    
    > db.stats()
    
    {
           "db" : "testDB",
           "collections" : 4,
           "objects" : 6230728,
           "avgObjSize" : 256.37665325785366,
           "dataSize" : 1597413192,
           "storageSize" : 1938201600,
           "numExtents" : 37,
           "indexes" : 6,
           "indexSize" : 2291485504,
           "fileSize" : 8519680000,
           "ok" : 1
    }
    

    此处fileSize分配了近8G的空间,但是实际存储的数据dataSize和索引indexSize加起来约3.6G,空间利用率45.65%,基本上一半的空间都是浪费的。
    对于使用普通SATA硬盘可能还可以接受,但是项目里为了提高速度使用了SSD硬盘,容量只有300G,按如此的使用率只能存一天多一点的数据。

    noprealloc:预分配方式。
    默认false:使用预分配方式来保证写入性能的稳定,预分配在后台进行,并且每个预分配的文件都用0进行填充。这会让MongoDB始终保持额外的空间和空余的数据文件,从而避免了数据增长过快而带来的分配磁盘空间引起的阻塞。
    设置noprealloc= true来禁用预分配的数据文件,会缩短启动时间,但在正常操作过程中,可能会导致性能显著下降。

    为了提高空间使用率,在启动MongoDB时使用了参数 --noprealloc 关闭了空间预分配,启动方式改变后查看库的统计信息:

    
    > use testDB
    switched to db testDB
    
    > db.stats()
    
    {
           "db" : "testDB",
           "collections" : 4,
           "objects" : 6150619,
           "avgObjSize" : 256.32243193733837,
           "dataSize" : 1576541620,
           "storageSize" : 1938201600,
           "numExtents" : 37,
           "indexes" : 6,
           "indexSize" : 2258045760,
           "fileSize" : 6373244928,
           "ok" : 1
    }
    
    

    fileSize分配了近6G的空间,实际存储的数据dataSize和索引indexSize加起来约3.5G,空间利用率60.17%,看的出来有了很大的提高,如此一块SSD硬盘上可以存两天的数据了,可以达到应用的要求了。

    有文章介绍说关闭预分配对性能有影响,不过从目前使用来看,并没有发现有显著的影响,也许是应用场景不一样,不同的场景可以采用不同的策略。

    参考

    MongoDB的空间分配
    https://zhuanlan.zhihu.com/p/19967617

    常见问题: MongoDB 存储
    https://mongoing.com/archives/27228

    相关文章

      网友评论

          本文标题:【mongoDB】mongoDB空间预分配机制

          本文链接:https://www.haomeiwen.com/subject/eicaortx.html