运营因素和数据模型
在本页面
为 MongoDB 建模应用程序数据既取决于数据本身,也取决于 MongoDB 本身的 Feature。例如,不同的数据模型可能允许应用程序使用更有效的查询,增加插入和更新操作的吞吐量或将活动更有效地分配给分片群集。
这些因素是可操作的或满足了在应用程序外部出现但影响基于 MongoDB 的应用程序性能的要求。开发数据模型时,请结合以下注意事项分析所有应用程序的读写操作。
Document Growth
在 3.0.0 版中更改。
对文档的某些更新可能会增加文档的大小。这些更新包括将元素推送到数组(即$push)并将新字段添加到文档中。
使用 MMAPv1 存储引擎时,文档增长可能是您数据模型的考虑因素。对于 MMAPv1,如果文档大小超出了为该文档分配的空间,则 MongoDB 将在磁盘上重新放置该文档。但是,在 MongoDB 3.0.0 中,默认使用2 大小分配的幂可以最大程度地减少这种重新分配的发生,并可以有效地重用已释放的记录空间。
使用 MMAPv1 时,如果您的应用程序需要进行更新,而这些更新通常会导致文档增长超过当前 2 分配的幂,则您可能需要重构数据模型以在不同文档中的数据之间使用引用,而不是使用非规范化数据模型。
您也可以使用“预分配”策略来明确避免文档增长。有关处理文档增长的“预分配”方法的示例,请参考预汇总报告用例。
有关 MMAPv1 的更多信息,请参见MMAPv1 存储引擎。
Atomicity
在 MongoDB 中,操作是document级别的原子操作。 “ **”单个写入操作不能更改多个文档。修改集合中多个文档的操作仍然一次只能处理一个文档。 [1]确保您的应用程序将所有具有原子依赖性要求的字段存储在同一文档中。如果应用程序可以容忍两个数据的非原子更新,则可以将这些数据存储在单独的文档中。
在单个文档中嵌入相关数据的数据模型有助于实现这些原子操作。对于在相关数据段之间存储引用的数据模型,应用程序必须发出单独的读取和写入操作以检索和修改这些相关数据段。
有关为单个文档提供原子更新的示例数据模型,请参见原子操作的模型数据。
[1] | 文档级原子操作包括单个 MongoDB 文档记录内的所有操作:影响该单个记录内的多个嵌入式文档的操作仍然是原子的。 |
Sharding
MongoDB 使用sharding提供水平扩展。这些集群支持具有大数据集和高吞吐量操作的部署。分片允许用户在数据库中partition _ collection跨多个mongod实例或shards分发集合的文档。
为了在分片集合中分发数据和应用程序流量,MongoDB 使用shard key。选择适当的shard key会对性能产生重大影响,并且可以启用或阻止查询隔离和增加的写入容量。务必仔细考虑用作分片键的一个或多个字段,这一点很重要。
Indexes
使用索引可以提高常见查询的性能。在查询中经常出现的字段以及返回排序结果的所有操作上构建索引。 MongoDB 会在_id
字段上自动创建一个唯一索引。
创建索引时,请考虑以下索引行为:
-
每个索引至少需要 8 kB 的数据空间。
-
添加索引会对写操作产生负面的性能影响。对于具有高读写比的集合,索引很昂贵,因为每个插入都必须同时更新任何索引。
-
具有较高读写比率的集合通常会受益于其他索引。索引不影响未索引的读取操作。
-
处于活动状态时,每个索引都会占用磁盘空间和内存。这种用法可能很重要,应该跟踪进行容量规划,尤其是对工作集大小的担忧。
有关索引以及分析查询性能的更多信息,请参见Indexing Strategies。此外,MongoDB database profiler可能有助于识别效率低下的查询。
大量收藏
在某些情况下,您可能选择将相关信息存储在多个集合中,而不是存储在单个集合中。
考虑一个 samples 集合logs
,该 samples 集合存储了各种环境和应用程序的日志文档。 logs
集合包含以下格式的文档:
{ log: "dev", ts: ..., info: ... }
{ log: "debug", ts: ..., info: ...}
如果文档总数很少,则可以按类型将文档分组到集合中。对于日志,请考虑维护不同的日志集合,例如logs_dev
和logs_debug
。 logs_dev
集合将仅包含与开发环境有关的文档。
通常,具有大量集合不会造成明显的性能损失,并且会导致非常好的性能。不同的集合对于高通量批处理非常重要。
当使用具有大量集合的模型时,请考虑以下行为:
-
每个集合都有一定的最小开销,只有几千字节。
-
每个索引(包括
_id
上的索引)至少需要 8 kB 的数据空间。 -
对于每个database,一个名称空间文件(即
<database>.ns
)存储该数据库的所有元数据,并且每个索引和集合在名称空间文件中都有其自己的条目。 MongoDB 放置命名空间文件大小的限制。 -
使用
mmapv1
存储引擎的 MongoDB 具有命名空间数量的限制。您可能希望知道名称空间的当前数目,以确定数据库可以支持多少个其他名称空间。要获取当前的名称空间数量,请在mongo shell 中运行以下命令:
db.system.namespaces.count()
名称空间数量的限制取决于<database>.ns
的大小。命名空间文件默认为 16 MB。
要更改* new *命名空间文件的大小,请使用选项--nssize<new size MB>启动服务器。对于现有数据库,使用--nssize启动服务器后,从mongo shell 运行db.repairDatabase()命令。有关运行db.repairDatabase()的影响和注意事项,请参见repairDatabase。
集合包含大量小文件
如果您有一个包含大量小文档的集合,则出于性能原因,应考虑嵌入。如果您可以按某种逻辑关系对这些小文档进行分组并且经常通过此分组来检索文档,则可以考虑将这些小文档“汇总”为包含嵌入式文档数组的较大文档。
将这些小型文档“汇总”为逻辑分组意味着,检索一组文档的查询涉及 Sequences 读取和较少的随机磁盘访问。此外,“汇总”文档并将公共字段移动到较大的文档对这些字段上的索引有利。公共字段的副本将更少,并且相应索引中的关联键条目将更少。有关索引的更多信息,请参见Indexes。
但是,如果您通常只需要检索组中文档的子集,则“汇总”文档可能无法提供更好的性能。此外,如果小的单独文档代表数据的自然模型,则应维护该模型。
小文档的存储优化
每个 MongoDB 文档都包含一定量的开销。这种开销通常是微不足道的,但是如果所有文档都只有几个字节,那么这将变得很重要,如果您集合中的文档只有一个或两个字段,则可能会发生这种情况。
请考虑以下建议和策略,以优化这些集合的存储利用率:
- 明确使用
_id
字段。
MongoDBClient 端会自动为每个文档添加一个_id
字段,并为_id
字段生成一个唯一的 12 字节的ObjectId。此外,MongoDB 始终为_id
字段编制索引。对于较小的文档,这可能会占用大量空间。
为了优化存储使用,在将文档插入集合中时,用户可以为_id
字段显式指定一个值。此策略允许应用程序将_id
字段中存储的值存储在文档另一部分中。
您可以在_id
字段中存储任何值,但是由于此值用作集合中文档的主键,因此它必须唯一地标识它们。如果该字段的值不是唯一的,则该字段不能用作主键,因为在集合中会发生冲突。
- 使用较短的字段名称。
Note
缩短字段名称会降低表达能力,并且对于较大的文档以及文档开销不是很重要的情况,不会带来很大的好处。较短的字段名称不会减少索引的大小,因为索引具有 sched 义的结构。
通常,没有必要使用短字段名。
MongoDB 在每个文档中存储所有字段名称。对于大多数文档,这只占文档使用空间的一小部分;但是,对于小型文档,字段名称可能会按比例表示大量的空间。考虑一个类似于以下内容的小文件的集合:
{ last_name : "Smith", best_score: 3.9 }
如果按照以下方式将名为last_name
的字段缩短为lname
并将名为best_score
的字段缩短为score
,则每个文档可以节省 9 个字节。
{ lname : "Smith", score : 3.9 }
- Embed documents.
在某些情况下,您可能希望将文档嵌入其他文档中,并节省每个文档的开销。参见集合包含大量的小文件。
数据生命周期 Management
数据建模决策应考虑数据生命周期 Management。
集合生存时间或 TTL 功能会在一段时间后使文档过期。如果您的应用程序需要一些数据在有限的时间内保留在数据库中,请考虑使用 TTL 功能。
此外,如果您的应用程序仅使用最近插入的文档,请考虑Capped Collections。上限集合提供对插入文档的“先进先出”(FIFO)Management,并有效地支持根据插入 Sequences 插入和读取文档的操作。