3.5 固定集合

3.5.1 固定集合简介

固定集合(capped collection)是一种限定大小的集合,其中capped是覆盖、限额的意思。跟普通的集合相比,数据在写入这种集合时遵循FIFO原则。可以将这种集合想象为一个环状的队列,新文档在写入时会被插入队列的末尾,如果队列已满,那么之前的文档就会被新写入的文档所覆盖,如图3-5所示。

图3-5 固定大小的集合

这是一个很有意思的设计,通过固定集合的大小,我们可以保证数据库只会存储“限额”的数据,超过该限额的旧数据都会被丢弃。

对于普通的集合来说,如果想实现对于大小的限定,即我们需要做的事情如下:

● 设定集合的大小上限,比如文档的数量或是该集合所占用的存储空间大小,作为阈值。

● 开始向该集合中写入数据。

● 在写入数据后判断集合大小是否超过限额,通过collection.count命令可以获得文档的数量,而存储空间大小则可以使用collection.stat命令获得。

● 如果超过了限额,那么找出最早的一条数据将其删除。

上述方案有很多缺点,比如每次写入都需要进行一次判断和数据删除操作,对性能的影响是不小的,同时应用在代码实现上也比较复杂。相比之下,固定集合(capped collection)是在数据库层面进行处理,除了更加方便,可靠性也更有保证。

3.5.2 使用示例

通过下面的语句可以声明一个固定集合:

这里指定了两个参数。

(1)max:指集合的文档数量最大值,这里是10条。

(2)size:指集合的空间占用最大值,这里是4096字节(4KB)。

这两个参数会同时对集合的上限产生影响。也就是说,只要任一条件达到阈值都会认为集合已经写满。其中size是必选的,而max则是可选的。

我们尝试在这个集合中插入15条数据,代码如下:

接着尝试查询集合里的数据,可以看到,由于文档数量上限被设定为10条,前面插入的5条数据已经被覆盖了,结果如下:

当然,如果我们不指定max参数,那么当文档的总大小(空间占用)超过size限额时,也会产生覆盖的情况:

可以使用collection.stats命令查看文档的占用空间,代码如下:

其中,"size":530表示文档总大小为530字节,而"maxSize":4096则是文档总大小的上限。

需要注意,maxSize的值必须是2的n 次方。如果设定值不符合条件,则会被自动对齐,比如创建固定集合时指定"size":500,那么最终的maxSize就是512字节。

3.5.3 特征与限制

固定集合在底层使用的是顺序I/O操作,而普通集合使用的是随机I/O。众所周知,顺序I/O在磁盘操作上由于寻道次数少而比随机I/O要高效得多,因此固定集合的写入性能是很高的。此外,如果按写入顺序进行数据读取,也会获得非常好的性能表现。

但它也存在一些限制,主要有如下5个方面:

(1)无法动态修改存储的上限,如果需要修改max或size,则只能先执行collection.drop命令,将集合删除后再重新创建。

(2)无法删除已有的数据,对固定集合中的数据进行删除将会得到如下错误:

(3)对已有数据进行修改,新文档大小必须与原来的文档大小一致,否则不允许更新:

(4)默认情况下,固定集合只有一个_id索引,而且最好是按数据写入的顺序进行读取。当然,也可以添加新的索引,但这会降低数据写入的性能。

(5)固定集合不支持分片,同时,在MongoDB 4.2版本中规定了事务中也无法对固定集合执行写操作。

3.5.4 适用场景

固定集合很适合用来存储一些“临时态”的数据。“临时态”意味着数据在一定程度上可以被丢弃。同时,用户还应该更关注最新的数据,随着时间的推移,数据的重要性逐渐降低,直至被淘汰处理。

一些适用的场景如下:

(1)系统日志,这非常符合固定集合的特征,而日志系统通常也只需要一个固定的空间来存放日志。在MongoDB内部,副本集的同步日志(oplog)就使用了固定集合。

(2)存储少量文档,如最新发布的TopN条文章信息。得益于内部缓存的作用,对于这种少量文档的查询是非常高效的。