Пошаговое руководство по использованию MongoDB Aggregation Pipeline: от базового $match/$group до $lookup, $facet и оптимизаций. Примерное время выполнения: 45–90 минут.
Что вы изучите
Когда применять MongoDB aggregation pipeline вместо SQL-запросов.
Практическая работа с этапами: $match, $group, $lookup, $facet, $sort, $limit, $project.
Типичные ошибки, их диагностика и исправление для mongosh 1.11 (2026) и MongoDB Server 7.2 (2025).
Оптимизации: индексы, pipeline-пуллинг, ограничение полей и использование $setWindowFields для аналитики.
Антипаттерны и рекомендации по RAM/CPU и Docker-образам для продакшна.
Требования
MongoDB Server 7.2 (релиз 2025) или совместимая версия.
mongosh 1.11 (релиз 2026) для интерактивной работы.
Docker 24.0 (2025) для локального разворачивания: образ mongo:7.2 (~550 MB).
Минимум 4 ГБ RAM и 2 CPU для тестовой инстанции; для нагрузочного теста минимум 8 ГБ и 4 CPU.
Порты: MongoDB по умолчанию слушает 27017.
0
Статья была полезной?
Комментарии (0)
Войдите или зарегистрируйтесь, чтобы оставить комментарий
Загрузка комментариев…
К концу руководства вы сможете собрать рабочие aggregation pipeline для типичных аналитических задач и оптимизировать их под MongoDB 7.2. Время выполнения полного руководства: примерно 45–90 минут в зависимости от практики.
Скриншот: схема aggregation pipeline с этапами $match,$group,$lookup
Когда pipeline удобнее SQL?
Aggregation pipeline удобнее SQL в сценариях, где данные денормализованы, требуется потоковая трансформация документов или многослойная агрегация на стороне БД без лишних round-trip к приложению. В 2025–2026 годах команды всё чаще используют pipeline для временных рядов, событийной аналитики и ETL-процессов, когда нужно выполнить несколько последовательных преобразований одного документа: фильтрацию, проекции, вычисления, джойны и фасеты. Типичный пример — подсчёт сессионной аналитики по событиям клиента, где один документ содержит массив событий.
Преимущества pipeline перед SQL: встроенные этапы (например, $setWindowFields для оконных функций, появившиеся и стабилизированные к 2025), возможность работать с вложенными массивами без JOIN таблиц и гибкость при изменении схемы. Недостаток — сложность отладки длинных конвейеров и чувствительность к объёму RAM при группировках и фасетах.
Используйте pipeline для трансформаций одного документа и для дешёвых JOIN по индексированным полям.
Выбирайте SQL при сложных многотабличных транзакциях и строгой консистентности ACID между сущностями.
Шаг 1: $match и $group
Задача: посчитать количество событий по типу и по пользователю за последний месяц. Используем коллекцию events, документы формата {userId, type, ts}.
Команда в mongosh 1.11 (выполнение ~10–50 ms локально на тестовой выборке 100k):
Типичная ошибка: если users._id не индексирован, lookup выполняет COLLSCAN по users и время запроса растёт экспоненциально.
Проблема: high latency, план показывает $lookup -> unindexed COLLSCAN
Фикс: убедитесь, что внешний ключ индексирован — обычно это _id. Для кастомного поля создайте индекс:
db.users.createIndex({ _id: 1 }) // обычно уже есть
// или для кастомного ключа
db.users.createIndex({ userId: 1 })
Дополнительный приём: если связка много событий -> один пользователь и коллекция users небольшая (<=100MB), можно загрузить users в память приложения и делать join на уровне приложения. См. материалы по кешированию в devops.
Шаг 3: $facet
Задача: одновременно получить 3 результата — топ событий по типу, распределение по часам и общую статистику — в одном запросе. Используем $facet для параллельных веток в pipeline.
Пояснение: $facet создаёт независимые под-пайплайны и возвращает результаты в одном документе. Это удобно для dashboard-метрик, но потребляет память: сумма промежуточных наборов может быть значительной.
Фикс: использовать allowDiskUse: true, сократить поля в ранних этапах ($project), или разбить задачу на несколько запросов по времени.
Пример с опцией дисковой поддержки:
db.events.aggregate(pipeline, { allowDiskUse: true })
// Опция может увеличить время выполнения до 2–5x, но снимает ограничение RAM.
Шаг 4: $sort и $limit
Задача: получить топ-20 пользователей по количеству событий. Правильный порядок этапов критичен: сначала $group, затем $sort, затем $limit. Если есть индекс покрытия, стоит использовать его до группировки.
Типичная ошибка: если выполнить $sort на непроиндексированном большом множестве, MongoDB тратит память на сортировку.
Проблема: Sort exceeded memory limit, consider adding index or increasing RAM
Фикс: использовать индекс по полю, участвующему в сортировке, либо применять allowDiskUse: true и предварительно уменьшать набор через $match или $project. Альтернатива: поддерживать агрегированные счётчики в отдельной коллекции обновляемой через change streams или application layer.
Шаг 5: $project и вычисления полей
Задача: сформировать итоговую проекцию с вычислением стоимости заказа и приведением дат в нужную зону времени. Используем $project и выражения.
Скриншот: пример $project с $dateToString и вычислением total
Как оптимизировать?
Оптимизация aggregation pipeline включает индексирование, минимизацию передаваемых полей, использование allowDiskUse осознанно и применение кеширования. Конкретные шаги и рекомендации:
Индексы: создавайте индекс по полям, использующимся в $match (например, {ts:1}). Индексная селективность важна: индекс по {userId:1, ts:-1} уменьшит нагрузку при запросах по пользователю за период.
Порядок этапов: $match → $project → $group → $sort/$limit. Это уменьшает объём данных до тяжёлых операций.
Используйте allowDiskUse:true для больших группировок; ожидаемое увеличение времени: 2–5x, но RAM не будет переполняться.
Поддерживайте предагрегированные коллекции для тяжёлых, но часто запрашиваемых метрик (например, ежедневные счётчики). Обновления можно делать через change streams и background workers.
Используйте шардирование для наборов > 100GB и нагрузок с большим параллелизмом. Минимальные требования для кластера шардирования: 3 конфигурации + 3 реплики шардов, суммарно 12+ vCPU и 32+ GB RAM для production на 2026.
Если totalDocsExamined значительно выше ожидаемого, проверьте отсутствие COLLSCAN и улучшите индекс.
Дополнительно: используйте Redis 7 (2025) как кэш для готовых результатных наборов dashboard-метрик. См. статью в категории database для практик кеширования.
Какие антипаттерны?
Список распространённых антипаттернов при использовании aggregation pipeline и способы их избегать.
Антипаттерн: длинные монолитные конвейеры (>20 этапов) без промежуточной валидации. Проблема: трудно отлаживать и поддерживать, растёт время выполнения. Решение: разбивать на логические блоки и тестировать промежуточные результаты.
Антипаттерн: использование $facet для большого объёма данных без allowDiskUse. Последствие: OOM. Решение: разбить запросы, выполнить параллельно или включить дисковую поддержку.
Антипаттерн: джойны с коллекциями, которые не индексированы по полю связи. Последствие: COLLSCAN и рост latency. Решение: индексировать внешние ключи либо вынести join на уровень ETL/приложения.
Антипаттерн: хранение больших массивов в одном документе и последующая группировка внутри документа. Последствие: BSON size limit (16MB) и ухудшение обновлений. Решение: нормализовать либо лимитировать размер массивов, использовать bucket pattern.
Антипаттерн: хранение денег в float. Последствие: погрешности при суммировании. Решение: хранить в целых (cents) и проектировать вычисления в целых.
Если итоговые метрики нужны часто и рассчитаны по одним и тем же ключам — поддерживайте предагрегированные коллекции и обновляйте их инкрементально через change streams.
Частые вопросы
Как выбрать между $lookup и denormalization?
Выбор зависит от характера данных и частоты обновлений. Если связанные данные редко меняются и читаются вместе с основными документами, денормализация уменьшит задержки и уберёт необходимость в join на чтении. Если же отношение «один-ко-многим» с большим количеством связанных записей или частыми изменениями в связанной сущности, выгоднее хранить отдельную коллекцию и использовать $lookup. Для high-read сценариев предпочтительнее денормализация с механизмом обновления данных через батч-процессы или change streams. При выборе учитывайте размер документов: MongoDB ограничивает BSON до 16MB, поэтому нельзя релизовать полную денормализацию для очень больших связанных массивов.
Что делать, если $group вызывает OOM?
Первое действие — включить allowDiskUse: true для переноса промежуточных массивов на диск. Второй шаг — сократить объём данных перед $group с помощью $match и $project. Третье — разбить агрегацию по временным интервалам и затем объединить результаты. В production-инфраструктуре хорошее решение — поддержание предагрегатов (daily/hourly) и шардирование коллекции, если объём превышает 100–200 GB. Наконец, анализируйте план через .explain("executionStats"), чтобы понять, где именно потребление памяти критично.
Почему мой $lookup медленный, хотя users маленькая коллекция?
Даже маленькая коллекция может вызвать задержки, если джойны выполняются миллионами раз без индекса по внешнему полю. Проверьте, индексирован ли foreignField. Дополнительно убедитесь, что размер документов небольш, и что сеть/IO на сервере не перегружены. На тестовой машине Docker образ mongo:7.2 (~550 MB) загружается за ~8–20 секунд на типичном SSD; проблемы с IO напрямую влияют на время $lookup. Если latency остаётся высокой, рассмотрите кеширование связанной коллекции в RAM или на стороне приложения.
Когда стоит применять $setWindowFields?
$setWindowFields полезен для расчёта скользящих средних, ранжирования и других оконных функций без необходимости клиента аггрегировать данные. Используйте его для аналитики по времени, когда нужна информация о предыдущих значениях (lag/lead) или округлённые скользящие метрики. Поскольку это относительно тяжёлая операция, убедитесь, что входной набор предварительно отфильтрован и что вы ограничиваете объем обработанных документов — например, применяйте $match по диапазону времени.
Сколько RAM нужно для фасетов и больших группировок?
Минимальная рекомендация для серьёзных фасетов: 8 GB RAM на ноду при обработке десятков миллионов документов; для production-аналитики — 16–32 GB RAM и SSD NVMe. Если вы планируете использовать sharded cluster для аналитики, общий объём RAM и IO должен масштабироваться пропорционально объёму данных и требуемой параллельности. Всегда тестируйте с реалистичными данными: на локальной машине с 8 GB одна фасет-операция по 10M документов может завершаться за минуты с включённым дисковым режимом.
Дополнительные материалы и практические примеры по оптимизации агрегатов находятся в рубриках database и devops.
Комментарии (0)
Войдите или зарегистрируйтесь, чтобы оставить комментарий
Загрузка комментариев…