如何优化MongoDB写入性能?

MongoDB Quarterback 336℃ 0评论

本文主要讨论这几个问题:

  1. MongoDB执行写入的流程
  2. 什么是WriteConcern
  3. 优化1:缩短请求响应流程
  4. 优化2:减少请求次数
  5. 优化3:合理的并发连接数
  6. 优化4:减少(随机)IO
  7. 优化5:优化(随机)IO
  8. 优化6:减少并发读写锁竞争
  9. 优化7:使用分片集群
  10. 优化8:其他

1. MongoDB执行写入的流程

要优化MongoDB的写入,我们首先需要大致了解MongoDB在接收到写请求后,写操作在MongoDB内部的一个写入流程:
1)数据首先会写入到MongoDB缓存中。
2)然后缓存会定时异步刷写到journal日志文件中(journal是MongoDB的预写日志,相当于mysql的redo log,这一步就意味着数据已经持久化)
3)然后会定时异步刷写到MongoDB的数据文件中(journal的刷写时间比数据文件的刷写时间更频繁)
4)如果是插入操作或更新操作中包含索引列,同时会维护索引结构。
5)如果是副本集的话,数据还会写到oplog定容集合中(从节点会跟踪并同步这个集合中的写操作,进行回放来达到复制数据的目的)

2. WriteConcern

这里还需要了解一个影响MongoDB写操作响应时间的参数:安全写级别(WriteConcern),这个参数描述了 MongoDB 在返回一个写操作成功前应该提供的保证,这里的保证也就是说在执行到上述哪一步的时候,MongoDB就可以认为写操作成功并返回响应给客户端。所以越严格的安全写级别,也意味着要在执行更多步骤后才返回客户端响应,也就意味着更长的响应时间。

3. 优化1:缩短请求响应流程

这里主要就是指安全写级别的设置,安全写级别主要包括两个选项:w和j,其中w指返回前需要确认的次数(比如w=1表示只需要主节点确认,w=2表示主节点和至少一个从节点确认… j=1表示需要成功写到journal文件)

一般来说,使用默认的设置w=1, j=0即可(即主节点确认收到写请求即返回)。只有我们需要更严格的数据安全持久化保证的时候,才需要设置更严格writeConcern。

除了设置安全写级别来简化确认的步骤,大多数语言的驱动已经提供了异步写(async)操作的支持,可以避免上述同步等待。

4. 优化2:减少请求次数

一般的写操作,每次写入一个文档,也就是说对于每个文档,客户端都会发起一次写操作请求。因此,我们可以通过采取批量写的方式,一次提交多个文档,来减少请求的次数。从而减少请求响应的时间,达到优化写入的目的。

MongoDB提供了写入文档数组,以及Bulk Write两种批量提交的方式。

5. 优化3:合理的并发连接数

驱动一般都提供了连接池的方式处理客户端连接请求,通过池化的连接,复用连接减少连接创建的消耗。但是一定要注意,虽然连接数越多意味着越大的并发处理能力,但连接数不应该设置过大,因为对于服务器端来说,每个连接都需要启动一个线程进行处理,而每个线程通常默认都需要1M的内存开销,另外,太多线程,线程切换的开销也很大。因此,过大的连接数设置反而会对性能造成影响。

6. 优化4:减少(随机)IO

减少不必要的或重复的索引(复合索引左前缀重复),以避免更新或插入时对索引的维护开销。

适当增大刷写journal和数据文件的时间间隔,这样等于减少了IO的频率(但要注意,这样的代价是数据持久化不及时,丢失的可能性增大)。

作为主键的_id值应该避免使用随机值(比如md5, UUID之类的值),而应该使用自增值(比如默认的ObjectId),因为btree索引叶子节点是按照值顺序存放的,这样有利于_id上的范围查询。

避免内存太小,如果内存不足,可能导致频繁的缺页交换(page fault)。

7. 优化5:优化(随机)IO

如果随机IO成为写入瓶颈,可以考虑使用SSD固态硬盘替代机械磁盘。

如果开启了journal日志,可以考虑将journal日志和数据文件分别放置在不同的块设备上,以减少它们之间写操作对设备的竞争影响。

对于不需要修改或随机查询的业务,可以考虑使用定容集合代替一般集合,因为定容集合总是采用append,顺序写的方式。

7. 优化6:减少并发读写锁竞争

2.x 版本MMAPv1存储引擎的时候,读写锁粒度很粗(version < 2.2进程级别锁,2.2<= version < 2.8库级别锁,3.0 <= version集合级别锁),高并发读写情况下,锁竞争十分激烈。

3.x版本WiredTiger存储引擎后,支持文档级别锁,锁竞争相对MMAPv1减小很多。

除了升级版本为WiredTiger细化锁粒度外,还可以通过配置读写分离,来避免读写操作之间对读写锁的竞争。

7. 优化7:使用分片集群

如果写入性能已经达到单机写入性能瓶颈,则应该考虑使用分片集群来分担写入压力。通过设置合理的片键,可以将并发写入操作分发路由到不同的分片节点上,从而提高了并发写入的处理能力。

10. 优化8:其他

对于2.x使用MMAPv1存储引擎的时候,还要注意,文档修改是采用update in place,即在原文档存储位置进行修改,如果修改后文档的大小会超过预先分配的文档存储空间大小,那么就会导致需要重新分配一个足够大小的空间,然后将文档迁移到新的文档存储位置中,这样会导致更多额外的空间分配和IO,影响写入速度。

在WiredTiger中,高并发写入情况下,如果发现dirty(缓存中脏数据比例)过高,写入速度严重下降的时候,可以通过优化缓存相关eviction参数或使用SSD提到IO性能,使缓存中脏数据提早刷盘、尽快刷盘,或者增大缓存大小,降低dirty比例。

如果你使用的是企业版,还可以通过使用In-Memory存储引擎来提升写性能。比如,部署2台In-Memory节点作为主从节点,再部署一台WiredTiger作为隐藏的从节点,构成一个副本集,两台In-Memory节点承担业务的读写请求,另外一台WiredTiger则对用户透明,只作为从节点复制数据。这样既保留了In-Memory的低延迟读写特性,又使得数据不至于在In-Memory节点挂掉后彻底丢失。

 

/* 本文属于原创文章,转载请注明作者和出处 quarterback.cn,请勿用于任何商业用途 */



喜欢 (0)or分享 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址