Восстановление связи после сетевого разделения обнаружило две версии одной записи. Почему стратегия Last Write Wins может безвозвратно потерять корректное изменение?
Last Write Wins (LWW) выбирает одну версию по метке времени, а остальные отбрасывает. Если два изменения были приняты независимо во время разделения, более поздняя метка не означает, что это изменение содержит результат другого; поэтому корректная версия может быть удалена без возможности восстановления.
Подход LWW появился как простой способ автоматически разрешать конфликты в реплицируемых системах, которые продолжают принимать записи при недоступности части узлов. Он снижает стоимость синхронизации: после восстановления связи реплики сравнивают версии и оставляют одну победившую запись.
Цена простоты — потеря информации. LWW подходит для данных, где допустима замена значения, но опасен для независимых изменений, которые необходимо сохранить одновременно.
Пусть во время сетевого разделения две реплики независимо изменили один объект. Одна реплика записала адрес пользователя, другая — его телефон. После восстановления связи система видит две версии одной записи и должна выбрать результат.
LWW оставит только версию с большей меткой времени. Если записи представлены целиком, изменение телефона может удалить новый адрес или наоборот. Если часы узлов расходятся, более ранняя в реальном времени запись может получить большую метку и победить.
LWW хранит вместе со значением метку версии, обычно физическое время, логическое время или их комбинацию. При конфликте сравниваются метки; при равенстве применяется детерминированный идентификатор реплики. Это гарантирует сходимость реплик, но не гарантирует сохранение всех принятых изменений.
Главная ошибка — считать временную метку доказательством причинной зависимости. Запись с большим временем не обязательно была создана после чтения или учёта другой записи. Кроме того, физические часы могут иметь различия, а задержка сети не отражается в самой метке.
Безопасность LWW зависит от гранулярности данных. Если конфликтующие изменения относятся к независимым полям, лучше хранить поля отдельно либо использовать слияние на уровне полей. Для счётчиков, множеств и других структур, где важны все независимые операции, применяют подходящие CRDT или журнал операций.
Если автоматическое слияние невозможно, система должна сохранять обе конфликтующие версии и передавать конфликт на разрешение бизнес-логике. Для обнаружения причинной связи используют, например, версии или векторные часы; они позволяют отличить последовательные изменения от конкурентных, но сами по себе не выбирают бизнес-результат.
LWW можно сделать предсказуемее с помощью логических часов, привязанных к версии реплики, однако это не устраняет семантическую потерю данных. Детерминированный tie-breaker обеспечивает одинаковый результат на всех узлах, но не делает победившее значение правильным.
В профиле клиента во время разделения один регион изменил адрес, другой — номер телефона. Рассматривались три варианта.
Был выбран LWW на уровне полей для независимых атрибутов, а для связанных реквизитов — сохранение конфликта с последующим подтверждением пользователем. В результате автоматическое слияние не уничтожало независимые изменения, а потенциально противоречивые данные не выдавались как однозначно корректные.
Нет. Точные часы помогают упорядочивать события по времени, но не показывают, была ли одна запись причинно связана с другой. Даже при синхронизированных часах независимые записи могут иметь порядок меток, не отражающий их бизнес-смысл. Кроме того, равенство или небольшое расхождение времени требует tie-breaker, который только выбирает результат, но не доказывает его корректность.
Нет. Read repair может распространить выбранную версию на отставшие реплики, но не восстанавливает запись, которую LWW уже отбросил. Он исправляет расхождение копий относительно принятой политики, а не сохраняет все конкурентные изменения.
Он допустим, если более новая версия действительно должна полностью заменить старую, потеря конкурентного изменения приемлема, а порядок по используемой метке имеет требуемый смысл. Типичные примеры — кэшируемое состояние или значение, для которого важен только последний подтверждённый результат.
Для платежей, остатков, прав доступа и других данных с важными инвариантами LWW обычно недостаточен. Там нужны операции с явным слиянием, сериализация конфликтующих изменений, транзакционный владелец либо сохранение конфликтов для последующего разрешения.