如何加速MongoDB的插入速度?

32

我正在尝试最大化每秒插入的数量。目前,我的性能随着使用的线程和CPU数量增加而降低(我有16个可用核心)。在一个16核双处理器机器上,两个线程比16个线程每秒执行更多操作。您有什么想法是问题出在哪里?是因为我只使用了一个mongod吗?是索引导致速度变慢吗?我需要使用分片吗?我想知道是否有一种方式可以分片,但同时也保持数据库的容量限制...

约束条件:必须处理约300k次每秒的插入操作,必须具有自我限制功能(容量限制),必须能够相对快速地查询。

问题空间:必须处理一家重要手机公司的通话记录(约300k次每秒的插入操作),并使这些通话记录在尽可能长的时间内(例如一周)可查询。

#!/usr/bin/perl

use strict;
use warnings;
use threads;
use threads::shared;

use MongoDB;
use Time::HiRes;

my $conn = MongoDB::Connection->new;

my $db = $conn->tutorial;

my $users = $db->users;

my $cmd = Tie::IxHash->new(
    "create"    => "users",
    "capped"    => "boolean::true",
    "max"       => 10000000,
    );

$db->run_command($cmd);

my $idx = Tie::IxHash->new(
    "background"=> "boolean::true",
);
$users->ensure_index($idx);


my $myhash =
    {
        "name"  => "James",
        "age"   => 31,
        #    "likes" => [qw/Danielle biking food games/]
    };

my $j : shared = 0;

my $numthread = 2;  # how many threads to run

my @array;
for (1..100000) {
    push (@array, $myhash);
    $j++;
}

sub thInsert {
    #my @ids = $users->batch_insert(\@array);
    #$users->bulk_insert(\@array);
    $users->batch_insert(\@array);
}

my @threads;

my $timestart = Time::HiRes::time();
push @threads, threads->new(\&thInsert) for 1..$numthread;
$_->join foreach @threads; # wait for all threads to finish
print (($j*$numthread) . "\n");
my $timeend = Time::HiRes::time();

print( (($j*$numthread)/($timeend - $timestart)) . "\n");

$users->drop();
$db->drop();
6个回答

19

目前,向MongoDB写入数据需要获取全局写锁,尽管集合级别的锁很快就可能推出。通过使用更多线程,您很可能会引入更多并发问题,因为这些线程在等待锁被释放时会相互阻塞。

索引也会减慢您的速度,为了获得最佳的插入性能,最好在加载数据后再添加索引,但这并非总是可能的,例如如果您正在使用唯一索引。

要真正最大化写入性能,最好的选择是分片。这将给你更好的并发性能和更高的磁盘I/O容量,因为你可以将写入分散到多台机器上。


3
请注意,集合级别的锁定在这里没有帮助。 - Thilo
@Thilo 正确,但是值得注意的是集合级别的锁定正在开发中。一旦它可用,就会有一个额外的选项将数据分割到多个集合中以减少锁竞争,以及分片和分布在不同的数据库中。 - Chris Fulstow
“将数据分割到多个集合的选项”。这是如何工作的?这类似于Oracle中的分区表吗?即,您仍然可以将其视为单个集合进行查询,但它在内部进行了“分片”处理? - Thilo
@Thilio 我原本想要更手动的方式,但我喜欢这个想法。一旦集合级别的锁定到位,你可以建立一个整洁的小分区层,覆盖多个集合以减少锁定;它可以通过驱动程序呈现为单个集合。在这种情况下,甚至可以使用每个范围一个锁来实现集合级别的锁定,这样你就可以直接获得每个集合的多个锁。 - Chris Fulstow
哦,我明白了,你说的“会有一个额外选项”是指应用程序开发人员的选项,而不是MongoDB的功能,对吗? - Thilo
@Thilio 是的,就像提供不同选择的选项,不一定是一个新功能。 - Chris Fulstow

17

在一个16核双处理器机器上,目前2个线程比16个线程的性能更高。

MongoDB插入操作不能并发执行。每个插入操作都需要获取写锁。不确定是全局锁还是集合级别的锁,但在您的情况下这没有任何区别。

因此,一旦Mongo成为瓶颈,将此程序变为多线程的意义就不大了。

我需要使用分片吗?

无法对固定大小集合进行分片。


