← назад к разделу

Когда данных становится много, один сервер перестаёт справляться — медленно читает, медленно пишет, и если упадёт, приложение встанет. MongoDB решает это двумя механизмами: репликация хранит копии данных на нескольких серверах (надёжность), шардинг разрезает коллекцию между серверами (горизонтальное масштабирование). Разберём оба.

Почему нельзя просто держать один сервер

Представьте интернет-магазин с миллионами товаров. При одном сервере:

  • Если сервер упадёт — сайт недоступен, продажи стоят.
  • Если данных больше, чем помещается в оперативную память — каждый запрос идёт на диск, скорость падает в десятки раз.
  • Нагрузка на запись ограничена скоростью одного диска.

Репликация решает первую проблему, шардинг — вторую и третью.

Replica set — резервные копии в реальном времени

Replica set — это группа серверов MongoDB, которые хранят одни и те же данные. Минимальная конфигурация — три узла: один primary принимает все записи, два secondary автоматически копируют изменения.

        ┌──────────┐
write → │ primary  │ ──── репликация ────┐
        └──────────┘                     │
                                         ▼
                              ┌──────────┐  ┌──────────┐
read ──────────────────────── │secondary │  │secondary │
                              └──────────┘  └──────────┘

Почему три, а не два? Потому что при сбое узлы голосуют, кто станет новым primary. Нужно большинство — при двух серверах одного голоса недостаточно. Три узла дают кворум.

Как работает oplog

Механизм репликации построен на oplog — специальной коллекции в базе local, куда primary записывает каждую успешную операцию. Secondary постоянно читают этот журнал и воспроизводят операции у себя.

Это похоже на бухгалтерскую книгу: primary записывает «добавлен товар X», «изменена цена Y», а secondary «переигрывают» эти действия. Если secondary отстал и снова подключился — он догоняет по oplog.

Один важный момент: oplog имеет фиксированный размер (по умолчанию около 5% свободного диска). Если secondary отстал сильно и нужных записей в oplog уже нет — придётся копировать весь массив данных заново (initial sync). Поэтому при большой нагрузке oplog стоит увеличивать.

Что происходит при падении primary

Secondaries обнаруживают пропажу primary через несколько секунд и начинают голосование. Побеждает тот, у кого самый свежий oplog. Вся процедура занимает 10–30 секунд — в это время кластер не принимает записи.

Важное правило: если из трёх узлов два недоступны — оставшийся один переходит в режим только для чтения. Это защита от split brain: если бы оба «выживших» сегмента продолжали принимать записи независимо, данные разошлись бы.

Куда идут запросы на чтение

По умолчанию все запросы идут на primary. Но можно направить чтение на secondary — чтобы разгрузить primary или читать из ближайшего географически узла. Это называется read preference:

РежимОткуда читаемКогда использовать
primary (по умолчанию)Только primaryКогда нужны самые актуальные данные
secondaryТолько secondaryАналитика, отчёты — не нагружать primary
secondaryPreferredSecondary, при недоступности — primaryНагрузка на чтение, данные могут немного отставать
nearestУзел с минимальной задержкойРаспределённые кластеры в разных регионах

Подводный камень: secondary может немного отставать от primary. Если вы только что записали данные и сразу читаете — используйте primary, иначе можете прочитать устаревшее.

db.product.find({ categoryId: 1 })
    .readPref("secondaryPreferred");

Когда одной replica set уже мало

Replica set решает проблему надёжности, но не масштабирования. Если данных настолько много, что они не помещаются в память одного сервера — запросы начинают ходить на диск, и скорость падает в десятки раз.

Практический ориентир: если активный набор данных (те документы, к которым обращаются чаще всего) занимает больше 70% оперативной памяти — пора думать о следующем шаге.

Следующий шаг в MongoDB — sharded cluster: данные физически разрезаются между несколькими replica set.

Sharded cluster — как устроено изнутри

В шардированном кластере четыре типа компонентов:

client → mongos (роутер) → Shard A (replica set)
                         → Shard B (replica set)
                         → Shard C (replica set)
                    ↕
             Config Servers (replica set из 3 узлов)
  • mongos — точка входа. Клиент подключается к mongos, не зная о шардах. Mongos смотрит в метаданные и направляет запрос на нужный шард. Mongos можно запустить несколько штук — он не хранит данные, работает как прокси.
  • Config servers — три узла, которые хранят карту: какие данные на каком шарде. Без них кластер не работает.
  • Shards — обычные replica set, каждый хранит свою часть данных.
  • Balancer — фоновый процесс, который следит за равномерностью. Если один шард перегружен — перемещает часть данных на другой.

