MongoDB分片后插入速度变慢了。

9
我有一个包含一个主副本和一个辅助副本的Mongodb集群,它们一起作为一个复制集运行。但随着流量增长,我决定执行分片操作以获得更多的写入速度。
我根据 tutorial 进行了基于“_id”列的哈希分片,并将数据分成了两个分片。然后进行了一些基准测试,发现在某些情况下,分片集群甚至比未分片的集群更慢。
这是测试结果。
  1. 最大吞吐量测试:使用十台机器同时运行“mongoimport”将数据加载到目标数据库中,以测试数据库的最大写入速度。

    结果:

    分片集群可以插入39500个文档/秒。

    未分片集群可以插入27400个文档/秒。

  2. 单实例mongoimport测试:只使用一台机器运行“mongoimport”将数据加载到目标数据库中。

    结果:

    分片集群可以插入14285个文档/秒。

    未分片集群可以插入14085个文档/秒。

  3. 使用mongodb java驱动程序进行单实例数据加载:只使用一个实例通过调用mongodb java驱动程序的api将数据加载到目标数据库中。

    结果:

    分片集群可以插入4630个文档/秒。

    未分片集群可以插入17544个文档/秒。

第一个测试的结果非常合理。将数据库分片成2片,并且吞吐量增加了约50%,一切都很完美,太好了!

第二个测试有点意义。虽然吞吐量大致相同,但瓶颈可能在数据加载器的一侧,毕竟我们只使用一个实例来加载数据。
但是第三个测试真的困扰着我。分片集群比未分片集群慢那么多是没有道理的。另一方面,未分片的数据库速度惊人,甚至比使用mongoimport加载数据还要快。
以下是用于加载数据的Java代码。我真的无法弄清楚这个问题,提前感谢所有的答案。
public static void insert(String host, int port) throws FileNotFoundException,
        InterruptedException, ExecutionException {
    MongoClient mongoClient = new MongoClient(host, port);
    mongoClient.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
    MongoDatabase database = mongoClient.getDatabase("my-db");
    MongoCollection<Document> collection = database.getCollection("my-collection");
    Scanner scan = new Scanner(new File("my-sample-dataset"));

    // Pre-load the data into the memory, so that the db load test won't be 
    // affected by disk I/O time.
    Queue<List<String>> resource = new LinkedList<>();
    for (int i = 0; i < 100; i++) {
        List<String> strs = new ArrayList<>();
        for (int j = 0; j < 10000; j++)
            strs.add(scan.nextLine());
        resource.add(strs);
    }

    System.out.println("start");
    long startTime = System.currentTimeMillis();
    while (!resource.isEmpty()) {
        List<String> strs = resource.poll();
         List<WriteModel<Document>> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
             list.add(new
             InsertOneModel<Document>(Document.parse(strs.get(i))));
        }
        collection.bulkWrite(list);
    }
    System.out.println("Finished loading. Time taken: " + (System.currentTimeMillis() - startTime) + "ms");
    scan.close();
}

你尝试过通过传递所有副本集主机和端口来创建MongoClient吗?像这样:new MongoClient(Arrays.asList( new ServerAddress("localhost", 27017), new ServerAddress("localhost", 27018), new ServerAddress("localhost", 27019))); - Babl
您的源数据是否为每个记录指定了 _id? - steve cook
还有一些指针在这里:http://chat.stackoverflow.com/rooms/40058/discussion-between-mason-and-asya-kamsky 尝试检查您的主机的统计信息,以及文档如何在它们之间进行分片。 - steve cook
你使用的Mongo版本是哪个? - Rahul
2个回答

7

可能的罪魁祸首是collection.bulkWrite(list);

在批量写入的情况下,mongos需要将您的批次拆分为较小的批次,以发送到每个分片。

由于您没有指定批处理中文档的插入顺序,MongoDB必须遵守按照指定顺序进行插入的要求。其结果是仅当连续插入对应于同一分片时,才能将它们批处理。

mongos维护原始文档顺序,因此只有属于同一分片的连续插入才能被分组在一起

例如,考虑“k”是分片键的情况。有两个分片,对应于范围

[MinKey, 10], (20, MaxKey]

现在假设我们批量插入以下文档:

[{k: 1}, {k: 25}, {k: 2}]

文档1 -> 分片1,文档2 -> 分片2,文档3 -> 分片3

没有连续的两个文档属于同一个分片,因此在这种情况下每个文档都需要调用 getLastError

对于哈希键,文档将在分片之间更随机地分布。即,属于同一分片的文档可能更分散,因此会创建更多批次分布越随机,批次的大小就越小,总批次数就越多,导致 getLastError 的成本更高,这意味着性能更差。

修复:指定 "ordered: false"

collection.bulkWrite(list, new BulkWriteOptions().ordered(false));

这将告诉数据库您不关心插入操作的严格顺序。使用"ordered: false", mongos会为每个分片创建一个批次,省去额外的getLastError调用。每个批量操作可以在适当的分片上并发执行,无需等待先前批次的getLastError响应。
另外,

MongoClient mongoClient = new MongoClient(host, port);

基于单个mongodb节点创建Mongo实例,不能发现复制集或分片集群中的其他节点。

在这种情况下,所有写入请求都被路由到单个节点,该节点负责由于分片群集而进行的所有其他簿记工作。您应该使用的是
MongoClient(final List<ServerAddress> seeds)

当根据请求类型(读或写)和读取优先级(如果是读请求)有多个服务器可供选择时,驱动程序将随机选择一个服务器发送请求。这适用于复制集和分片集群。

注意:在列表中放置尽可能多的服务器,系统将自动处理其余部分。


0

一般来说,每当您使用分片解决方案时,您需要考虑以下两种情况之一:

  1. 您的客户端应用程序将具有集群感知能力,因此能够自行进行路由
  2. 您的客户端应用程序将联系执行路由的中间节点

我怀疑Mongo Client不会自动地具备集群感知能力,这意味着如果您没有指定它们,它不会查找属于集群的节点。以下事实进一步加强了我的这种感觉:

您可以通过将ServerAddress列表传递给MongoClient构造函数来连接副本集。例如:
MongoClient mongoClient = new MongoClient(Arrays.asList( new ServerAddress("localhost", 27017), new ServerAddress("localhost", 27018), new ServerAddress("localhost", 27019)));
您也可以使用相同的构造函数连接到分片集群。MongoClient会自动检测服务器列表是副本集成员列表还是mongos服务器列表。

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