Два сервиса передают друг другу результаты операций через реплицируемое хранилище. Какую гарантию нужно обеспечить, чтобы зависимое событие не наблюдалось раньше породившего его?
Нужно обеспечить каузальную согласованность: если операция B зависит от операции A, ни один клиент не должен наблюдать B раньше A. Для этого система сохраняет информацию о причинной зависимости и передаёт её вместе с запросами или метаданными репликации.
В географически распределённых системах синхронная репликация часто даёт высокую задержку и снижает доступность при проблемах между регионами. Полностью линейризуемая согласованность решает проблему порядка, но может требовать координации каждого чтения и записи.
Каузальная согласованность возникла как компромисс: сохранить порядок действительно связанных операций, не вводя единый глобальный порядок для независимых событий. Это позволяет использовать асинхронную репликацию, сохраняя более сильные гарантии, чем у обычной eventual consistency.
Пусть один сервис создаёт документ, а затем другой сервис публикует ссылку на него. Если запись документа и публикация ссылки реплицируются с разной задержкой, клиент может сначала увидеть ссылку, а при переходе по ней получить сообщение, что документа ещё нет.
Проблема не в простом устаревании отдельной реплики. Нарушен причинный порядок: наблюдатель увидел следствие до причины. Это может приводить к некорректным переходам состояний, ошибкам обработки событий и временно невозможным состояниям в интерфейсе или бизнес-логике.
Система должна фиксировать отношение «произошло до» между операциями. Когда сервис читает результат A и на его основе выполняет B, контекст операции A передаётся при создании B. Реплика, обслуживающая чтение B, обязана сначала применить все записи, от которых B зависит.
Для представления зависимостей применяют логические метки, векторные часы, версии с причинными интервалами или другой эквивалентный механизм. Упрощённо метаданные запроса содержат не только версию изменяемого объекта, но и минимальный набор версий, который должен быть виден перед обработкой операции.
Важно отличать каузальную согласованность от линеаризуемости. Независимые операции могут быть видны разным клиентам в разном порядке, если между ними нет причинной связи. Линеаризуемость, напротив, требует единого порядка, совместимого с реальным временем завершения операций, поэтому обычно требует более дорогой координации.
Гарантия зависит от сохранения контекста. Если клиент или сервис потерял причинные метаданные, система не может надёжно узнать, что новая операция зависит от предыдущей. Контекст нужно передавать через цепочку вызовов, сообщения, фоновые задания и повторные попытки.
Цена подхода — дополнительное место для метаданных, задержка ожидания отстающей реплики и усложнение обработки конфликтов. При сетевом разделении реплика может не иметь всех требуемых предшественников: тогда она должна задержать чтение, направить его на другую реплику или вернуть временную ошибку, вместо того чтобы нарушить гарантию.
В системе совместного редактирования один сервис сохраняет комментарий, а другой создаёт уведомление со ссылкой на этот комментарий. При межрегиональной асинхронной репликации уведомление иногда становилось доступно раньше самого комментария.
Рассматривались три варианта. Линеаризуемая запись устраняла нарушение порядка, но требовала синхронного согласования между регионами и увеличивала задержку. Искусственная задержка уведомления была простой, но не давала гарантии: задержка репликации могла оказаться больше выбранного интервала. Передача причинного контекста сохраняла асинхронность и связывала ожидание именно с необходимой зависимостью.
Выбрали третий вариант: сервис уведомлений получал контекст версии комментария, а чтение уведомления разрешалось только реплике, уже применившей эту версию. Если реплика отставала, запрос временно направлялся в другой регион или откладывался. В результате независимые данные продолжили реплицироваться асинхронно, а ссылки на ещё невидимые комментарии перестали появляться.
Нет. Она упорядочивает только операции, связанные отношением причинности. Два независимых события могут быть применены в разном порядке на разных репликах, если это не создаёт наблюдаемого нарушения зависимости. Единый порядок для всех событий требует более сильной гарантии, например глобальной сериализации или линеаризуемости.
Не всегда. Операция может зависеть от нескольких объектов или событий, прочитанных в разных сервисах. Одна версия не описывает весь причинный контекст, поэтому применяют составные метки, векторные часы или компактные структуры, сохраняющие транзитивные зависимости.
Она не должна обслуживать операцию так, будто зависимость уже выполнена. Возможные варианты — дождаться применения предшественника, перенаправить запрос на актуальную реплику или вернуть ошибку, которую клиент обработает повтором. Выбор зависит от требований к задержке и доступности, но выдача противоречивого состояния означает нарушение заявленной гарантии.