Ужасно сниженная производительность с другими условиями соединения в $ lookup (с использованием конвейера) - PullRequest
1 голос
/ 05 мая 2020

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

    .aggregate([
        //difference starts here
        {
            "$lookup": {
                "from": "sessions",
                "localField": "_id",
                "foreignField": "_client",
                "as": "sessions"
            }
        },
        {
            $unwind: "$sessions"
        },
        {
            $match: {
                "sessions.deleted_at": null
            }
        },
        //difference ends here
        {
            $project: {
                name: client_name_concater,
                email: '$email',
                phone: '$phone',
                address: addressConcater,
                updated_at: '$updated_at',
            }
        }
    ]);

до этого:

    .aggregate([
    //difference starts here
    {
        $lookup: {
            from: 'sessions',
            let: {
                id: "$_id"
            },
            pipeline: [
                {
                    $match: {
                        $expr: {
                            $and:
                                [
                                    {
                                        $eq: ["$_client", "$$id"]
                                    }, {
                                    $eq: ["$deleted_at", null]
                                },
                                ]
                        }
                    }
                }
            ],
            as: 'sessions'
        }
    },
    {
        $match: {
            "sessions": {$ne: []}
        }
    },
    //difference ends here
        {
            $project: {
                name: client_name_concater,
                email: '$email',
                phone: '$phone',
                address: addressConcater,
                updated_at: '$updated_at',
            }
        }
    ]);

Я подумал, что второй вариант должен быть лучше, так как у нас на один этап меньше, но разница в производительности огромна, наоборот, первый запрос выполняется в среднем ~ 40 мсек, другой - от 3,5 до 5 секунд, в 100 раз больше. Другая коллекция (сеансы) содержит около 120 документов, в то время как эта около 152, но все же, даже если это было приемлемо из-за размера данных, почему разница между этими двумя, в основном, не одно и то же, мы просто добавляем условие соединения в конвейере с другим основным условием соединения. Я что-то упустил?

Некоторые функции или переменные, включенные в него, в основном состоят из c или конкатенации, которые не должны влиять на часть поиска.

Спасибо

EDIT:

Добавлены планы запросов для версии 1:

{
        "stages": [
            {
                "$cursor": {
                    "query": {
                        "$and": [
                            {
                                "deleted_at": null
                            },
                            {}
                        ]
                    },
                    "fields": {
                        "email": 1,
                        "phone": 1,
                        "updated_at": 1,
                        "_id": 1
                    },
                    "queryPlanner": {
                        "plannerVersion": 1,
                        "namespace": "test.clients",
                        "indexFilterSet": false,
                        "parsedQuery": {
                            "deleted_at": {
                                "$eq": null
                            }
                        },
                        "winningPlan": {
                            "stage": "COLLSCAN",
                            "filter": {
                                "deleted_at": {
                                    "$eq": null
                                }
                            },
                            "direction": "forward"
                        },
                        "rejectedPlans": []
                    }
                }
            },
            {
                "$lookup": {
                    "from": "sessions",
                    "as": "sessions",
                    "localField": "_id",
                    "foreignField": "_client",
                    "unwinding": {
                        "preserveNullAndEmptyArrays": false
                    }
                }
            },
            {
                "$project": {
                    "_id": true,
                    "email": "$email",
                    "phone": "$phone",
                    "updated_at": "$updated_at"
                }
            }
        ],
        "ok": 1
    }

Для версии 2:

{
        "stages": [
            {
                "$cursor": {
                    "query": {
                        "deleted_at": null
                    },
                    "fields": {
                        "email": 1,
                        "phone": 1,
                        "sessions": 1,
                        "updated_at": 1,
                        "_id": 1
                    },
                    "queryPlanner": {
                        "plannerVersion": 1,
                        "namespace": "test.clients",
                        "indexFilterSet": false,
                        "parsedQuery": {
                            "deleted_at": {
                                "$eq": null
                            }
                        },
                        "winningPlan": {
                            "stage": "COLLSCAN",
                            "filter": {
                                "deleted_at": {
                                    "$eq": null
                                }
                            },
                            "direction": "forward"
                        },
                        "rejectedPlans": []
                    }
                }
            },
            {
                "$lookup": {
                    "from": "sessions",
                    "as": "sessions",
                    "let": {
                        "id": "$_id"
                    },
                    "pipeline": [
                        {
                            "$match": {
                                "$expr": {
                                    "$and": [
                                        {
                                            "$eq": [
                                                "$_client",
                                                "$$id"
                                            ]
                                        },
                                        {
                                            "$eq": [
                                                "$deleted_at",
                                                null
                                            ]
                                        }
                                    ]
                                }
                            }
                        }
                    ]
                }
            },
            {
                "$match": {
                    "sessions": {
                        "$not": {
                            "$eq": []
                        }
                    }
                }
            },
            {
                "$project": {
                    "_id": true,
                    "email": "$email",
                    "phone": "$phone",
                    "updated_at": "$updated_at"
                }
            }
        ],
        "ok": 1
    }

Одно примечание: коллекция объединенных сеансов имеет определенные свойства с очень большие данные (некоторые импортированные данные), поэтому я думаю, что это может каким-то образом повлиять на размер запроса из-за этих данных? Но почему разница между двумя версиями $ lookup?

1 Ответ

2 голосов
/ 05 мая 2020

Вторая версия добавляет выполнение конвейера агрегирования для каждого документа в объединенной коллекции .

В документации указано:

Задает конвейер для запуска в объединенной коллекции. Конвейер определяет результирующие документы из объединенной коллекции. Чтобы вернуть все документы, укажите пустой конвейер [].

Конвейер выполняется для каждого документа в коллекции, а не для каждого совпадающего документа.

В зависимости от размера коллекции есть (как количество документов, так и размер документа), это может занять приличное количество времени.

после удаления ограничения версия конвейера перескочила на более чем 10 секунд

Имеет смысл - для всех дополнительных документов из-за снятия ограничения также должен быть выполнен конвейер агрегирования.

Возможно, выполнение конвейера агрегирования для каждого документа не так оптимизировано, как оно может быть. Например, если конвейер настроен и разорван для каждого документа, в , что может быть больше накладных расходов, чем в условиях $ match.

Есть ли случай при использовании того или другого?

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...