Данные внутри коллекции делятся на chunks (куски по 128 МБ). Balancer перемещает chunks между шардами, чтобы нагрузка была примерно одинаковой.

Важно понимать цену: минимальный production-кластер — это 3 узла config servers + 2 шарда по 3 реплики + 2 mongos = 11 серверов. Это значительно дороже одной replica set. Шардинг включают только когда без него действительно не обойтись.

Shard key — самое важное решение при шардинге

Shard key — поле (или несколько полей), по которому MongoDB решает, на какой шард положить документ. Это решение принимается один раз и менять его сложно — поэтому выбор shard key требует внимания.

Равномерное распределение против горячего шарда

Представьте: магазин шардирует коллекцию товаров по полю _id типа ObjectId. ObjectId монотонно растёт со временем — все новые товары попадают на один и тот же шард. Он перегружен, остальные простаивают. Это называется hot shard — главная ошибка при шардинге.

Ranged sharding — документы распределяются по диапазонам значений. Подходит когда значения равномерно распределены или когда запросы часто фильтруют по диапазону (например, по дате):

sh.shardCollection("shop.product", { categoryId: 1 });

Hashed sharding — MongoDB сама хеширует значение, распределение всегда равномерное. Хорошо для монотонных ключей вроде ObjectId или timestamp. Минус: запросы по диапазону вынуждены опрашивать все шарды:

sh.shardCollection("shop.product", { _id: "hashed" });

Составной shard key

Часто лучший вариант — составной ключ: первая часть даёт разнообразие (равномерность), вторая — локальность для типичных запросов.

Если запросы обычно идут по categoryId, а в каждой категории товаров разное количество:

sh.shardCollection("shop.product", { categoryId: 1, _id: "hashed" });

Товары одной категории хешируются по _id и распределяются по шардам равномерно. При этом запросы по categoryId идут на меньшее число шардов, чем при чистом хешировании по _id.

Zoned sharding — данные в нужном регионе

Если регуляторика требует хранить данные пользователей в определённой стране — используется zoned sharding: диапазонам значений shard key назначаются теги, шардам присваиваются те же теги:

sh.addShardTag("shardEU", "EU");
sh.addShardTag("shardUS", "US");

sh.addTagRange(
    "shop.product",
    { region: "EU", productId: MinKey },
    { region: "EU", productId: MaxKey },
    "EU"
);

Данные с region: "EU" физически останутся на европейских серверах.

Как выбрать хороший shard key

Пять критериев, которые нужно проверить:

  1. Высокая кардинальность — много разных значений. Поле status с тремя значениями не позволит распределить данные по десяти шардам.
  2. Равномерное распределение — иначе горячий шард. userId обычно хорошо, country для одной страны — плохо.
  3. Присутствует в большинстве запросов — иначе каждый запрос опрашивает все шарды (scatter-gather), что медленно.
  4. Редко меняется — shard key практически неизменный. Изменение с 5.0 возможно через reshardCollection, но это долгая операция.
  5. Запись идёт на один шард — каждая операция записи попадает на конкретный шард без распределённых транзакций.

Практический совет: если коллекция product шардируется, а коллекция category нет — запросы с $lookup между ними вынуждены опрашивать все шарды. Лучше шардировать обе коллекции по categoryId: тогда связанные документы окажутся физически рядом и соединение останется локальным.

Коротко

  • Replica set — три и более узлов, хранящих одни данные. Primary принимает записи, secondary реплицируют через oplog.
  • При падении primary узлы голосуют и выбирают нового — 10–30 секунд без записи. Это нормально.
  • Запись с w: "majority" переживёт failover; с w: 1 — нет.
  • Oplog — журнал операций; если secondary отстал дальше его границы, нужен полный начальный sync.
  • Чтение можно направить на secondary через readPreference — разгрузить primary или читать из ближайшего узла.
  • Sharded cluster нужен когда активные данные не помещаются в память одного сервера. Минимум 11 узлов — дорого.
  • Shard key — главное решение: высокая кардинальность, равномерность, присутствие в запросах, редкие изменения.
  • Монотонный ключ без хеширования создаёт hot shard — все записи в один шард. Решение: hashed шардинг.
  • Составной shard key часто лучше одного поля: равномерность + локальность запросов.

Что почитать дальше

  • ACID и согласованность в MongoDB — какие гарантии работают в sharded cluster и как использовать causal consistency.
  • Моделирование документов — правильная схема снижает нагрузку и откладывает потребность в шардинге.