MongoDB:结合聚合和过滤器

时间:2018-10-09 06:57:02

标签: c# mongodb aggregation

有关某些背景,请参见以下帖子:MongoDB C# Driver - Return last modified rows only

在运行了将近两年的代码之后,最近我们一直遇到性能问题,而我一直说代码不是问题,基础设施坚持认为这是因为我正在进行全表扫描。

问题在于问题是特定于环境的。我们的质量保证环境始终如梦似幻,但Dev和Prod有时非常慢,而其他时候则很好-这非常不稳定。它们具有相同的数据和代码,但Dev和Prod具有另一个也在数据库上运行的应用程序。

我的数据有一个ID和一个_id(或AuditId)-我按ID对数据进行分组,然后返回该记录的最后一个_id,但未删除该记录。我们有多个具有相同ID的历史记录,我想返回最后一个(请参阅原始帖子)。

所以我有以下方法:

private static FilterDefinition<T> ForLastAuditIds<T>(IMongoCollection<T> collection) where T : Auditable, IMongoAuditable
    {
        var pipeline = new[] { new BsonDocument { { "$group", new BsonDocument { { "_id", "$Id" }, { "LastAuditId", new BsonDocument { { "$max", "$_id" } } } } } } };
        var lastAuditIds = collection.Aggregate<Audit>(pipeline).ToListAsync().Result.ToList().Select(_ => _.LastAuditId);

        var forLastAuditIds = Builders<T>.Filter.Where(_ => lastAuditIds.Contains(_.AuditId) && _.Status != "DELETE");

        return forLastAuditIds;
    }

此方法由以下方法调用,该方法接受一个表达式,该表达式将附加到由ForLastAuditIds创建的FilterDefinition。

protected List<T> GetLatest<T>(IMongoCollection<T> collection,
                                     Expression<Func<T, bool>> filter, ProjectionDefinition<T, T> projection = null,
                                     bool disableRoleCheck = false) where T : Auditable, IMongoAuditable
    {
        var forLastAuditIds = ForLastAuditIds(collection);

        var limitedList = (
                projection != null
                    ? collection.Find(forLastAuditIds & filter, new FindOptions()).Project(projection)
                    : collection.Find(forLastAuditIds & filter, new FindOptions())
            ).ToListAsync().Result.ToList();

        return limitedList;
    }

现在,所有这些工作都非常好,并且可以被我所有调用集合的代码重复使用,但是这个特定集合比其他集合大很多,而我们在该集合上的运行速度越来越慢。

我的问题是:有没有办法让我将聚合器和Filter Builder合并起来,以返回单个FilterDefinition,而无需先运行全表扫描就可以使用它吗?

我真的希望我有道理。

1 个答案:

答案 0 :(得分:1)

假设我完全了解您想要什么,这应该很简单:

首先,在LastAuditId字段上放置一个降序索引:

db.collection.createIndex{ "LastAuditId": -1 /* for sorting */ }

甚至扩展索引以覆盖过滤器中的其他字段:

db.collection.createIndex{ "Status": 1, "LastAuditId": -1 /* for sorting */ }

但是,请确保您了解how indexes can/cannot support certain queries。并且始终使用explain()来查看实际情况。

下一步是要意识到,您必须始终在第一步中尽可能多地进行过滤,以减少所需的排序量。

因此,如果您需要如果您的业务需求允许,请首先使用Name进行过滤,然后将其作为第一步。但是请注意,从一开始就进行过滤会改变您的语义,因为每个Id都将通过先前的$match阶段,从而获得每个Id的最后修改的文档,而不是每个{ {1}}恰好也通过了以下$match阶段。

无论如何,最重要的是,一旦您有了一个排序集,就可以通过将$group$first一起使用来轻松,快速地获取最新的完整文档,该文档将具有正确的索引,不再进行集合扫描(现在将是索引扫描,因此速度更快)。

最后,您希望利用$$ROOT变量通过C#运行以下MongoDB查询的等效项,以避免再次查询(发布{{1后,我可以为您提供所需的代码}},AuditAuditable类型,以及任何潜在的序列化器/约定):

IMongoAuditable

最后,请注意,迁移到最新版本的MongoDB可能是一个好主意,因为他们目前正在努力优化诸如您的聚合案例。这个:https://jira.mongodb.org/browse/SERVER-9507