13. MongoDB: Replica Sets
Replica Set — это группа MongoDB серверов (nodes), которые хранят одинаковые данные. Обеспечивает высокую доступность (HA) и отказоустойчивость.
Архитектура Replica Set
Заголовок раздела «Архитектура Replica Set»graph TD A[Application] --> B[Primary] A -.-> C[Secondary 1] A -.-> D[Secondary 2]
B -->|Replication| C B -->|Replication| D
B -->|Heartbeat| C B -->|Heartbeat| D C -->|Heartbeat| DРоли:
- Primary: Принимает все write операции
- Secondary: Реплицируют данные с Primary, могут обслуживать read (если настроено)
- Arbiter: Участвует только в выборах, не хранит данные
Зачем нужны Replica Sets?
Заголовок раздела «Зачем нужны Replica Sets?»✅ Преимущества:
- High Availability: При падении Primary автоматически выбирается новый
- Data Redundancy: Данные хранятся на нескольких серверах
- Read Scaling: Можно читать с Secondary (trade-off: eventual consistency)
- Zero-Downtime Maintenance: Обновление серверов по одному
Настройка Replica Set
Заголовок раздела «Настройка Replica Set»Локальная настройка (для тестирования)
Заголовок раздела «Локальная настройка (для тестирования)»# Создание 3 nodesmkdir -p /data/rs0-1 /data/rs0-2 /data/rs0-3
# Запуск nodesmongod --replSet rs0 --port 27017 --dbpath /data/rs0-1 --bind_ip localhostmongod --replSet rs0 --port 27018 --dbpath /data/rs0-2 --bind_ip localhostmongod --replSet rs0 --port 27019 --dbpath /data/rs0-3 --bind_ip localhostИнициализация Replica Set
Заголовок раздела «Инициализация Replica Set»// Подключение к одному из nodesmongosh --port 27017
// Инициализацияrs.initiate({ _id: "rs0", members: [ { _id: 0, host: "localhost:27017", priority: 2 }, // Primary предпочтительнее { _id: 1, host: "localhost:27018" }, { _id: 2, host: "localhost:27019" } ]})
// Проверка статусаrs.status()rs.isMaster() // или rs.hello()Добавление/удаление членов
Заголовок раздела «Добавление/удаление членов»// Добавить Secondaryrs.add("localhost:27020")
// Добавить Arbiterrs.addArb("localhost:27021")
// Удалить членrs.remove("localhost:27020")
// Изменить конфигурациюcfg = rs.conf()cfg.members[0].priority = 3 // увеличить приоритетrs.reconfig(cfg)Опции членов Replica Set
Заголовок раздела «Опции членов Replica Set»rs.add({ _id: 3, host: "localhost:27020", priority: 0, // не может стать Primary hidden: true, // скрыт от приложений slaveDelay: 3600 // отстаёт на 1 час (для восстановления)})
// Delayed member полезен для защиты от ошибок// Если случайно удалили данные, можно восстановить с delayed memberWrite Concern
Заголовок раздела «Write Concern»Write Concern определяет уровень гарантий записи.
// w: 1 (default) - подтверждение от Primarydb.users.insertOne( { username: "john" }, { writeConcern: { w: 1 } })
// w: "majority" - подтверждение от большинстваdb.users.insertOne( { username: "alice" }, { writeConcern: { w: "majority", wtimeout: 5000 } })// Если не получено подтверждение за 5 сек - ошибка
// w: 0 - без подтверждения (fire and forget)db.users.insertOne( { username: "bob" }, { writeConcern: { w: 0 } })
// j: true - запись в journal (durability)db.users.insertOne( { username: "eve" }, { writeConcern: { w: "majority", j: true } })Trade-offs:
w: 1- быстро, но можно потерять данные при падении Primaryw: "majority"- медленнее, но данные надёжно сохраненыj: true- ещё медленнее, но гарантирует durability
Read Preference
Заголовок раздела «Read Preference»Read Preference определяет, откуда читать данные.
// primary (default) - только с Primarydb.users.find().readPref("primary")
// primaryPreferred - Primary, если доступен, иначе Secondarydb.users.find().readPref("primaryPreferred")
// secondary - только с Secondarydb.users.find().readPref("secondary")
// secondaryPreferred - Secondary, если доступен, иначе Primarydb.users.find().readPref("secondaryPreferred")
// nearest - ближайший по latencydb.users.find().readPref("nearest")⚠️ Важно: Чтение с Secondary может вернуть устаревшие данные!
Read Concern
Заголовок раздела «Read Concern»Read Concern определяет уровень консистентности чтения.
// local (default) - возвращает последние данные с текущего nodedb.users.find().readConcern("local")
// majority - данные, подтверждённые большинствомdb.users.find().readConcern("majority")
// linearizable - строгая консистентность (медленно!)db.users.findOne({ _id: 1 }).readConcern("linearizable")Failover и Elections
Заголовок раздела «Failover и Elections»При падении Primary автоматически запускаются выборы (elections).
graph TD A[Primary падает] --> B[Secondaries обнаруживают] B --> C[Выборы нового Primary] C --> D[Secondary с наивысшим приоритетом становится Primary] D --> E[Приложения переключаются на нового Primary]// Принудительный перезапуск выборовrs.stepDown(60) // Primary step down на 60 секунд
// Заморозить Secondary (не участвует в выборах)rs.freeze(120) // на 120 секундМониторинг Replica Set
Заголовок раздела «Мониторинг Replica Set»// Статус replica setrs.status()
// Информация о репликацииrs.printReplicationInfo() // на Primaryrs.printSlaveReplicationInfo() // на Secondary
// Oplog статистикаuse localdb.oplog.rs.stats()
// Текущий oplog lagdb.getReplicationInfo()Oplog (operations log) — это capped коллекция в БД local, которая хранит все операции записи.
// Просмотр oploguse localdb.oplog.rs.find().sort({ $natural: -1 }).limit(10)
// Размер oplogdb.oplog.rs.stats().maxSize / (1024 * 1024 * 1024) // GB
// Изменение размера oplog (MongoDB 4.0+)use admindb.adminCommand({ replSetResizeOplog: 1, size: 10240 }) // 10GBВажно: Oplog должен быть достаточно большим, чтобы Secondary успевали реплицироваться!
TypeScript примеры
Заголовок раздела «TypeScript примеры»import { MongoClient } from 'mongodb';
// Connection String с replica setconst uri = 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0';const client = new MongoClient(uri);
async function replicaSetExamples() { await client.connect(); const db = client.db('myapp');
// Write с majority write concern await db.collection('users').insertOne( { writeConcern: { w: 'majority', wtimeout: 5000 } } );
// Read с secondary preference const users = await db.collection('users') .find() .readPref('secondary') .toArray();
// Read с majority read concern const user = await db.collection('users') .find({ username: 'john' }) .readConcern('majority') .toArray();
await client.close();}
// Мониторинг replica set healthasync function monitorReplicaSet() { await client.connect(); const admin = client.db('admin');
// Статус replica set const status = await admin.command({ replSetGetStatus: 1 }); console.log('Replica Set Status:', status);
// Проверка Primary const isMaster = await admin.command({ isMaster: 1 }); console.log('Is Master:', isMaster.ismaster); console.log('Primary:', isMaster.primary); console.log('Secondaries:', isMaster.hosts);
// Oplog info const local = client.db('local'); const oplogStats = await local.collection('oplog.rs').stats(); console.log('Oplog size:', oplogStats.size);
await client.close();}
// Mongoose с Replica Setimport mongoose from 'mongoose';
await mongoose.connect( 'mongodb://localhost:27017,localhost:27018,localhost:27019/myapp?replicaSet=rs0', { readPreference: 'secondaryPreferred', w: 'majority', wtimeoutMS: 5000 });Production Best Practices
Заголовок раздела «Production Best Practices»1. Минимум 3 members
Заголовок раздела «1. Минимум 3 members»// ❌ Плохо: 2 members (нет majority при падении одного)// ✅ Хорошо: 3+ membersrs.initiate({ _id: "rs0", members: [ { _id: 0, host: "server1:27017" }, { _id: 1, host: "server2:27017" }, { _id: 2, host: "server3:27017" } ]})2. Odd number of voting members
Заголовок раздела «2. Odd number of voting members»Если чётное число members, добавьте arbiter:
rs.addArb("arbiter:27017")3. Распределение по датацентрам
Заголовок раздела «3. Распределение по датацентрам»rs.initiate({ _id: "rs0", members: [ { _id: 0, host: "dc1-server1:27017", priority: 2 }, // Primary в DC1 { _id: 1, host: "dc1-server2:27017" }, { _id: 2, host: "dc2-server1:27017", priority: 0 }, // DR в DC2 { _id: 3, host: "dc2-server2:27017", priority: 0 } ]})4. Hidden member для backups
Заголовок раздела «4. Hidden member для backups»rs.add({ _id: 4, host: "backup-server:27017", priority: 0, hidden: true, votes: 0 // не участвует в выборах})
// Backup с hidden member не влияет на productionОбновление Replica Set (Zero Downtime)
Заголовок раздела «Обновление Replica Set (Zero Downtime)»# 1. Обновить Secondaries по одному# Secondary 1mongosh --port 27018use admindb.shutdownServer()# Обновить бинарник MongoDBmongod --replSet rs0 --port 27018 --dbpath /data/rs0-2
# Secondary 2# ... аналогично
# 2. Step down Primarymongosh --port 27017rs.stepDown(60)
# 3. Обновить бывший Primaryuse admindb.shutdownServer()# Обновить бинарникmongod --replSet rs0 --port 27017 --dbpath /data/rs0-1💡 Best Practices
Заголовок раздела «💡 Best Practices»- Write Concern: Используйте
w: "majority"для критичных данных - Read Preference:
secondaryPreferredдля read-heavy нагрузки - Oplog Size: Минимум 24 часа операций
- Monitoring: Настройте алерты на replication lag
- Backups: Делайте с hidden member
Troubleshooting
Заголовок раздела «Troubleshooting»Высокий Replication Lag
Заголовок раздела «Высокий Replication Lag»// Проверка lagrs.printSlaveReplicationInfo()
// Причины:// 1. Слабый Secondary (CPU, disk I/O)// 2. Большая write нагрузка// 3. Медленная сеть// 4. Маленький oplog
// Решения:// - Увеличить oplog// - Улучшить hardware Secondary// - Использовать secondary reads с осторожностьюSplit Brain (редко)
Заголовок раздела «Split Brain (редко)»Когда несколько members считают себя Primary:
// Решение: force reconfigurationcfg = rs.conf()cfg.version++rs.reconfig(cfg, { force: true })⚠️ Частые ошибки
Заголовок раздела «⚠️ Частые ошибки»- 2 members без arbiter (нет majority)
- Чтение с secondary без учёта lag
- Маленький oplog
- Игнорирование write concern (потеря данных)
Следующий урок: Транзакции в MongoDB →