Тюнинг производительности MongoDB (WiredTiger, индексация)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Тюнинг производительности MongoDB (WiredTiger, индексация)
Сложная
~2-3 рабочих дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Тюнинг производительности MongoDB (WiredTiger, индексация)

MongoDB с WiredTiger как движком хранилища — по умолчанию выделяет 50% доступной RAM под кэш. На сервере с 32 ГБ это 16 ГБ кэша. Звучит щедро, но дьявол в деталях: неэффективные индексы, коллекции без нужных индексов, операции без hint вообще не используют кэш — они читают с диска страницу за страницей.

WiredTiger Cache: настройка

# /etc/mongod.conf
storage:
  wiredTiger:
    engineConfig:
      # Явное указание размера кэша (по умолчанию: max(50% RAM - 1GB, 256MB))
      cacheSizeGB: 12  # Для сервера 32 GB, оставляем ~20 GB для ОС и других процессов

      # Журнал транзакций
      journalCompressor: snappy  # snappy быстрее zlib, чуть меньше сжатие

    collectionConfig:
      # Алгоритм сжатия данных
      blockCompressor: snappy  # snappy — баланс скорость/сжатие

    indexConfig:
      # Сжатие индексов
      prefixCompression: true  # экономит место в памяти и на диске

Мониторинг состояния кэша:

const cacheStats = db.serverStatus().wiredTiger.cache

printjson({
    "cache_size_MB": Math.round(cacheStats["maximum bytes configured"] / 1024 / 1024),
    "currently_in_cache_MB": Math.round(cacheStats["bytes currently in the cache"] / 1024 / 1024),
    "dirty_bytes_MB": Math.round(cacheStats["tracked dirty bytes in the cache"] / 1024 / 1024),
    "pages_read_from_disk": cacheStats["pages read into cache"],
    "pages_evicted": cacheStats["pages evicted by application threads"],
    // Если evicted > 0 — кэш под давлением, возможно нужно увеличить cacheSizeGB
})

Стратегия индексирования

Индексы — главный инструмент производительности. Без индекса — COLLSCAN, полный перебор каждого документа.

Правило ESR (Equality, Sort, Range) для составных индексов:

// Запрос: найти активные заказы пользователя, отсортировать по дате
db.orders.find({
    user_id: ObjectId("..."),    // Equality
    status: "active"             // Equality (второй)
}).sort({ created_at: -1 })      // Sort

// Правильный индекс: сначала equality-поля, потом sort
db.orders.createIndex(
    { user_id: 1, status: 1, created_at: -1 },
    { name: "idx_user_status_date", background: true }
)

// НЕПРАВИЛЬНО: range перед sort
// { user_id: 1, created_at: -1, status: 1 } — sort не использует индекс

Индексы для специфичных паттернов

// Мультиключевой индекс для массивов тегов
db.articles.createIndex({ tags: 1 })
// Документ: { tags: ["nodejs", "mongodb", "backend"] }
// Запрос: db.articles.find({ tags: "mongodb" }) — использует индекс

// Text индекс для полнотекстового поиска
db.articles.createIndex(
    { title: "text", body: "text" },
    { weights: { title: 10, body: 1 }, default_language: "russian" }
)
db.articles.find({ $text: { $search: "индексирование MongoDB" } })

// Sparse индекс — только документы с полем (экономит место)
db.users.createIndex(
    { phone: 1 },
    { sparse: true, unique: true }
)

// 2dsphere для геолокации
db.stores.createIndex({ location: "2dsphere" })
db.stores.find({
    location: {
        $near: {
            $geometry: { type: "Point", coordinates: [37.618, 55.752] },
            $maxDistance: 5000  // 5 км
        }
    }
})

Aggregation Pipeline: оптимизация

// Медленно: $match после $lookup
db.orders.aggregate([
    { $lookup: { from: "users", localField: "user_id", foreignField: "_id", as: "user" } },
    { $match: { "user.country": "RU" } }  // ПЛОХО: lookup всей коллекции
])

