«Данные должны быть согласованы» звучит как безусловно правильное требование. Но это одно из самых дорогих и самых недопонятых требований в проектировании. Строгая согласованность везде делает систему медленной и хрупкой к разрывам связи. Её отсутствие там, где она критична, приводит к решениям на основе противоречивых данных. Искусство — не в том, чтобы «сделать согласованно», а в том, чтобы выбрать правильный уровень для каждого участка.
В прошлой статье мы разбирали, как части системы общаются асинхронно и с задержкой. Как только появляется асинхронность, всплывает вопрос: что значит «данные согласованы», если они физически расходятся во времени? Эта статья — про то, как отвечать на него осознанно.
В статье
- Почему «согласованность везде» — это миф
- Три уровня согласованности на практике
- Где это решает аналитик, а не база данных
- Лунная база: разные данные — разные правила
- Как держать согласованность без распределённых транзакций
- Короткий checklist по согласованности
- Что дальше
Почему «согласованность везде» — это миф
В распределённой системе при сетевом разделении (когда канал между независимыми узлами рвётся) нельзя одновременно сохранить и мгновенную согласованность, и доступность. Это не пессимизм, а фундаментальное ограничение: пока всё на связи, можно иметь и то и другое — но в момент разрыва (а в лунной базе он случается регулярно) приходится выбирать: либо отказать в операции ради согласованности, либо разрешить её ценой временного расхождения.
Поэтому «согласованность везде» обычно означает одно из двух: либо система медленная и падает при каждом разрыве, либо согласованность только декларируется, а на деле держится на удаче. Честный путь — признать, что разным данным нужны разные гарантии.
Согласованность, свежесть, синхронизация — три разные оси
Здесь легко смешать три независимых вопроса — их полезно держать порознь (мы уже касались этого в статье про доменную модель):
1. Гарантия согласованности — что участники видят относительно порядка и видимости изменений.
- Строгая (strong). Все участники видят согласованное состояние по правилам системы (видимость и порядок операций гарантированы). Дорого, требует доступности участников. Иногда это near-real-time, но не обязательно: strong — это про гарантию, а не про скорость. Нужна там, где расхождение опасно для жизни или денег.
- Eventual (в итоге). Значения сходятся через какое-то время. Дёшево и устойчиво к разрывам. Подходит там, где короткое расхождение допустимо. Между strong и eventual есть промежуточные модели (causal, read-your-writes и другие), но для рабочего разговора этих двух полюсов обычно достаточно.
2. Допустимая свежесть данных (staleness budget) — насколько старые данные ещё безопасны: 0 секунд, 5 минут, 40 минут, сутки. Это отдельная ось: eventual-система может отставать и на секунды, и на часы.
3. Способ синхронизации — как обновления доходят до участников: near-real-time поток, репликация, периодическая пакетная (batch) выгрузка. «Пакетно / с запаздыванием» — это про способ и частоту обновления, а не отдельный уровень согласованности: batch-репликация обычно даёт ту же eventual consistency, просто с бо́льшим окном отставания. Пакетные обновления нормальны для отчётов, прогнозов и аналитики.
Ошибка — не в выборе значения по одной оси, а в том, чтобы применить один и тот же ответ по всем трём осям сразу ко всем данным.
Часто аналитику понятнее не сами термины, а бюджет устаревания (staleness budget): какое отставание данных допустимо здесь? Практический вопрос звучит не «какая нужна консистентность?», а «насколько старые данные тут ещё безопасны?». Ответ в секундах и минутах подсказывает и гарантию, и способ синхронизации: 0 обычно ведёт к строгой, минуты — часто к eventual в near-real-time, часы и сутки — к eventual с пакетной синхронизацией (сама гарантия при этом остаётся eventual, меняется лишь окно и способ обновления).
Где это решает аналитик, а не база данных
Уровень согласованности часто считают технической деталью «на уровне БД». Но на самом деле это доменное решение, и аналитик участвует в нём первым. Вопрос «допустимо ли, чтобы наземный центр 40 минут видел старое значение запаса кислорода?» — не про базу, а про риск и смысл. Ответ на него и определяет, какая нужна согласованность.
Аналитик приносит сюда то, чего не видно из технологии: цену расхождения. Для одних данных 40 минут задержки — пустяк, для других — катастрофа. Это различие нельзя вывести из схемы БД, его нужно принести из домена.
Лунная база: разные данные — разные правила
Применим это к сквозному сценарию серии: связь с Землёй пропала на 40 минут, а энергобюджет просел. Разные данные ведут себя совершенно по-разному:
| Данные | Уровень | Почему | Что при разрыве связи |
|---|---|---|---|
| Уровень кислорода, давление | строгая, локально | расхождение опасно для жизни | решается на базе, Земля не нужна |
| Энергобюджет и приоритеты нагрузок | строгая, локально | решение нужно сейчас и точное | пересчитывается локально |
| Запасы расходников | eventual | короткое расхождение допустимо | сходится после восстановления связи |
| Прогноз поставок, аналитика | eventual, пакетно | заведомо отстаёт, это нормально | просто обновится позже |
У каждого набора данных полезно сразу назвать источник правды (кто владеет истиной): для кислорода и энергии — локальный контур базы; для запасов — локальный складской контур с последующей синхронизацией; для прогноза — наземная аналитика. Источник правды и допустимое отставание вместе и определяют уровень согласованности.
Ключевой вывод: критичные для выживания данные не должны зависеть от связи с Землёй — их согласованность обеспечивается локально. А логистике и аналитике eventual/batch не только допустимы, но и желательны: они делают систему устойчивой к разрывам.
(не зависит от Земли)"] C --> F["Eventual
(сходится после связи)"] D --> G["Eventual, пакетно
(обновляется позже)"]
flowchart LR
A["Данные базы"] --> B["Жизнеобеспечение и энергия"]
A --> C["Запасы и логистика"]
A --> D["Прогноз и аналитика"]
B --> E["Строгая, локальная
(не зависит от Земли)"]
C --> F["Eventual
(сходится после связи)"]
D --> G["Eventual, пакетно
(обновляется позже)"]
Как держать согласованность без распределённых транзакций
Соблазн — обеспечить согласованность «двухфазным коммитом» через все контуры. В системе с ненадёжной связью это худший выбор: транзакция, которая ждёт всех участников, падает при первом же разрыве. На практике используют другие подходы (на уровне идеи, без погружения в реализацию):
- Локальная согласованность + согласование позже. Критичный контур принимает решение на своих данных немедленно, а синхронизация с остальными происходит асинхронно, когда канал доступен.
- Outbox / надёжные события. Изменение и запись факта о нём фиксируются одной локальной транзакцией — в этом и есть гарантия outbox: событие не потеряется относительно изменения, без распределённой транзакции. Но саму доставку outbox не обеспечивает: наружу события выносит отдельный relay/publisher, который читает outbox и публикует в брокер с ретраями и подтверждением (at-least-once), а потребитель должен быть идемпотентным, чтобы повтор не навредил.
- Компенсация вместо отката. Если шаг распределённого процесса не удался, выполняется компенсирующее действие, а не глобальный rollback (это идея саги — об этом подробнее в отдельной статье про Saga).
- Идемпотентность и версии. Повтор и приход данных не по порядку не ломают состояние, потому что операции можно безопасно применять повторно.
Главное правило: согласованность достигается не «общей транзакцией на всё», а явным выбором, что синхронизируется немедленно, что — позже, и как система ведёт себя в промежутке.
Конкретный конфликт: устаревшая команда Земли
Абстракции выше оживают на одном сценарии. В момент t0 наземный центр шлёт команду «перевести аварийный резерв кислорода модуля B в общий контур» — по энергобалансу, который был актуален на Земле (снимок версии v41), с пометкой valid-until = t0 + 30 мин. Через 5 минут связь рвётся. Локальный контур видит просадку энергии и принимает своё решение — изолировать модуль B и держать резерв; версия локального состояния растёт до v42. Ещё через 35 минут связь возвращается, и буферизованная команда Земли приходит на исполнение.
| Момент | Событие | Состояние |
|---|---|---|
| t0 | Земля шлёт команду (valid-until t0+30, ждёт v41) |
энергобаланс v41 |
| t0+5 | связь рвётся; база решает держать резерв B | локально v42 |
| t0+40 | связь вернулась; команда Земли готова примениться | локально v42, команда ждёт v41 |
Применить команду вслепую — значит слить резерв, который база осознанно решила держать: прямой вред. Три механизма из списка выше не дают этому случиться:
- TTL /
valid-untilотбрасывает саму устаревшую команду: срок (t0+30) уже прошёл к t0+40 — команда не исполняется, а логируется как просроченная. - Версия-предусловие ловит разошедшееся состояние: команда ждала
v41, а локально ужеv42→ предусловие не выполнено, слепое применение заблокировано. - Reconciliation превращает конфликт в явное решение, а не тихое перезатирание: расхождение «Земля хотела X, база сделала Y» выносится оператору или наземному центру, которые разрешают его осознанно, опираясь на журнал решений.
Ни один из трёх по отдельности не полон: без TTL протухшая команда может пройти по версии после нового снимка; без версии свежая по времени, но конфликтующая команда всё равно навредит; без reconciliation система молча выберет одну сторону и потеряет факт конфликта. Вместе они и есть «согласованность без распределённых транзакций» на практике.
Короткий checklist по согласованности
Для каждого важного набора данных спросите:
- Какова цена того, что кто-то увидит устаревшее значение?
- Какой максимум устаревания допустим (0 секунд, 5 минут, 40 минут, сутки)?
- Что система делает, когда допустимый бюджет устаревания превышен?
- Должны ли эти данные оставаться доступными при разрыве связи?
- Кто «владеет истиной» по этим данным (источник правды)?
- Строгая, eventual или пакетная согласованность здесь уместна?
- Как данные сходятся после восстановления связи?
- Что защищает от дубликатов и нарушения порядка?
- Что показываем пользователю/оператору, если данные могут быть устаревшими (предупреждение, статус синхронизации, время последнего обновления)?
Если на всё отвечают «строгая везде» — это сигнал, что цену согласованности ещё не обсудили честно.
Что дальше
Мы выбрали уровни согласованности и взаимодействий. Но любая, даже идеально спроектированная, система живёт ровно настолько, насколько её можно эксплуатировать. Следующая статья серии — эксплуатация в архитектуре: как инженеры возвращают схему в реальность — про наблюдаемость, деградацию и то, кто будет разбираться в инциденте в три часа ночи.
См. также
- Взаимодействия и интеграции: где ломается кажущаяся простота
- Как доменная модель ломает и спасает архитектуру
- Требования как архитектурные драйверы
- Внешнее: Martin Kleppmann — Designing Data-Intensive Applications (репликация, согласованность, конфликты и их разрешение), Gilbert & Lynch — доказательство CAP-теоремы (PDF).
Комментарии