Skip to main content

【译】运维因素和数据模型


针对MongoDB的应用数据进行建模会考虑数据本身以及MongoDB本身的特征。例如,不同的数据模型可能会为应用提供更高效的查询,提高插入和更新操作的吞吐量,或者将行为更高效地分发到一个分片集群中。

这些因素都是运维性质的或者能够解决应用之外但是能够基于应用影响MongoDB性能需求的。当开发一个数据模型时,结合下面的考虑分析我们应用的读写操作。

文档增长

从3.0.0版本开始
对文档的一些更新操作可能会增加文档的大小。这些操作包括向一个数组中插入元素(例如$push)和向文档中增加新的字段。

当使用MMAPv1存储引擎时,文档大小的增长是我们数据模型的一个考虑因素。对于MMAPv1。如果我们的文档大小超过了给该文档分配的空间,MongoDB将会将文档放在磁盘的另一个位置。但是,从MongoDB 3.0.0开始,2的幂次方大小分配的默认使用最小化了这种重新分配情况的出现,并且也提供了剩余记录空间的重用。

在使用MMAPv1时,如果我们的应用需要的更新会频繁地造成文档大小超过当前2的幂次方分配空间,我们可能需要重新定义我们的数据模型,例如在唯一文档的数据之间使用关联,而不是使用一个非范式的数据模型。

我们也许会使用一个预分配的策略来显式地避免文档的增长。

原子性

在MongoDB中,操作在文档级别具有原子性。单一的写操作只会修改至多一个文档。修改集合中多个文档的操作每次也是只会操作一个文档。确保我们的应用在相同的文档中满足原子依赖需求来存储所有的字段。如果我们的应用能够容忍两个数据段的非原子性更新,那么我们可以将这些数据存储在不同的文档中。

在一个单一文档中嵌入相关数据的数据模型为这些原子性操作的类型提供了方便。对于存储相关数据段之间应用的数据模型,应用必须进行单独的读写操作来检索和修改这些相关的数据段。

分片

MongoDB使用分片来提供横向拓展。这些集群支持大型数据集和高吞吐操作的部署。分片允许用户在一个数据库中合并集合,将集合的文档发送到一系列的mongod实例或分片中。

如果想要在分片集合中分发数据和应用流量,MongoDB使用片键。选择合适的片键对性能有非常重大的影响,可以启动或防止查询分离,提高写能力。仔细考虑用作片键的字段是非常重要的。

索引

使用索引来提高常用查询的性能。在经常出现于查询的字段上和所有操作中返回排序结果的字段上创建索引。MongoDB自动在_id字段上创建唯一索引。

在创建索引的时候,考虑索引的以下功能:
– 每个索引需要至少8kB的数据空间
– 增加作引对写操作有一些负面的性能影响。对于写读比率比较高的集合,索引时非常昂贵的,因为每个插入必须更新所有索引。
– 读写比非常高的集合经常
会从额外的索引中受益。索引并不会影响非索引的读操作。
– 在活跃的时候,每个索引都会消耗磁盘空间和内存。索引的使用时非常重要的,应该使用容量查询跟踪,特别是关注下工作集大小。

大量集合

在一些情况下,我们可能会选择在多个集合中存储相关信息而不是在一个单一的集合中。

考虑存储着不同环境和应用的日志文档示例集合logslogs集合包含下列形式的文档:

{ log: "dev", ts: ..., info: ... }
{ log: "debug", ts: ..., info: ...}

文档的总数目非常小,我们可以根据类型将文档分组到集合中。对于日志,考虑维护不同的日志集合,例如logs_devlogs_debuglogs_dev集合只会包含与开发环境相关的文档。

一般说来,有大数据量的集合没有重大的性能副作用,并且会带来非常好的性能。分开的集合对于高吞吐批量处理是非常重要的。

