MongoDB WiredTiger引擎调优技巧
- 调优Cache Size
WiredTiger
最重要的调优参数就是cache
规模。默认,MongoDB
从3.x
开始会保留可用物理内存的50%(3.2
是60%)作为数据cache
。虽然,默认的设置可以应对大部分的应用,通过调节为特定应用找到最佳配置值还是非常值得的。cache
的规模必须足够大,以便保存应用整个工作集(working set)。
除了这个cache
,MongoDB
在做诸如聚合、排序、连接管理等操作时需要额外的内存。因此,必须确保有足够的内存可供使用,否则,MongoDB
进程有被OOM killer
杀死的风险。
调节这个参数,首先要理解在默认配置下,cache
的使用情况。运行以下命令,可以获得cache
统计:
db.serverStatus().wiredTiger.cache
{
"tracked dirty bytes in the cache" : 409861,
"tracked bytes belonging to internal pages in the cache" : 738956332,
"bytes currently in the cache" : 25769360777,
"tracked bytes belonging to leaf pages in the cache" : 31473298388,
"maximum bytes configured" : 32212254720,
"tracked bytes belonging to overflow pages in the cache" : 0,
"bytes read into cache" : 29628550664,
"bytes written from cache" : 34634778285,
"pages evicted by application threads" : 0,
"checkpoint blocked page eviction" : 102,
"unmodified pages evicted" : 333277,
"page split during eviction deepened the tree" : 0,
"modified pages evicted" : 437117,
"pages selected for eviction unable to be evicted" : 44825,
"pages evicted because they exceeded the in-memory maximum" : 74,
"pages evicted because they had chains of deleted items" : 33725,
"failed eviction of pages that exceeded the in-memory maximum" : 1518,
"hazard pointer blocked page eviction" : 34814,
"internal pages evicted" : 21623,
"maximum page size at eviction" : 10486876,
"eviction server candidate queue empty when topping up" : 8235,
"eviction server candidate queue not empty when topping up" : 3020,
"eviction server evicting pages" : 191708,
"eviction server populating queue, but not evicting pages" : 2996,
"eviction server unable to reach eviction goal" : 0,
"pages split during eviction" : 8821,
"pages walked for eviction" : 157970002,
"eviction worker thread evicting pages" : 563015,
"in-memory page splits" : 52,
"percentage overhead" : 8,
"tracked dirty pages in the cache" : 9,
"pages currently held in the cache" : 1499798,
"pages read into cache" : 2260232,
"pages written from cache" : 3018846
}
第一个要关注的数值试,`cache`中脏数据的百分比。如果这个百分比比较高,那么调大`cache`规模很有可能可以提升性能。如果应用是重读的,可再关注`bytes read into cache`这个指标。如果这个指标比较高,那么调大`cache`规模很有可能可以提升读性能。
- 动态调整WiredTiger
db.adminCommand( { "setParameter": 1, "wiredTigerEngineRuntimeConfig": "cache_size=xxG"})
- 控制Read/Write Tickets
WiredTiger使用tickets
来控制可以同时被存储引擎处理的读/写操作数。默认值是128,在大部分情况下表现良好。如果这个值经常掉到0,所有后续操作将会被排队等待。例如,观察到读tickets
下降,系统可能有大量长耗时的操作(未索引操作)。如果你想找出有哪些慢操作,可以用一些第三方工具。你可以根据系统需要和性能影响上下调节tickets
。
运行以下命令可以确认tickets
的使用情况:
db.serverStatus().wiredTiger.concurrentTransactions
{
"write" : {
"out" : 0,
"available" : 128,
"totalTickets" : 128
},
"read" : {
"out" : 3,
"available" : 128,
"totalTickets" : 128
}
}
- 可以动态调节
tickets
:
db.adminCommand( { setParameter: 1, wiredTigerConcurrentReadTransactions: xx } )
db.adminCommand( { setParameter: 1, wiredTigerConcurrentWriteTransactions: xx } )
一次Mongodb异常宕机崩溃
一次 mongoDB 异常崩溃地址串
- mongos 退出时的日志如下表所示,报 “Failed to mlock: Cannot allocate memory” 错误。同时,分析故障发生时分片集群中多个主节点的日志,发现都有多个连接在执行耗时较长的查询操作。
2017-12-14T18:01:43.047+0800 I ASIO [NetworkInterfaceASIO-TaskExecutorPool-19-0] Successfully connected to 192.168.1.100:27017, took 291ms (10 connections now open to 192.168.1.100:27017)
2017-12-14T18:01:43.048+0800 I ASIO [NetworkInterfaceASIO-TaskExecutorPool-19-0] Connecting to 192.168.1.200:27017
2017-12-14T18:01:43.048+0800 I ASIO [NetworkInterfaceASIO-TaskExecutorPool-19-0] Connecting to 192.168.1.200:27017
2017-12-14T18:01:43.050+0800 F - [NetworkInterfaceASIO-TaskExecutorPool-22-0] Failed to mlock: Cannot allocate memory
2017-12-14T18:01:43.050+0800 I - [NetworkInterfaceASIO-TaskExecutorPool-22-0] Fatal Assertion 28832 at src/mongo/base/secure_allocator.cpp 246
2017-12-14T18:01:43.050+0800 I - [NetworkInterfaceASIO-TaskExecutorPool-22-0] …
2017-12-14T18:01:43.054+0800 F - [NetworkInterfaceASIO-TaskExecutorPool-22-0] Got signal: 6 (Aborted).
- 故障分析已经重现
初步分析触发故障的原因是大量慢查询导致了 mongos 与 mongod 之间的连接长时间没有释放,使得连接池中的连接无法复用,开始新建连接,而新建连接需要分配一定的锁定内存。mongos 需要分配的锁定内存超过 max locked memory 的限制,于是程序崩溃退出。max locked memory 的默认值如下图所示是 64KB(这个限制是对整个进程的,而且限制只对普通用户起作用,对 root 用户不起作用).
[mongodb@mongodb4 ~]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 63717
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 59999
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 59999
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
- 解决方法
1、调整 max locked memory 的大小。根据之前的分析发现是 mongos 对锁定内存的分配超出了操作系统的限制,所以可以通过调整该限制来解决问题。官方在修复 bug 之前,也是推荐增加 max locked memory 的限制。调整方式如下:ulimit -l 96,该命令将限制值设置为 96KB。具体调整为多少官方没有说明,之后分析源码发现每一个连接占用的锁定内存是 60 个字节,但锁定内存不仅仅在此处用到。所以如果每个连接的请求都发生慢查询,那么 64KB 理论上支持的最大连接数是 1092 个。
2、升级到 3.4.6 及以上版本。当时线上 mongos 和 mongod 版本是 3.4.4,我们将 mongos 和 mongod 升级到 3.4.9,使用相同的方式进行测试,发现不会发生类似故障。
MongoDB 官网有 issue 提到这个问题 SERVER-28997,这是 3.4.4 版本的 bug,而我们当时线上环境使用的正好也是 3.4.4 版本,之后 3.4.6 版本已经修复。官方描述是:
SasLTCHa1clipse会话有一个将从缓存中拔出的加密。
RADIONTS在默认构造函数中分配安全存储,因此可以填充它们。
相反,SASLTCHAK1clitTalk和缓存应该将SysDypRTs存储到SCRAMSecret。
生产环境shard配置文件分享
logpath=/home/mongodb/log/shard11.log
pidfilepath=/home/mongodb/shard11.pid
logappend=true
bind_ip=192.168.128.10
port=27017
fork=true
replSet=shard1
dbpath=/home/mongodb/data/shard11
oplogSize=10000
noprealloc=true
shardsvr=true
directoryperdb=true
storageEngine = wiredTiger
wiredTigerCacheSizeGB = 3
syncdelay = 30
wiredTigerCollectionBlockCompressor = snappy
Mongo日志自动分卷脚本
- mongos_log.sh 可以吧mongos、config都添加上,然后在添加上linux 的定时任务。则每天定时日志分卷
[mongodb@mongodb4 ~]$ more mongos_log.sh
/home/mongodb/mongodb_3.2.12/bin/mongo 192.168.128.19:27021/admin <
错误的Mongodb分片的选择
-
Mongodb运行现状
- mongodb集群是3台服务器三个副本集群,每台服务器配置mongos、confi、mongod(主、从、仲裁)5个进程
场景说明
近期线上Mongodb服务器某一台服务器负载情况特别高,单个集合shard主节点占用CPU负载很高,使用mongotop 和mongostat命令分析,发现某个副本集合,要比其他两个集合要操作频繁,而从影响线上数据入库导致延迟。
-
原因
- 是因为当初创建表时,使用的start_time来作为片键使用的是hash,也就导致入库hash到某个集合时,频繁操作入库都是在这个shard集群上面,也就导致服务器负载压力特别高
-
解决方法
- 使用多字段创建分键,先导出数据、然后删除表、创建分片、在导入数据。