Гайды

Индексы и aggregation pipeline в MongoDB

createIndex, compound и partial индексы, explain executionStats, стадии $match $lookup $group и оптимизация пайплайна.

~13 мин чтения

Индексы и aggregation pipeline в MongoDB

Без индексов MongoDB на больших коллекциях часто делает COLLSCAN. Aggregation Pipeline — основной способ отчётов, join-подобных операций и подготовки данных для API. Опирается на «Введение в MongoDB: CRUD и схемы»; про шарды и scatter-gather — «Репликация и шардирование MongoDB».


1. Зачем индексы

Индекс — структура (часто B-tree), сопоставляющая значение ключа списку документов. Планировщик выбирает IXSCAN вместо COLLSCAN, если фильтр/сорт покрываются индексом.

Цена: каждый индекс ускоряет свой класс запросов, но замедляет записи и занимает место.


2. Создание и просмотр

javascript
db.orders.createIndex({ customerId: 1 })
db.users.createIndex({ email: 1 }, { unique: true })
db.orders.createIndex({ customerId: 1, createdAt: -1 })
db.orders.createIndex(
  { createdAt: -1 },
  { partialFilterExpression: { status: "new" } }
)
db.sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 })
javascript
db.orders.getIndexes()
db.orders.dropIndex("customerId_1_createdAt_-1")

3. Типы индексов (обзор)

ТипКогда
Single-fieldОдин ключ фильтра/сортировки
CompoundНесколько полей; порядок полей и направлений должен соответствовать запросу
MultikeyАвтоматически для массивов
Text$text
2dsphereGeoJSON
HashedЧасть shard key или равномерное распределение

Правило ESR для compound: Equality → Sort → Range.


4. Покрытие запроса (covered)

javascript
db.orders.find(
  { customerId: "c-1" },
  { _id: 0, customerId: 1, status: 1 }
).explain("executionStats")

Смотрите totalDocsExamined vs nReturned, IXSCAN, PROJECTION_COVERED.


5. explain

javascript
db.orders.find({ customerId: "x" }).sort({ createdAt: -1 }).explain("executionStats")
  • executionStats.executionTimeMillis
  • totalKeysExamined / totalDocsExamined
  • winningPlan.stage — избегать COLLSCAN на больших коллекциях без необходимости

Для агрегации:

javascript
db.orders.aggregate(
  [{ $match: { status: "paid" } }, { $group: { _id: "$customerId", n: { $sum: 1 } } }],
  { explain: true }
)

6. Pipeline: ментальная модель

Стадии идут последовательно; порядок сильно влияет на производительность.

СтадияНазначение
$matchФильтр — как можно раньше, с индексом
$project / $set / $unsetФорма документа
$lookupJoin с другой коллекцией
$groupАгрегация по _id
$sortБольшие сортировки могут требовать allowDiskUse
$limit / $skipskip больших смещений дорог — keyset-пагинация
$facetНесколько подпайплайнов
$unionWithОбъединение с другой коллекцией

Пример

javascript
db.orders.aggregate([
  {
    $match: {
      status: "paid",
      createdAt: { $gte: ISODate("2025-01-01"), $lt: ISODate("2026-01-01") }
    }
  },
  { $group: { _id: "$customerId", orders: { $sum: 1 }, revenue: { $sum: "$total" } } },
  { $sort: { revenue: -1 } },
  { $limit: 50 }
])

Индекс вроде { status: 1, createdAt: 1 } помогает первому $match.


7. $lookup

javascript
db.orders.aggregate([
  { $match: { status: "new" } },
  {
    $lookup: {
      from: "customers",
      localField: "customerId",
      foreignField: "_id",
      as: "customer"
    }
  },
  { $unwind: { path: "$customer", preserveNullAndEmptyArrays: true } }
])

На больших объёмах $lookup дорогой; иногда денормализация или два запроса выгоднее.


8. Оптимизация pipeline

  1. $match / $sort, покрываемые индексом, в начале (проверяйте explain).
  2. Узкий $project рано — меньше данных дальше.
  3. Избегайте тяжёлого $regex без якоря.
  4. $group после сильного $match.
  5. Тяжёлые отчёты: allowDiskUse: true и контроль памяти на сервере.

9. explain и индексы под pipeline

Перед выкладкой отчёта в прод прогоняйте:

javascript
db.orders.aggregate([/* stages */], { explain: "executionStats" })

Смотрите totalDocsExamined vs nReturned, стадии IXSCAN vs COLLSCAN, executionTimeMillis. Если ранний $match не использует индекс — переставьте стадии или добавьте partial индекс под типичный фильтр.


10. Шардированные коллекции

$match должен по возможности включать shard key (или префикс), иначе scatter-gather по всем шардам. Подробности — в гайде по репликации и шардированию.


См. также