АрхитектураАрхитектура данныхРазработчик серверных систем

Два процесса одновременно изменяют разные поля одного документа. Как механизм версионирования предотвращает...

Два процесса одновременно изменяют разные поля одного документа. Как механизм версионирования предотвращает потерю одного из обновлений?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Механизм оптимистического контроля конкуренции хранит вместе с документом его версию. Процесс при обновлении указывает версию, на основе которой он читал документ; хранилище применяет изменение только при совпадении версии и атомарно увеличивает её.

Если другой процесс уже изменил документ, проверка версии не проходит. Обновление отклоняется, после чего приложение перечитывает актуальный документ и повторяет операцию или разрешает конфликт по правилам предметной области.

Исторический контекст

Документная модель хранения появилась как удобный способ хранить агрегаты, которые обычно читаются и изменяются целиком. Она уменьшает количество связей между таблицами и допускает более гибкую структуру данных.

Однако гибкость документа не устраняет конкуренцию. При распространённом сценарии чтения документа, изменения его в памяти и последующей записи два процесса могут работать со старой копией и затереть результаты друг друга.

Постановка проблемы

Пусть оба процесса прочитали документ версии 7. Первый изменил поле адреса и записал версию 8, а второй, не зная об этом, изменил телефон и записал свою копию как новую. Если запись безусловно заменяет весь документ, изменение адреса может исчезнуть.

Особенно опасна такая ошибка при частичном обновлении, которое технически реализовано как замена полного документа. Успешный статус записи ещё не означает, что обновление было выполнено поверх актуального состояния.

Подробное решение

Документ получает монотонный номер версии или другой маркер ревизии. При чтении процесс сохраняет этот маркер, а при записи передаёт условие: применить новую версию только если текущая версия всё ещё равна прочитанной.

Проверка версии и запись должны выполняться одной атомарной операцией на стороне хранилища. При успехе версия увеличивается. При несовпадении хранилище сообщает о конфликте, а не молча перезаписывает более новое состояние.

После конфликта возможны разные стратегии:

  • перечитать документ, повторно применить изменение и попробовать записать его относительно новой версии;
  • объединить изменения по полям, если предметная область гарантирует, что они независимы;
  • передать конфликт на ручное разрешение или отклонить операцию;
  • заменить полную запись атомарным обновлением конкретного поля, если такая операция поддерживается и не требует проверки других данных.

Версионирование не делает конфликт невозможным и не определяет правильный результат слияния. Оно лишь обнаруживает, что исходные данные устарели, поэтому правила разрешения должны находиться в приложении или в модели данных.

Автоматическое слияние по полям безопасно не всегда. Например, одновременные изменения лимита, статуса или набора ролей могут быть логически конфликтующими, даже если затрагивают разные поля документа.

Ситуация из практики

В профиле клиента хранились адрес, контактные данные и настройки уведомлений. Веб-приложение редактировало профиль целиком, а фоновый процесс отдельно обновлял настройки. Иногда сохранение профиля стирало только что изменённые настройки фонового процесса.

Рассматривались три варианта. Полная безусловная замена была простой, но сохраняла потерю обновлений. Блокировки уменьшали вероятность конкуренции, однако усложняли масштабирование и увеличивали время ожидания. Переход на независимые атомарные изменения полей устранял часть конфликтов, но не подходил для операций, где нужно согласованно изменить несколько полей.

Выбрали версионирование документа для целостных изменений и условные частичные обновления для независимых настроек. При конфликте целостное изменение перечитывалось и повторялось ограниченное число раз, после чего операция возвращала явную ошибку. В результате тихая потеря обновлений исчезла, а редкие реальные конфликты стали наблюдаемыми и обрабатываемыми.

Что кандидаты часто упускают

  1. Достаточно ли проверять версию только перед записью?

Нет. Проверка должна быть частью атомарной операции записи. Если сначала отдельно прочитать текущую версию, затем проверить её в приложении и только потом записать документ, другой процесс может изменить документ между этими действиями. Такое разделение снова оставляет окно для гонки.

  1. Можно ли всегда объединять изменения разных полей автоматически?

Нет. Физическая независимость полей не гарантирует независимость бизнес-операций. Изменение адреса и лимита может затрагивать разные поля, но зависеть от общего состояния клиента или от одного правила. Автоматическое слияние допустимо только при явно доказанной семантической независимости и сохранении инвариантов.

  1. Чем версионирование отличается от блокировки?

Версионирование обычно относится к оптимистическому подходу: процессы не удерживают блокировку во время чтения, а конфликт обнаруживается при записи. Это хорошо работает, когда столкновения редки, и позволяет лучше использовать ресурсы при высокой конкуренции за разные документы.

Блокировка относится к пессимистическому подходу: она заранее ограничивает одновременное изменение ресурса. Блокировка может снизить число конфликтов, но создаёт ожидание, риск дедлоков и дополнительные требования к восстановлению после сбоев. Выбор зависит от частоты конфликтов, длительности операций и требований к задержке.