【转】MongoDB性能优化实践总结

MongoDB性能优化,这里只总结了一下我们实践过的性能要点,作为回顾。

一. MongoDB服务端性能优化点

1. 限制连接数
Mongod 的服务模型是每个网络连接由一个单独的线程来处理,每个线程配置了1MB 的栈空间,当网络连接数太多时,过多的线程会导致上下文切换开销变大,同时内存开销也会上涨。另外,每个连接都要打开一个文件句柄,当然从成本上讲,这个消耗相对内存是小了很多。但换个角度,文件句柄也被其他模块消耗着,比如WT存储引擎,就需要消耗大量的文件句柄。

分布式shard集群部署环境的最大连接数通过route进程的配置文件的 net.maxIncomingConnections 指定,默认值为1000000,相当于没有限制,生产环境强烈建议根据Mongodb节点的实际需求配置,以避免客户端误用导致mongodb负载过高 。

2. 关闭数据库文件的atime

atime是linux文件系统记录的文件访问时间,大部分时候,它是没有用的。所以,在高IO,CPU wait高的情况下,关闭atime,可以提高性能。

【配置方法】在/etc/fstable中设置,如

/dev/sdc1 /var/ceilometer ext4 defaults
#改为
/dev/sdc1 /var/ceilometer ext4 defaults,noatime
#然后重新挂载
mount -o remount /var/ceilometer

3.使用XFS 文件系统

MongoDB在WiredTiger存储引擎下建议使用XFS文件系统。Ext4最为常见,但是由于ext文件系统的内部journal和WiredTiger有所冲突,所以在IO压力较大情况下表现不佳。

下面以ubuntu为例,使用XFS文件系统。

#安装xfs软件
apt install xfsprogs
#分区并格式化为 xfs
umount /dev/sdd1
fdisk /dev/sdd1
mkfs.xfs -f /dev/sdd1
#挂载
mount /dev/sdd1 /storage

同时修改开机挂载配置文件,/etc/fstab ,同时增加 noatime参数。

/dev/sdd1  /storage xfs  defaults,noatime  0  0

在高性能磁盘上,比如SSD或磁盘阵列,XFS性能比Ext4快约1倍。在低性能磁盘上,差距不明显。

4.提高Linux最大进程数/默认文件描述符限制

Linux默认的文件描述符数和最大进程数对于MongoDB来说一般会太低。官方建议把这个数值设为64000。因为MongoDB服务器对每一个数据库文件以及每一个客户端连接都需要用到一个文件描述符。如果这个数字太小的话在大规模并发操作情况下可能会出错或无法响应。
目前IoM给予的都比较大。
ulimit -n 64000
ulimit -u 64000

5.调小readahead(部分操作系统无需更改,默认就是优化后的值)

readhead 是磁盘预读字节,默认值是512KB。但是,由于MongoDB 随机访问数据的特点,所以不需要预读那么多,反而浪费内存。

【配置方法】
(1)查看readhead (RA)

/sbin/blockdev --getra /dev/sda

blockdev --report

结果为,即RA列值

RO RA SSZ BSZ StartSec Size Device
rw 1024 512 4096 0 193269334016 /dev/sda

(2)设置readhead为256,256*512字节/扇区=128KB,预读从512KB降到128KB

blockdev --setra 256 /dev/sda

二. MongoDB应用端性能优化点

1. 为cm,dm等模块访问DB的实际情况设置合适的MongoDB连接池大小

通常 MongoClient 使用默认100的连接池(具体默认值以 Driver 的文档为准)都没问题,当访问同一个 Mongod 的源比较多时,则需要合理的规划连接池大小。举个例子,Mongod 的连接数限制为2000,应用业务上有40个服务进程可能同时访问 这个Mongod,这时每个进程里的 MongoClient 的连接数则应该限制在 2000 / 40 = 50 以下 (连接复制集时,MongoClient 还要跟复制集的每个成员建立一条连接,用于监控复制集后端角色的变化情况)。

2. 对于多个字段的查询,建议使用组合索引,比交叉索引效率更好

如果你的查询会使用到多个字段,MongoDB有两个索引技术可以使用:交叉索引和组合索引。交叉索引就是针对每个字段单独建立一个单字段索引,然后在查询执行时候使用相应的单字段索引进行索引交叉而得到查询结果。交叉索引目前触发率较低,所以如果你有一个多字段查询的时候,建议使用组合索引能够保证索引正常的使用。