谢谢您的回复。您是否听说过使用分片来最大化插入同时进行限制?我知道在MongoDB内部这不是一个功能,但我想知道是否有一种通过脚本或其他方式进行限制的方法。 - EhevuTov
也许对于高交易量的数据来说,MongoDB并不是最合适的解决方案。 - Thilo
我认为MongoDB通过分片具有进行高容量插入的潜力,但是旋转/限制数据库是一个挑战。 - EhevuTov
除了锁之外,还有其他限制可以通过多线程加速。请参见https://dev59.com/EXPYa4cB1Zd3GeqPgTvB。 - claudio
我相信即使是版本3.4.3,这个答案也是正确的。并发文档很令人困惑。它谈到插入需要一个意向排他(IX)锁。当你研究意向排他锁时,它允许你在写入并发时进行读取,但不能在数据库中执行2个写入并发。换句话说,尝试使用多个客户端将大量数据写入同一数据库(甚至不同的集合)似乎会减慢整体吞吐量。:( - Fishish

2

我注意到在插入后构建索引会有所帮助。


1
通常情况下,您不会向已封顶的集合添加索引。 - Remon van Vliet
1
对,我之前提到这个是因为他在调用 ensure_index。也许不索引会更省时间? - jeffsaracco
我应该使用哪个调用来不进行索引?虽然这些插入操作需要查询,但我可能仍需要进行索引。基本上,客户将记录所有电话通话并需要根据某些值查询这些通话。 - EhevuTov
1
有限集合和查询需要索引的用例通常是互斥的。 有限集合适用于各种类型的日志和其他受益于可追溯游标的内容。 一旦您需要查询,通常最好使用普通集合。 请注意,您可以为有限集合建立索引,但这很少有意义。 - Remon van Vliet
@Remon,听起来我越来越想使用分片技术,并且在Mongo之外使用我的应用程序对集合进行截断。 - EhevuTov

2

嗯... 一个mongodb服务器不可能提供那么高的性能。

0.3M * 60 * 60 * 24 = 26G 记录/天,180G 记录/周。我猜你的记录大小大约是100字节,所以每天有2.6TB 的数据。我不知道你用哪个(些)字段进行索引,但我怀疑它们的长度不会低于10-20字节,所以仅仅每天的索引就会超过2G,更别说整个星期了... 索引无法放入内存,加上很多查询,这是一场灾难。

你应该进行手动分片,根据搜索字段对数据进行分区。这是一家主要的电信公司,你应该进行复制。购买大量单核/双核机器,你只需要为主要的(perl?)服务器提供核心。

顺便问一下,你如何查询数据?你可以使用键值存储吗?


系统拥有12.2TB硬盘,192GB内存和16个处理核心,所以每日的索引应该没问题。如果我理解正确,我不明白为什么不能使用键值存储。你有什么想法? - EhevuTov
哦,另外,我将会通过几个字段查询结果集,比如电话号码、呼叫时间等。 - EhevuTov
@EhevuTov:顺便问一下,你找出瓶颈在哪了吗?我怀疑不是网络,应该是硬盘,或许是CPU。 - Karoly Horvath
我很感激你的帮助,但你正在问有关硬件的同样问题,而我必须用另一个问题回答:为什么我的旧双核运行速度较慢的驱动程序表现比16核、RAID驱动器和192GB RAM性能更好?你会认为16核服务器的性能至少应该提高10倍。我更倾向于你的想法,即分片没有正确设置,这是MongoDB内部的问题。 - EhevuTov
1
我认为,拥有十二台双核心、4G内存和单个硬盘的机器可能更便宜,而且总体性能更好。你可以进行横向扩展,但是到某个点时,没有一台服务器能够处理所有请求,你必须切换到分布式解决方案。许多公司都采用了这种方法。从一开始就选择正确的架构是更好的选择。 - Karoly Horvath
显示剩余3条评论

2

为什么不手动限制集合大小呢?您可以将其分片到多台机器上,并应用所需的查询索引,然后每隔一小时左右删除不需要的文档。

您遇到的瓶颈很可能是全局锁 - 我在评估MongoDB用于插入密集型时间序列数据应用程序时看到过这种情况。您需要确保分片键不是时间戳,否则所有插入操作都会在同一台机器上顺序执行,而不是分布在多台机器上。


0

MongoDB上的写锁是全局的,但引用this的话,“集合级别的锁定即将推出”。

我需要使用分片吗?

这个问题不太容易回答。如果一个不能满足您的要求,那么您必须使用分片,因为分片是MongoDB扩展写入的唯一方法(在不同实例上的写入不会相互阻塞)。


你不能对已固定大小的集合进行分片。 - Thilo
是的,我的错误。我没有看到它是一个有上限的集合。 - Eren Güven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接