MongoDB: объединить агрегацию и фильтр - PullRequest
0 голосов
/ 09 октября 2018

Пожалуйста, ознакомьтесь со следующим постом: MongoDB C # Driver - возвращать только последние измененные строки

После почти двух лет работы этого кода в последнее время у нас возникают проблемы с производительностьюи как бы я ни говорил, что проблема не в коде, инфраструктура настаивает на том, что я делаю полное сканирование таблицы.

Дело в том, что проблема связана с окружающей средой.Наша среда контроля качества работает как сон все время, но Dev и Prod иногда очень медленны, а с другой - хороши.Они имеют те же данные и код, но у Dev и Prod есть другое приложение, которое также работает в базе данных.

У моих данных есть Id, а также _id (или AuditId) - я группирую данные по 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;
    }

Этот метод называетсянижеприведенным, который принимает выражение, которое он добавляет к FilterDefinition, созданному ForLastAuditIds.

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;
    }

Теперь все это работает очень хорошо и повторно используется всем моим кодом, который вызывает Collections,но эта конкретная коллекция намного больше других, и мы получаем замедления только на этом.

Мой вопрос: есть ли у меня способ взять агрегат и построитель фильтров и объединить их, чтобы вернутьодин FilterDefinition, который я мог бы использовать без предварительного полного сканирования таблицы?

Я действительно надеюсь, что я понимаю.

1 Ответ

0 голосов
/ 10 октября 2018

Предполагая, что я полностью понимаю, что вы хотите, это должно быть так просто:

Сначала поместите нисходящий индекс в поле LastAuditId:

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

Или даже расширьтеиндекс для других полей, которые есть в вашем фильтре:

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

Однако убедитесь, что вы понимаете , как индексы могут / не могут поддерживать определенные запросы .И всегда используйте объяснение () , чтобы увидеть, что на самом деле происходит.

Следующим шагом является осознание того, что вы всегда должны фильтровать как можно больше, чем самый первый шаг, чтобы уменьшить количествотребуется сортировка.

Итак, если вам нужно, например, отфильтровать по Name, то непременно сделайте это в качестве самого первого шага, если это позволяют ваши бизнес-требования.Будьте осторожны, однако, что фильтрация в начале меняет вашу семантику в том смысле, что вы будете получать последние измененные документы для каждого Id, который прошел предыдущий этап $match, в отличие от последних документов на каждый Id, которые происходяттакже пройти следующую стадию $match.

В любом случае, что наиболее важно, после того, как вы получили отсортированный комплект, вы можете легко и быстро получить последний полный документ, используя $group с $first, который- с правильным индексом на месте - больше не будет выполнять сканирование коллекции (сейчас это будет сканирование индекса и, следовательно, намного быстрее).

Наконец, вы хотите запустить эквивалент следующего запроса MongoDBчерез C #, используя переменную $$ ROOT , чтобы избежать повторного запроса (я могу собрать необходимый код для вас, как только вы опубликуете типы Audit, Auditable и IMongoAuditable, а такжелюбые потенциальные сериализаторы / соглашения):

db.getCollection('collection').aggregate({
    $match: {
        /* some criteria that you currently get in the "Expression<Func<BsonDocument, bool>> filter" */
    }
}, {
    $sort: {
        "ModifiedDate": -1 // this will use the index!
    }
}, {
    $group: {
        "_id": "$Id",
        "document": { $first: "$$ROOT" } // no need to do a separate subsequent query or a $max/$min across the entire group because we're sorted!
    }
}, {
    $match: { // some additional filtering depending on your needs
        "document.Status": { $ne: "Delete" }
    }
})

И наконец, учтите, что было бы неплохо перейти на последнюю версию MongoDBпотому что в настоящее время они прилагают много усилий для оптимизации таких случаев агрегирования, как у вас, например: https://jira.mongodb.org/browse/SERVER-9507

...