// Быстро: $match как можно раньше в пайплайне
db.orders.aggregate([
    { $match: { status: "completed", created_at: { $gte: ISODate("2025-01-01") } } },
    // ^ Использует индекс, фильтрует ДО lookup
    { $lookup: {
        from: "users",
        localField: "user_id",
        foreignField: "_id",
        as: "user",
        pipeline: [
            { $match: { country: "RU" } },  // Фильтруем внутри lookup
            { $project: { name: 1, email: 1 } }  // Только нужные поля
        ]
    }},
    { $project: { _id: 1, total: 1, "user.name": 1 } }
])

// allowDiskUse для тяжёлых aggregation (если превышает 100 MB RAM)
db.orders.aggregate([...], { allowDiskUse: true })

Профайлер и анализ запросов

// Включить профайлер для медленных операций
db.setProfilingLevel(1, { slowms: 50 })

// Анализ результатов
db.system.profile.aggregate([
    { $match: { millis: { $gt: 50 } } },
    { $group: {
        _id: "$command.filter",
        count: { $sum: 1 },
        avg_ms: { $avg: "$millis" },
        max_ms: { $max: "$millis" }
    }},
    { $sort: { avg_ms: -1 } },
    { $limit: 10 }
])

// Принудительный explain для подозрительного запроса
db.orders.find({ status: "pending" }).explain("allPlansExecution")
// "winningPlan" — выбранный план
// "rejectedPlans" — альтернативы, которые рассматривались
// "executionStats.totalDocsExamined" vs "nReturned" — selectivity индекса

Aggregation: $facet для параллельных пайплайнов

// Вместо нескольких отдельных запросов — один $facet
db.products.aggregate([
    { $match: { category: "electronics", price: { $gte: 1000 } } },
    { $facet: {
        "total_count": [{ $count: "count" }],
        "by_brand":    [{ $group: { _id: "$brand", count: { $sum: 1 } } }, { $sort: { count: -1 } }],
        "price_stats": [{ $group: { _id: null, min: { $min: "$price" }, max: { $max: "$price" }, avg: { $avg: "$price" } } }],
        "paginated":   [{ $sort: { price: 1 } }, { $skip: 0 }, { $limit: 20 }]
    }}
])
// Один round-trip вместо четырёх запросов

Read Preference на Replica Set

// Аналитические запросы — на secondary, без нагрузки на primary
const client = new MongoClient(uri, {
    readPreference: ReadPreference.SECONDARY_PREFERRED
})

// Или для конкретного запроса
db.orders.find({ status: "completed" })
    .readPreference("secondaryPreferred")
    .hint({ status: 1, created_at: -1 })

Шардирование: когда и как

Шардинг добавляет сложность — нужен mongos, config servers, выбор shard key. Имеет смысл при:

  • Данных > 100–200 ГБ, которые не помещаются на один сервер
  • Записи > 10 000 RPS, которые не справляется обработать один primary
sh.enableSharding("mydb")

// Ключ шардирования: высокая кардинальность, монотонный рост плохо
// Хорошо: хеш от user_id (равномерное распределение)
sh.shardCollection("mydb.events", { user_id: "hashed" })

// Плохо: монотонно растущий _id (все записи идут в один шард)
// sh.shardCollection("mydb.events", { _id: 1 })  // НЕ ДЕЛАТЬ

Практический чеклист тюнинга

  • cacheSizeGB установлен явно исходя из RAM сервера
  • Все регулярные запросы покрыты индексами (проверка через profiler)
  • Нет индексов с accesses.ops == 0 (удалить неиспользуемые)
  • $match стоит первым в aggregation pipeline
  • Аналитика читается с secondary
  • allowDiskUse включён только там, где реально нужно
  • Размер oplog покрывает минимум 4–8 часов операций
  • WiredTiger checkpoint interval = 60s (дефолт, обычно не менять)