在使用有大量数目的集合的数据模型时,考虑下面的功能:
– 每个集合有确定最小数目为kb级别的开销
– 每个索引,包含_id字段上的索引,要求至少8kB的数据空间
– 对于每个数据库,一个单一的命名空间文件(例如,<database>.ns)存储着该数据库的所有元数据,每个索引和集合在命令空间文件中自己的条目。MongoDB在命名空间文件上设置了限制
– 使用mmapv1存储引擎的MongoDB有命名空间的数目限制。我们可能会想知道当前命名空间的数目,以便于决定数据库可以支持多少个其它的命名空间。可以在mongo shell中运行下面的命令以获取当前命名空间的数目:

db.system.namespaces.count()

命名空间数目的限制依赖于<database>.ns的大小。默认的命名空间文件大小为16MB。
如果想要修改命名空间文件的大小的话,使用--nssize<new size MB>选项启动服务器。对于已有数据库,在使用--nssize启动服务器,在mongo shell中运行db.repairDatabase()命令。

包含大量小文档的集合

为了性能因素,如果我们有一个包含了大量小文档的集合,我们可以考虑嵌入。如果我们可以将这些小文档使用一些逻辑关系进行分组并且我们会频繁地更具这些分组检索文档,那么我们可以考虑将这些小文档集合到包含一个嵌入文档数组的大文档中。

将这些文档打包到逻辑分组中意味着检索一系列文档的查询包含顺序读和更少的堆积磁盘读取。此外,将文档聚合并且将通用的字段移动到更大的文档中将会为这些字段上的索引提供方便。将会有更少的通用字段的复制并且会在想用的索引中有更小的相关件条目。

然而,如果我们经常只需要检索组内的文档子集,那么聚合文档可能不会提供更加的性能。进一步地,如果单独的文档表示数据的自然模型,我们应该维护该模型。

小文档的存储优化

每个MongoD文档包含一定数量的支出。一般说来,这些支出都是无关紧要的,但是如果所有文档都只是非常少的字节,可能会造成大的影响。例如,如果我们集合中的文档只有一个或两个字段时,就会出现这种情况。

考虑下面的建议和策略用来对这些集合优化存储利用率:
– 显式使用_id字段。MongoDB客户端自动向每个文档添加一个_id字段。此外,MongoDB通常会对_id字段创建索引。对于更小的文档,这可能会占用大量空间。为了优化存储使用,用户可以在向集合插入文档之前显式指定_id字段的值。这个策略允许应用在_id字段存储一个值,本来这个值是存储在文档另一个部分的空间。我们可以在_id字段存储任何值,但是由于这个字段时作为集合中文档的主键的,它必须能够唯一标识这些文档。如果该字段的值不是唯一的,那么这个值就不能作为主键,因为这将会造成集合中的碰撞。
– 使用更短的字段名称。
注意:更短的字段名称减少了可表达性,但是对更大的文档提供客观的收益,因为文档的支出不是重要的关心点。更短的字段名称不会降低索引的大小,因为索引有一个预先定义好的结构。一般来说,没有必要使用短的字段名称。

MongoDB在每个文档中存储所有的字段名称。对于大多数文档而言,这是文档使用的非常一小部分。然而,对于小文档而言,字段名称可能会组成庞大的空间。考虑一个类似于下面的小文档集合:

{ last_name : "Smith", best_score: 3.9 }

如果我们将名为lats_name的字段简化为lname,将名为best_score的字段改为score,那么我们将会为每个文档结余9个字节。

{ lname : "Smith", score : 3.9 }
  • 嵌入文档。在一些情况下,我们可能会想要在其它文档中嵌入文档来节约每个文档的支出。

数据生存周期管理

数据模型的选择应该将数据的生存周期管理考虑进去。

集合的Time to Live或者是TTL功能将会使得文档在一定的时候后过去。如果我们的应用要求数据只在数据库中保留一个有限的时间段,可以考虑使用TTL功能。

此外,如果把我们的应用只会使用到最近插入的文档,可以考虑使用限制集合。限制集合提供了对插入文档先进先出的管理,能够高效地支持基于插入顺序的插入和读取文档的操作。

打赏
微信扫一扫支付
微信logo微信扫一扫, 打赏作者吧~

mickey

记录生活,写给几十年后的自己。