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 — это осознанный компромисс скорость/надёжность под конкретные данные.
Проверьте себя
1. Что гарантирует write concern w: "majority" по сравнению с w: 1?
AЗапись подтверждена большинством узлов и переживёт сбой primary, тогда как при w:1 она может откатиться
BЗапись выполняется быстрее, чем при w:1
CЗапись попадает только на один узел
DЗапись автоматически становится линеаризуемой при чтении
2. Чем read concern majority отличается от local?
Amajority всегда возвращает данные быстрее, чем local
Bmajority читает только данные, подтверждённые большинством (их не откатит), а local может вернуть ещё не подтверждённые данные
Clocal читает с диска, а majority из оперативной памяти
Dlocal гарантирует самую строгую линеаризуемость