我最近遇到了一个有趣的MongoDB集群平衡过程中的性能问题。通过对日志的深入研究,我们发现这个问题与块移动花费了很长时间有关。
正如我们所知,默认的最大块大小是64 MB。因此,这些迁移应该在目前使用的大多数硬件中非常快。
在这种情况下,有几个远远超过限制的块被移动。这是怎么发生的?这些块不应该被标记为Jumbo吗?
回顾一下大块移动
首先,让我们回顾一下我们对块移动的了解。
如果一个块中的文档数大于配置的块大小(默认64mb)除以平均文档大小的1.3倍,MongoDB不会移动该块。
如果我们有一个平均文档大小为2 KB的集合,那么超过42597(65535 / 2 * 1.3)个文档的块将无法平衡。对于文档大小不统一的集合来说,这可能是一个问题,因为在这种情况下估计不是很好。
此外,如果一个块超过了最大大小,它将被标记为Jumbo。这意味着平衡器不会试图移动它。您可以查看下面的文章了解更多关于处理大块。
最后,我们必须记住块是移动的在进程的某个部分使用元数据锁因此,在块移动结束时,写操作会暂时阻塞。
在行动中的autoSplitter
在MongoDB 4.2之前,autoSplitter进程负责确保在mongos路由器上运行的块不超过最大大小。路由器进程保存有关其执行的操作的一些统计信息。参考这些统计数据来做出块分割决策。
问题是,在生产环境中,通常部署了许多这样的流程。单个mongos路由器只能部分了解特定数据块的情况。
如果多个mongos路由器更改同一块上的数据,它可能会超过最大大小,但仍然不被注意。
有许多与此主题相关的报告问题(例如,请参阅服务器- 44088,服务器- 13806,服务器- 16715).
频繁重启mongos进程也可能导致同样的问题。这将导致用于进行块分割决策的统计信息被重置。
由于这些问题,autoSplitter进程被更改为在每个分片副本集的主成员上运行。这发生在MongoDB 4.2版本中(参见服务器- 9287了解更多详情)。
进程现在应该更好地防止大块,因为每个分片都有一个更准确的视图,它包含什么数据(以及它是如何被改变的)。
在这种情况下,MongoDB的版本是3.6,而autoSplitter并没有像预期的那样好。
在MongoDB中查找未检测到的大块
对于初学者,我遇到了一个脚本来打印块统计信息的摘要在这里。我们可以在此基础上进行构建,以显示有关Jumbo块的信息,并生成手动分割超过最大大小的块所需的命令。
|
1
2
3.
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20.
21
22
23
24
25
26
27
28
29
30.
31
32
|
var
allChunkInfo
=
函数
(
ns
)
{
var
块
=
db
.getSiblingDB
(
“配置”
)
.chunks
;
(
{
“ns”
:
ns
}
)
.sort
(
{
最小值
:
1
}
)
.noCursorTimeout
(
)
;
//这
将
返回
所有
块
为
的
ns
命令
通过
最小值
var
totalChunks
=
0
;
var
totalJumbo
=
0
;
var
totalSize
=
0
;
var
totalEmpty
=
0
;
块
.forEach
(
函数
printChunkInfo
(
块
)
{
var
db1
=
db
.getSiblingDB
(
块
营收
.split
(
“。”
)
[
0
]
)
//
得到
的
数据库
我们
将
是
运行
的
命令
反对
晚些时候
var
关键
=
db
.getSiblingDB
(
“配置”
)
.collections
.findOne
(
{
_id
:
块
营收
}
)
。key
;
//
将
需要
这
为
的
dataSize
调用
//
dataSize
返回
的
信息
我们
需要
在
的
数据
,
但
使用
的
估计
选项
来
使用
计数
是
少
密集的
var
dataSizeResult
=
db1
.runCommand
(
{
datasize
:
块
营收
,
keyPattern
:
关键
,
最小值
:
块
.min
,
马克斯
:
块
.max
,
估计
:
假
}
)
;
如果
(
dataSizeResult
.size
>
67108864
)
{
totalJumbo++;
打印
(
“sh.splitFind(“”
+
块
营收
.toString
(
)
+
”,“
+
JSON
.stringify
(
块
.min
)
+
“)”
+
' // '+
块
.shard
+
' '
+
数学
.round
(
dataSizeResult
.size/1024/1024
)
+
' MB '
+
dataSizeResult
.numObjects
)
;
}
totalSize
+ =
dataSizeResult
.size
;
totalChunks++;
如果
(
dataSizeResult
.size
==
0
)
{
totalEmpty++
}
;
//数
空
块
为
总结
}
)
打印
(
“***********汇总块信息***********”
)
;
打印
(
“总块数:”+totalChunks
)
;
打印
(
“总大块:”+totalJumbo
)
;
打印
(
"平均块大小(兆字节):"+(
totalSize/totalChunks/1024/1024
)
)
;
打印
(
“空块:”+totalEmpty
)
;
打印
(
"平均块大小(非空):"+(
totalSize/(
totalChunks-totalEmpty
)/1024/1024
)
)
;
}
|
该脚本必须从mongos路由器调用,如下所示:
|
1
|
蒙戈
>
allChunkInfo
(
“db.test_col”
)
|
并且它将打印任何执行块分割所需的命令:
|
1
2
3.
4
5
6
7
8
9
10
11
|
上海
.splitFind
(
“db.test_col”
,
{
“_id”
:
“jhxT2neuI5fB4o4KBIASK1”
}
)
//
碎片-1
222
MB
7970
上海
.splitFind
(
“db.test_col”
,
{
“_id”
:
“zrAESqSZjnpnMI23oh5JZD”
}
)
//
碎片-2
226
MB
7988
上海
.splitFind
(
“db.test_col”
,
{
“_id”
:
“SgkCkfSDrY789e9nD4crk9”
}
)
//
碎片-1
218
MB
7986
上海
.splitFind
(
“db.test_col”
,
{
“_id”
:
“X5MKEH4j32OhmAhY7LGPMm”
}
)
//
碎片-1
238
MB
8338
。
。
。
***********总结
块
信息***********
总计
块
:
5047
总计
巨型
块
:
120
平均
块
大小
(
mb
)
:
19.29779934868946
空
块
:
1107
平均
块
大小
(
非-空
)
:
24.719795257064895
|
您可以看到脚本打印了分割大块的命令,以及每个大块所在的碎片、块的大小和其中的文档数量。最后还会显示汇总信息。
dataSize命令中的estimate: true选项使它使用集合的平均文档大小进行估计。如果文档大小有很多差异,但运行速度更快,资源消耗更少,那么这不是很准确。如果您知道文档大小大致相同,请考虑设置该选项。
剩下的就是运行脚本生成的命令,大块应该就消失了。然后,平衡器将在下一个平衡窗口中根据需要移动数据块以均衡数据。考虑在高峰工作负载时间之外安排平衡窗口,以减少影响。
结论
尽管MongoDB 3.6已经EOL有一段时间了,但很多人仍然在运行它,主要是由于应用端过时的驱动程序与新版本不兼容。现在,autoSplitter应该在控制块大小方面做得更好。
如果您仍在运行比4.2更老的MongoDB版本,那么不时检查数据块统计是一个好主意,以避免一些令人讨厌的意外。
雷竞技下载官网Percona Distribution for MongoDB是一个免费的MongoDB数据库替代方案,为您提供一个单一的解决方案,它结合了来自开源社区的最好和最重要的企业组件,设计和测试可以一起工作。
今天就下载Mong雷竞技下载官网oDB的Percona发行版!






