MongoDB和多个upsert

时间:2013-10-30 21:20:02

标签: java mongodb caching mongodb-java

我对MongoDB相对较新,但我们考虑将其用作遗留服务前的某种缓存。在这种情况下,我们偶然发现了一些问题。

首先,一些解释。

此缓存服务将位于旧服务和客户端之间。客户端将连接到缓存服务,缓存服务从旧服务获取其数据。缓存服务每X分钟获取一次数据,并将它们保存在MongoDB中。模式非常简单:只需要​​一个包含大量键/值的文档。没有嵌套文件等。此外,我们将_id设置为旧服务中的唯一ID,因此我们也可以对此进行控制。

当缓存服务从旧服务获取数据时,它只获得一个增量(仅自上次获取后更改)。因此,如果自上次以来5个“对象”发生了变化,那么你只得到那些5个“对象”(但是你得到了完整的对象,而不是对象的三角形)。如果遗留服务中添加了任何新的“对象”,那么这些对象当然也在增量中。

我们的“问题”

在我看来,这听起来像一个甜头。如果有新对象,请插入它们。如果现有对象发生更改,请更新它们。但是,MongoDB似乎并不特别喜欢多次upsert。只是插入给我一个关于重复键的错误,这是完全可以理解的,因为文档已经存在与相同的_id。 update函数可以采用upsert参数,不能获取新对象列表。在我看来,单个查询是不可能的。但是,我可能完全忽视了这里的一些东西。

可能的解决方案

有许多不同的解决方案,尤其是我想到的两个解决方案:

  • 执行两个查询:首先,计算一个包含所有_id的列表(请记住,我们的遗留服务中包含这些查询)。然后,使用$ in函数删除它们以及_id列表并立即插入新文档。实际上,这应该用新数据更新我们的集合。它也很容易实现。可能发生的问题是客户端要求删除和插入之间的数据,因此错误地获得空结果。这是一个交易破坏者,绝对不会发生。
  • 每个更改的对象执行一次upsert。也很容易实现,不应该给出与其他解决方案相同的问题。这有其他(可能是想象的)问题。在短时间内可以处理多少个upsert?它可以很容易地每分钟处理5000次upserts吗?这些不是大文档,只有大约20个键/值,没有子文档。这个数字是从空气中抽出来的,很难预测实际数字。在我看来,这种方法感觉不对。我无法理解为什么每个新文档都需要运行一个查询。

对于提出的两个解决方案和任何其他解决方案,我们将非常感谢任何帮助。作为旁注,技术不是真正可以讨论的,所以请不要建议其他类型的数据库或语言。为什么我们选择了我们选择的东西还有其他强大的理由:)

4 个答案:

答案 0 :(得分:1)

我会分享我的经验......

在我上一份工作中,我们遇到了类似的情况。我们最终为每个文档/对象执行一次查询/写入。我们使用Mule ESB将遗留系统中的数据抽取到Mongo,每次写入都是upsert。

表现相当不错,但不是很好。我们可以在几分钟内将几千份文件送入Mongo。这些文件相当丰富,这可能是我们为什么不得不限制对Mongo的写入的一部分。

在我们批量加载数据后,“实时”性能从来都不是问题。

您建议的第一个选项听起来太复杂,并且可能会使Mongo处于未知状态,以防操作在更新中途中止。 upsert选项保存了很多次,因为我们可以反复重放插件并且安全。

答案 1 :(得分:1)

扩展ryan1234的答案:

2.6版本的MongoDB将能够发送批量更新。现在,您需要为每个文档提交单独的请求。

正如ryan1234所说,对每个文档执行upsert是更新所有现有文档并添加新文档的唯一安全方法,如果您不知道旧版提供程序。单个MongoDB进程可以在中间硬件上轻松处理每秒数千次更新(1)。如果您没有达到该级别的性能,那么可能是客户端和MongDB服务器之间的请求延迟。 Asynchronous Java Driver可以帮助克服这一限制,允许多个更新请求同时传输到服务器,同时最小化客户端复杂性/线程。

HTH,Rob

1:我认为文档没有增长,没有索引更新,但即使是那些你应该能够每秒接近一千个更新。

答案 2 :(得分:1)

如果你的钥匙是复合的,你可以使用:

public static BulkWriteResult insertAll(MongoCollection<Document> coll, List<Document> docs, String[] keyTags, boolean upsert) {
    if(docs.isEmpty())
        return null;
    List<UpdateOneModel<Document>> requests = new ArrayList<>(docs.size());
    UpdateOptions opt = new UpdateOptions().upsert(upsert);
    for (Document doc : docs ) {
        BasicDBObject filter = new BasicDBObject();
        for (String keyTag : keyTags) {
            filter.append(keyTag, doc.get(keyTag));
        }
        BasicDBObject action = new BasicDBObject("$set", doc);
        requests.add(new UpdateOneModel<Document>(filter, action, opt));
    }
    return coll.bulkWrite(requests);
}

答案 3 :(得分:0)

我知道。它必须真正深入挖掘正确的方法。 试试这个:     / **      *将文档中的所有项目插入到集合中。      * @param coll目标集合      * @param提供新的或更新的文档      * @param keyTag文档中键的名称      * @param upsert if true如果未找到则创建新文档      * @return BulkWriteResult如果docs.isEmpty()则为null      * /

    public static BulkWriteResult insertAll(MongoCollection<Document> coll, List<Document> docs, String keyTag, boolean upsert) {
    if(docs.isEmpty())
        return null;
    List<UpdateOneModel<Document>> requests = new ArrayList<>(docs.size());
    UpdateOptions opt = new UpdateOptions().upsert(upsert);
    for (Document doc : docs ) {
        BasicDBObject filter = new BasicDBObject(keyTag, doc.get(keyTag)); 
        BasicDBObject action = new BasicDBObject("$set", doc);
        requests.add(new UpdateOneModel<Document>(filter, action, opt));
    }
    return coll.bulkWrite(requests);
}