例如,如果应用需要查找所有年龄小于30岁的深圳市马拉松运动员:

db.T_DeviceData.find({deviceId: "584519b9-3340-4226-ab4e-49311a8b1c3d", appId: "HIxvSb_1dieBJr5IZY2g1zkb8Jga"})

则需要这样的一个索引:
db.T_DeviceData.ensureIndex({deviceId:1, appId:1});

3. 组合索引字段顺序:匹配条件在前,范围条件在后

在创建组合索引时如果条件有匹配和范围之分,那么匹配条件(deviceId: “584519b9-3340-4226-ab4e-49311a8b1c3d”) 应该在组合索引的前面。范围条件比如字段应该放在组合索引的后面。

db.T_DeviceData.find({deviceId: "584519b9-3340-4226-ab4e-49311a8b1c3d", appId: "HIxvSb_1dieBJr5IZY2g1zkb8Jga",timeStamp:{ $lt: new Date(1486828800000) }})

db.T_DeviceData.find({timeStamp:{ $lt: new Date(1486828800000) },deviceId: "584519b9-3340-4226-ab4e-49311a8b1c3d", appId: "HIxvSb_1dieBJr5IZY2g1zkb8Jga",})

的索引性能更好

4. 最重要但是优化代价最大的还是业务逻辑上的优

将变化相对较少的数据缓存在redis,memcache, 减少应用端对DB的访问频率,特别是设备登录,上报数据等频繁发生的业务 。

. MongoDB的profilling工具使用

开启profiling功能

有两种方式可以控制 Profiling 的开关和级别,第一种是直接在启动参数里直接进行设置。启动MongoDB 时加上–profile=级别 即可。也可以在客户端调用db.setProfilingLevel(级别) 命令来实时配置,Profiler 信息保存在system.profile 中。我们可以通过db.getProfilingLevel()命令来获取当前的Profile 级别,类似如下操作:

db.setProfilingLevel(2);

上面profile 的级别可以取0,1,2 三个值,他们表示的意义如下:

  1. 0 – 不开启

  2. 1 – 记录慢命令 (默认为>100ms)

  3. 2 – 记录所有命令

Profile 记录在级别1 时会记录慢命令,那么这个慢的定义是什么?上面我们说到其默认为100ms,当然有默认就有设置,其设置方法和级别一样有两种,一种是通过添加 –slowms 启动参数配置。第二种是调用db.setProfilingLevel 时加上第二个参数:

db.setProfilingLevel( level , slowms )
db.setProfilingLevel( 1 , 10 );

MongoDB Profile 记录是直接存在系统db 里的,记录位置system.profile ,所以,我们只要查询这个Collection 的记录就可以获取到我们的 Profile 记录了。列出执行时间长于某一限度(5ms)的 Profile 记录:

db.system.profile.find( { millis : { $gt : 5 } } )

MongoDB Shell 还提供了一个比较简洁的命令show profile,可列出最近5 条执行时间超过1ms 的 Profile 记录


以上原文链接:https://zhuanlan.zhihu.com/p/26328563 有增改。


===补充:配置优化项=====================================

更多配置说明参见官方文档https://docs.mongodb.com/manual/administration/production-notes/

  directoryPerDB: true

  engine: "wiredTiger"
  wiredTiger:
       engineConfig:
           cacheSizeGB: 16
           journalCompressor: zstd
       collectionConfig:
           blockCompressor: zstd

1、设定缓存大小,可根据机器总的可用内存分配。

cacheSizeGB: 16

建议设置一个固定值,如果留空不设置,mongod可能自动分配,分配原则为:当你的内存大于1GB,mongodb会用掉 内存的60% - 1GB 的内存作为缓存;当你的内存小于1GB,mongodb会直接用掉1GB。

2、文档压缩,节省存储空间

对于需要压缩的文档,可设置为全局 zlib 压缩,效果与gzip接近,压缩率高,同时在读取时会消耗一定的cpu占用。如果不设置,默认压缩方法为 snappy。4.2版本以上支持 zstd 压缩,比zlib压缩率更高,CPU占用更低。

blockCompressor: none

