0%

MongoDB 数据类型及存储

What is MongoDB?

MongoDB is a document database with the scalability and flexibility that you want with the querying and indexing that you need.

Stores data in flexible, JSON-like documents.

Document => Collection => Database

ref: what-is-mongodb).

Data Type

BSON vs JSON

BSON [bee · sahn]), short for Binary JSON, is a binary-encoded serialization of JSON-like documents.

  1. 继承自 JSON,具备 JSON 的通用性与 schema-less(例如与 Protocol Buffers 比较);

  2. 扩展了 JSON 的数据类型,提供了 JSON 没有的一些数据类型,例如: Date 和 BinData(byte array,有了这种类型以后就不需要像 JSON 一样需要先 base64 编码再存储,减少了计算和存储的开销);

  3. BSON 的存储结构相比较 JSON 有更快的遍历速度;

  4. BSON 的存储有数据类型的支持,字段更新的时候更快更方便。JSON 的存储由于没有数据类型的支持,字段的更新需要移动文档的内容,操作代价大。

BSON Spec ref:bsonspec)

头部存有数据结构的长度,有数据类型的支持,遍历起来快,不用像 JSON 一样进行各种复杂的数据结构匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{"hello": "world"}

\x16\x00\x00\x00 // total document size
\x02 // 0x02 = type String
hello\x00 // field name
\x06\x00\x00\x00world\x00 // field value
\x00 // 0x00 = type EOO ('end of object')

{"BSON": ["awesome", 5.05, 1986]}

\x31\x00\x00\x00
\x04BSON\x00
\x26\x00\x00\x00
\x02\x30\x00\x08\x00\x00\x00awesome\x00
\x01\x31\x00\x33\x33\x33\x33\x33\x33\x14\x40
\x10\x32\x00\xc2\x07\x00\x00
\x00
\x00

BSON Types

BSON is a binary serialization format used to store documents and make remote procedure calls in MongoDB.

ref:https://docs.mongodb.com/v2.6/reference/bson-types/

Type Number Notes
Double 1
String 2
Object 3
Array 4
Binary Data 5
Undefined 6 deprecated
Object id 7
Boolean 8
Date 9
Null 10
Regular Expression 12
JavaScript 13
Symbol 14 deprecated
JavaScript (with scope) 15
32-bit integer 16
Timestamp 17
64-bit integer 18
Min key 255 Query with -1.
Max key 127

Comparison and Sort Order

  1. 不同数据类型之间的比较,类型之间有固定的比较优先级;

  2. 某些类型具有相同的优先级,比如:Numbers (ints, longs, doubles),比较之前先进行类型转换;

  3. non-existent field 与 empty field 等价,即 {}{a: null} 等价;

  4. Array 类型之间的比较排序,根据比较排序的类型选择 Array 中最小值(<,ASC)或最大值(>,DESC) 来比较;只有一个元素的 Array 与非 Array 的元素比较排序时,则直接使用数组中的唯一元素进行比较;Empty Array 将视为小于 Null 或者 缺少字段;

  5. String 类型使用的是 UTF-8存储,由不同编程语言的 driver 来负责序列化和反序列化 BSON。另外,由于 sort() 函数内部使用 C++ strcmp api,在多语言环境中排序会有问题。MongoDB v3.4 引入 collation) 参数。

ObjectId

Document 需要 _id 字段作为 primary key,Insert document 时如果没有手动指定的话,该字段由 Driver 或者 MongoDB Server 来负责自动生成。

MMAPv1 Storage Engine

ref:https://docs.mongodb.com/manual/core/mmapv1/

https://github.com/mongodb/mongo/tree/master/src/mongo/db/storage/mmap_v1

MMAPv1 是 MongoDB v3.2 之前的默认存储引擎,基于内存映射文件(memory mapped files),具有优秀的 Insert,read and in-place update 性能。v3.2 之后默认使用的是 WiredTiger 引擎。

MMAPv1 具有一下一些特点:

  • Journal,MongoDB 会先写 journal file(write-ahead redo logs),然后才是 data file,默认情况下 journal file 落地的时间是最大是 100ms,data file 落地的时间是最大是 60s(这是一个理论最大时间,实际上 OS 自身也有一个 flush 的落地操作)。这样当 MongoDB 发生故障的时候可以从 Journal 中恢复数据。

  • Document 连续地存储在磁盘中,当 Document 由于更新导致需要更多的存储空间时,需要重新分配空间并移动 Document 及更新相应的 Index。这会导致效率低下和存储碎片。

    • padding 模式, paddingFactor 存储空间冗余系数,1.0 表示没有冗余,1.5 表示 50% 的冗余,通常在 1.0 ~4.0 之间。这是 MongoDB 根据文档的频繁变化来调整的,我们无法控制,但是在 compact 的时候可以指定这个参数以控制压缩后的 Document 占用的实际空间:https://docs.mongodb.com/manual/reference/command/compact/#compact-paddingfactor

    • usePowerOf2Sizes 模式( db.collection.stats()),从 MongoDB v2.6 开始作为默认的存储分配方式:https://docs.mongodb.com/v2.6/reference/command/collMod/#usePowerOf2Sizes,按照 2 的 N 次方进行存储空间分配,最小分配 32 bytes,即 32,64,128,256…16777216。MongoDB v3.0 之前当 Document 的空间分配超过 4M 以后就会按照四舍五入的方式分配最接近的 N megabyte。v3.0 开始是达到 2M 以后按照 2M 为最小单位进行增长分配:https://docs.mongodb.com/manual/core/mmapv1/#power-of-2-sized-allocations

    • nopadding 模式,MongoDB v3.0 之后新增的一种分配方式,顾名思义最适合 insert-only 或者 update 不改变 Document size 的场景。

  • Free Memory 会尽可能(100%)被用来做 Cache 以提高性能。

  • Database Level Concurrency,数据库级别的锁。

  • 数据库层级分配存储文件,不会自动回收分配出去的空间。

