Read concern и Write concern
Две настройки, которыми вы выбираете баланс «быстро» против «надёжно» для каждой операции записи и чтения.
Write concern отвечает на вопрос «сколько узлов и насколько прочно должны подтвердить запись, прежде чем сервер скажет ОК». Read concern — на вопрос «насколько свежие и насколько устойчивые к откату данные я хочу прочитать».
Зачем на практике
В репликасете данные есть на нескольких узлах. Можно подтвердить запись после первого же узла (быстро, но при падении primary запись может откатиться) или дождаться большинства (медленнее, зато долговечно). Эти два уровня и настраиваются через write/read concern. Понимание этой пары — ключ к тому, чтобы не потерять платёж и при этом не утопить производительность там, где надёжность не критична.
Write concern: w и journal
Параметр w задаёт число подтверждающих узлов.
| Значение | Смысл |
w: 1 | подтвердил primary; быстро, но при потере primary запись может откатиться |
w: "majority" | подтвердило большинство узлов; запись переживёт сбой primary |
w: 0 | без подтверждения (fire-and-forget); максимально быстро, без гарантий |
j: true | дождаться записи в журнал на диск (durability на узле) |
wtimeout | сколько ждать подтверждения, прежде чем вернуть ошибку |
// надёжная запись платежа: большинство узлов + журнал, ждать не дольше 5 секунд
db.payments.insertOne(
{ _id: "p_1", sum: 990, status: "paid" },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
)
Журналирование (j: true) означает, что узел успел сбросить операцию в журнал на диск, и она переживёт перезапуск процесса. w: "majority" добавляет долговечность на уровне кластера: даже падение primary не откатит подтверждённую запись, потому что её уже приняло большинство.
Важная тонкость про wtimeout
Если за wtimeout большинство не успело подтвердить, клиент получит ошибку — но это НЕ откат: запись могла всё-таки примениться на primary. wtimeout ограничивает ожидание, а не отменяет операцию. Поэтому ошибку по таймауту нельзя трактовать как «записи нет».
Read concern: насколько свежо и устойчиво
Read concern управляет тем, какие данные вернёт чтение по отношению к репликации и возможным откатам.
| Уровень | Что гарантирует |
local | последние данные узла; могут быть ещё не подтверждены большинством и теоретически откатиться |
majority | только данные, подтверждённые большинством — их не откатит |
linearizable | самые свежие подтверждённые данные на момент чтения; строжайше, но медленно и только для одиночного документа на primary |
snapshot | согласованный снимок (используется в транзакциях) |
// читаем только то, что точно не откатится
db.payments.find({ status: "paid" }).readConcern("majority")
Разница ощутима: local может показать запись, которую большинство ещё не приняло — и если в этот момент сменится primary, такой записи в итоге может не оказаться. majority читает только «застрахованные» данные. linearizable идёт ещё дальше и гарантирует, что вы увидите результат любой записи, завершившейся до начала вашего чтения, — но платите за это задержкой и ограничением (один документ, чтение с primary).
Компромисс скорость/надёжность
Связка read+write concern — это ползунок между латентностью и гарантиями. Подбирайте под данные.
| Данные | Разумный выбор |
| Платежи, заказы, баланс | w: "majority" + read majority |
| Профиль, контент | w: "majority", чтение local допустимо |
| Метрики, логи, аналитика | w: 1, иногда w: 0 |
| Строгий «прочитать ровно последнее» | linearizable точечно |
Как это работает под капотом
Каждый узел репликасета держит oplog и отслеживает «majority commit point» — позицию, до которой операции подтверждены большинством. w: "majority" заставляет primary дождаться, пока вторичные применят запись и продвинут эту точку. Read concern majority читает данные не новее этой же точки — поэтому видит лишь то, что уже не может откатиться. linearizable дополнительно проверяет, что узел всё ещё primary (через «no-op» подтверждение большинством), чтобы исключить чтение со старого, отколовшегося лидера.
Частые ошибки
- w: 0 для важных данных. Fire-and-forget годится для метрик, но не для денег: вы не узнаете даже об ошибке.
- Таймаут принять за откат. Ошибка по
wtimeoutне означает, что записи нет; нужна идемпотентность и перепроверка. - linearizable везде. Он дорог и ограничен одним документом на primary; для широких выборок не подходит.
- Несогласованная пара. Запись с
w: 1и чтение сmajorityмогут «не увидеть» только что записанное — выбирайте уровни осознанно. - Полагаться на дефолт. Уровень по умолчанию мог быть изменён на кластере; для критики задавайте concern явно.
Итоги
- Write concern
wопределяет, сколько узлов подтвердят запись:1— быстро,majority— долговечно. j: trueждёт журнал на диск;wtimeoutограничивает ожидание, но не откатывает запись.- Read concern
majorityчитает только неоткатываемые данные,local— самые свежие, но рискованные. linearizableдаёт строжайшую свежесть ценой задержки и работает по одному документу на primary.- Пара read+write concern — это осознанный компромисс скорость/надёжность под конкретные данные.