В прошлом уроке транзакция гарантировала, что заказ и списание применятся вместе. Но на replica set данные лежат на нескольких узлах, и «применилось» может значить разное: на одном сервере, на большинстве или на всех. Сегодня разберём две настройки, которыми ты выбираешь эту степень уверенности: write concern для записи и read concern для чтения. Все примеры сняты с настоящего replica set из трёх узлов, в песочнице этих настроек нет.
Заказное письмо
Обычное письмо почтальон бросил в ящик, и ты не знаешь, дошло ли оно. Заказное письмо вручают под подпись. А важный договор отправляют с уведомлением о вручении и копией у нотариуса. Чем больше подтверждений ты требуешь, тем дольше ждёшь, но тем меньше шансов потерять документ.
Write concern это именно такой выбор для записи: какого подтверждения ждать, прежде чем считать запись сделанной.
Три уровня подтверждения
Начнём с того, что сервер делает по умолчанию:
db.adminCommand({ getDefaultRWConcern: 1 })
{
defaultReadConcern: { level: 'local' },
defaultWriteConcern: { w: 'majority', wtimeout: 0 },
defaultWriteConcernSource: 'implicit',
defaultReadConcernSource: 'implicit',
...
}
Ответ сокращён, служебные поля мы убрали. По умолчанию запись ждёт большинства узлов, а чтение берёт локальные данные. Вот основные варианты write concern:
| Значение | Ждём подтверждения | Что может потеряться |
|---|---|---|
w: 0 | ничего не ждём | всё, мы даже не узнаем об ошибке |
w: 1 | первичный узел принял запись | запись, если первичный упадёт до репликации |
w: 'majority' | большинство узлов | почти ничего, запись переживёт выборы |
j: true | запись в журнале на диске | добавка к любому w: переживёт перезапуск узла |
Задаётся он прямо в команде:
db.orders.insertOne(
{ _id: 6, customer_id: 3, created_at: new Date('2026-10-09'), status: 'new', items: [] },
{ writeConcern: { w: 'majority', j: true } }
)
{ acknowledged: true, insertedId: 6 }
Снаружи ответ ничем не отличается от обычного. Разница во времени ожидания и в том, что именно обещает этот ответ.
Ошибка, после которой запись осталась
Самое коварное в write concern то, как выглядят его ошибки. Попросим подтверждения от пяти узлов, хотя их всего три:
db.orders.insertOne({ _id: 7, customer_id: 1, status: 'new', items: [] }, { writeConcern: { w: 5 } })
Uncaught:
MongoWriteConcernError[UnsatisfiableWriteConcern]: Not enough data-bearing nodes
Additional information: { writeConcern: { w: 5, wtimeout: 0, provenance: 'clientSupplied' } }
Result: {
n: 1,
...
}
Посмотри на n: 1 в результате. Запись прошла, просто сервер не смог дать обещанное подтверждение. И правда:
db.orders.findOne({ _id: 7 })
{ _id: 7, customer_id: 1, status: 'new', items: [] }
Это главное правило: ошибка write concern не значит, что записи нет. Она значит «не могу подтвердить, что запись надёжна». Приложение, которое в ответ на такую ошибку просто повторит вставку, получит дубль.
Теперь ситуация из жизни. Остановим один из трёх узлов и попросим подтверждения от большинства и от всех трёх, с ограничением ожидания в две секунды через wtimeout:
db.orders.insertOne({ _id: 8, customer_id: 2, status: 'new', items: [] }, { writeConcern: { w: 'majority', wtimeout: 2000 } })
db.orders.insertOne({ _id: 9, customer_id: 2, status: 'new', items: [] }, { writeConcern: { w: 3, wtimeout: 2000 } })
{ acknowledged: true, insertedId: 8 }
Uncaught:
MongoWriteConcernError[WriteConcernTimeout]: waiting for replication timed out
Additional information: {
wtimeout: true,
writeConcern: { w: 3, wtimeout: 2000, provenance: 'clientSupplied' }
}
Result: {
n: 1,
...
}
Большинство это два узла из трёх, они живы, поэтому первая запись подтверждена сразу. Третий узел лежит, и вторая запись ждала его две секунды, а потом сдалась. Документ _id: 9 при этом всё равно в базе. Без wtimeout вторая команда ждала бы вечно, поэтому w больше единицы всегда пиши вместе с ограничением времени.
урок из курса
Урок из курса «MongoDB с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.