Плохая производительность агрегации

У меня есть две коллекции

Сообщений:

{
    "_Id": "1",
    "_PostTypeId": "1",
    "_AcceptedAnswerId": "192",
    "_CreationDate": "2012-02-08T20:02:48.790",
    "_Score": "10",
    ...
    "_OwnerUserId": "6",
    ...
},
...

и пользователей:

{
    "_Id": "1",
    "_Reputation": "101",
    "_CreationDate": "2012-02-08T19:45:13.447",
    "_DisplayName": "Geoff Dalgas",
    ...
    "_AccountId": "2"
},
...

и я хочу найти пользователей, которые пишут от 5 до 15 сообщений. Вот как выглядит мой запрос:

db.posts.aggregate([
    {
        $lookup: {
            from: "users", 
            localField: "_OwnerUserId",
            foreignField: "_AccountId", 
            as: "X"
        }
    },  
    {
        $group: {
            _id: "$X._AccountId", 
            posts: { $sum: 1 }
        }
    },   
    {
        $match : {posts: {$gte: 5, $lte: 15}}
    },  
    {
        $sort: {posts: -1 }
    },
    {
        $project : {posts: 1}
    }
])

и он работает ужасно медленно. Для пользователей 6k и сообщений 10k потребовалось более 40 секунд, чтобы получить ответ, в то время как в реляционной базе данных я получаю ответ в течение секунды. Где проблема? Я только начинаю с mongodb, и вполне возможно, что я испортил этот запрос.

Ответ 1

из https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/

foreignField Задает поле из документов в коллекция. $lookup выполняет сопоставление равенства на foreignField. localField из входных документов. Если документ в коллекция не содержит foreignField, $lookup рассматривает значение как null для целей сопоставления.

Это будет выполняться так же, как и любой другой запрос.

Если у вас нет индекса в поле _AccountId, он будет делать полный запрос на поиск таблиц для каждого из 10 000 сообщений. Основная часть времени будет потрачена на эти таблицы.

db.users.ensureIndex("_AccountId", 1) 

ускоряет процесс, поэтому он делает 10 000 обращений к индексу вместо 10 000 сканирований таблиц.

Ответ 2

В дополнение к предложению bauman.space поместить индекс в поле _accountId (что очень важно), вы также должны выполнить этап $ match как можно раньше в конвейере агрегации (т.е. На первом этапе). Даже если он не будет использовать какие-либо индексы (если вы не индексируете поле публикаций), он отфильтрует результирующий набор перед выполнением этапа $ lookup (join).

Причина, по которой ваш запрос ужасно медленный, заключается в том, что для каждого поста он выполняет неиндексированный поиск (последовательное чтение) для каждого пользователя. Это около 60 м читает!

Ознакомьтесь с разделом "Оптимизация конвейера" документов агрегации MongoDB.

Ответ 3

Сначала используйте $match затем $lookup. Фильтр $match строки должны быть проверены на $lookup. Это эффективно.