WiredTiger 存储引擎相比较 MMAPv1:

  • Document Level Concurrency,文档那个级别的锁。

  • Snapshot and Checkpoint,故障恢复支持 Snapshot 和 Journal 的方式。

  • Collection 层级分配存储文件,并及时回收空间,优化压缩且可配置压缩算法。

  • v3.2 以后可以配置最大占用内存空间。

Data File Structure

MongoDB 的文件分为 3 种,分别是

  • Journal File,日志文件,存放在 MongoDB 数据目录下的 journal 子目录下;

  • Namespace File,命名空间文件,后缀名为 『.ns』 的文件;

  • Data File,数据文件,存放 Document 和 Index,以 Collection Name 作为文件名前缀的文件。

Journal File

ref:https://docs.mongodb.com/v2.6/core/journaling/

存储在单独的 『journal』子目录下面,以 『j._』 作为文件名前缀。MongoDB 启动的时候会默认创建 3 个大小为 1 G 的空 journal file(append-only) 备用,当一个 journal file 中操作全部落地以后,MongoDB 便会删除该 journal file,除非有持续海量的数据写入,否也一般只会有 2 ,3 个 journal file。正常的关闭 MongoDB 会删除 journal file。将 Journal File 和 Data File 放在不同的 FileSystem 上可以加速频繁顺序写的性能。

调整 MongoDB 默认文件的大小,可以将 Journal File 的默认大小从 1 G 改为 128M。

Namespace File

ref:https://docs.mongodb.com/v2.6/reference/limits/#namespaces

MongoDB 每个 database 中都会包含一个后缀名为 『.ns』 的文件,用于存储 namespce 信息,实现上是一个 Hash Table 可以快速地定位某个 namespce 在 Data File 中位置。namespace 长度最大为 120 bytes(包括 『.』 分隔符在内不超过 120 个字符)。namaspace 在对应的数据结构大小为 624 bytes,默认一个 namaspce file 的大小为 16 M,也就是说可以支持 16M/624=26715 个 namespace。最大可通过参数配置为 2 G 大小。

Namespace 的数据结构如下所示:

1
2
3
4
5
Node {
int hash;
Namespace key;
NamespaceDetails value;
};
  • hash: namespace 的 hash 值,使用的线性探测方式。

  • key:namaspce ,是一个长度为 120 bytes 的字符数组。

  • value:namespace 的详情,包括在 Data File 中起止位置(第一个 Extent 以及最后一个 Extent 的位置)及其他信息。

Data File

ref:https://docs.mongodb.com/v2.6/faq/storage/#preallocated-data-files

MongoDB 的 Data 和 Index 都存放在 Data File 中。为了防止文件系统碎片化, MongoDB 会以特定的 size 预先分配 Data File,命名从 0 开始:<database_name>.0, <database_name>.1…<database_name>.N, 第一个文件为 64M,第二个位 128M,直到 2G,之后都以 2G 大小来分配,也就是 MongDB 的 Data File 单个最大为 2G。

Data File 结构如上图所示:

  • 一个 Data File 由多个 Extent 组成,一个 Extent 由多个 Record 组成,Record 中存储 MongoDB Document。Extent 和 Record 都被实现成双向链表。

  • 一个 Extent 只会包含一个 Collection 的 Data 或者 Index,但是同一个 Extent 不会既有 Data 又有 Index。

  • 一个 Collection 由多个 Extent 组成,这些 Extent 可以分布在多个 Data File 中。

  • Document 删除或移动(Not in-place update)后留下的未被使用的 Record 会被标记为 『Deleted Record』 而可以重新分配出去,但不会主动回收。『Deleted Record』在实现上会按照不同的 Size 组织成链表,加速重新分配过程。删除 Document 释放出来的 Extent 会被放到 Data File 的 Extent Free List 中。

关于 db.stats() 的几个 Size 的区别与联系:

  • dataSize: Records Total Size,Record 中包括 Header + BSON Data + Padding。

  • storageSize/indexSize: Extents Total Size,由于包含了 Delete Record 所以比 dataSize 大。

  • fileSize: 文件系统中文件的大小,包括了 Data Extent 、Index Extent 以及未被使用的空间,不会随着 DB 中 Document 的删除而减少。

Insert Document

  1. 检查 Namespace 对应的 『Deleted Record』 中是否有合适 Size 的 Record,如果有则直接复用这个空间,写入 Document(注:Extent 中空闲空间会被作为一个 Big Deleted Record 处理);

  2. 检查 Data File 的 Extent Free List 里面是否有合适大小的空闲 Extent 可用,如果有则使用空闲的 Extent,写入 Document;

  3. 否则就要创建新的 Extent,写入 Document 。如果 Data File 没有足够的空间创建 Extent 则创建新的 Data File。

Delete Document

删除 Document 释放出来的 Record 会作为 『Deleted Record』 复用,但不会主动回收。无法被复用的 Record 就会成为存储碎片,需要通过 Compact 操作来改善。

Update Document

In-place Update,否则就是 Delete + Insert 操作。释放出来的 Record 作为 『Deleted Record』 被复用。非 In-place Update 会导致存储碎片,可以通过调整 Document 空间分配模式来改善。

Query Document

没有索引的情况下就只能遍历整个 Collection ,直到找到对应的 Record(Document)。建立索引可以加速查询。