对于不需要压缩的内容,可关闭全局压缩。然后在创建集合时对单个集合单独设置。如下:对 数据库 dbname 中的集合 col_name 单独使用 zstd压缩。

mongo
use dbname
db.createCollection( "col_name", { storageEngine: { wiredTiger: { configString: 'block_compressor=zstd' }}})
db.col_name.stats()

3、使用 numactl 启动 MongoDB (未实测)

在多核架构高负载mongod主机中,需要占用较多内存的情况下,如果top命令的结果中,总有一个名叫irqbalance的进程居高不下,可考虑使用此方法。

NUMA:NUMA是多核心CPU架构中的一种,其全称为Non-Uniform Memory Access,简单来说就是在多核心CPU中,机器的物理内存是分配给各个核的,NUMA把内存分为本地和远程,每个物理CPU都有属于自己的本地内存,访问本地内存速度快于访问远程内存,缺省情况下,每个物理CPU只能访问属于自己的本地内存。

有下面几种访问控制策略:

1.缺省(default):总是在本地节点分配(分配在当前进程运行的节点上);

2.绑定(bind):强制分配到指定节点上;

3.交叉(interleave):在所有节点或者指定的节点上交织分配;

4.优先(preferred):在指定节点上分配,失败则在其他节点上分配。

对于MongoDB这种需要大内存的服务来说就可能造成内存不足,导致频繁进行内存交换(swap),引起性能下载。这里使用 numactl --interleave=all就是禁用NUMA为每个核单独分配。让cpu能访问远程内存,以提高mongodb的内存占用。


以下以Ubuntu为例,用numactl做为mongod的守护进程启动:

1)确认有 NUMA

ps --no-headers -o comm 1

如果是“ systemd”,则您的平台使用systemd初始化系统,您必须按照下面systemd选项卡中的步骤来编辑您的 MongoDB 服务文件。

如果“ init”,你的平台使用SysV初始化系统,并且你 也不需要执行此步骤。SysV Init 的默认 MongoDB init 脚本包括numactl默认启动 MongoDB 实例的必要步骤。

如果您管理自己的 init 脚本(即您没有使用这些 init 系统中的任何一个),您必须按照 下面自定义 init 脚本选项卡中的步骤来编辑您的自定义 init 脚本。

2)禁用 zone reclaim,使用命令

echo 0 | sudo tee /proc/sys/vm/zone_reclaim_mode
或
sudo sysctl -w vm.zone_reclaim_mode=0

3)安装 numactl

apt install numactl

找到 /etc/systemd/system/mongod.service 文件,在 ExecStart 行添加: /usr/bin/numactl --interleave=all,举例如下:

ExecStart=/usr/bin/mongod --config /etc/mongod.conf

改为:

ExecStart=/usr/bin/numactl --interleave=all /usr/bin/mongod --config /etc/mongod.conf

在systemctl 中生效

sudo systemctl daemon-reload
sudo systemctl stop mongod
sudo systemctl start mongod

4、设置 vm.swappiness 为1或0

原理同上,减少或禁用虚拟内存(小内存主机上建议设置为1,大内存主机可设置为0)。编辑文件 /etc/sysctl.conf 

vm.swappiness = 1

如果没有则新加一行,然后用以下命令使其生效

sudo sysctl -p

vm.swappiness 默认为60,即允许所有程序使用虚拟内存,设置1则只让系统内核使用虚拟内存,设置0则禁用虚拟内存。

5、提高 ulimit 的 1024 上限

方法和相关资料请参见官方文章 https://docs.mongodb.com/manual/reference/ulimit/  这里不再转述。

6、注意mongodb单条文档限制为最大16M

使用过程中需注意,暂未找到修改上限的方法。

7、开启Unix套接字 sock权限

默认配置虽然绑定了本机sock,但是sock权限为0600,只有mongodb自己能读取,使用其它程序将可能出现 Failed to connect to UNIX domain socket: Permission denied,需要添加权限配置为0666

net:
  port: 27017
  bindIp: 127.0.0.1

添加:修改文件权限为 0666 后重启mongodb生效。其它配置参数详见:https://docs.mongodb.com/manual/reference/configuration-options/ 

net:
  port: 27017
  bindIp: 127.0.0.1
  unixDomainSocket:
     enabled: true
     filePermissions: 0666