Согласованность данных: где нужна строгая, а где достаточно «в итоге»

Статья архитектурно-аналитической серии на примере лунной базы: как выбирать уровень согласованности данных под реальные сценарии — строгая, eventual или пакетная — и почему «согласованность везде» ломает систему так же, как её отсутствие

«Данные должны быть согласованы» звучит как безусловно правильное требование. Но это одно из самых дорогих и самых недопонятых требований в проектировании. Строгая согласованность везде делает систему медленной и хрупкой к разрывам связи. Её отсутствие там, где она критична, приводит к решениям на основе противоречивых данных. Искусство — не в том, чтобы «сделать согласованно», а в том, чтобы выбрать правильный уровень для каждого участка.

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

Согласованность данных между Луной и Землёй: строгая, eventual, пакетная

В статье

Почему «согласованность везде» — это миф

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

Поэтому «согласованность везде» обычно означает одно из двух: либо система медленная и падает при каждом разрыве, либо согласованность только декларируется, а на деле держится на удаче. Честный путь — признать, что разным данным нужны разные гарантии.

Согласованность, свежесть, синхронизация — три разные оси

Здесь легко смешать три независимых вопроса — их полезно держать порознь (мы уже касались этого в статье про доменную модель):

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 не только допустимы, но и желательны: они делают систему устойчивой к разрывам.

flowchart LR A["Данные базы"] --> B["Жизнеобеспечение и энергия"] A --> C["Запасы и логистика"] A --> D["Прогноз и аналитика"] B --> E["Строгая, локальная
(не зависит от Земли)"] 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 по согласованности

Для каждого важного набора данных спросите:

  1. Какова цена того, что кто-то увидит устаревшее значение?
  2. Какой максимум устаревания допустим (0 секунд, 5 минут, 40 минут, сутки)?
  3. Что система делает, когда допустимый бюджет устаревания превышен?
  4. Должны ли эти данные оставаться доступными при разрыве связи?
  5. Кто «владеет истиной» по этим данным (источник правды)?
  6. Строгая, eventual или пакетная согласованность здесь уместна?
  7. Как данные сходятся после восстановления связи?
  8. Что защищает от дубликатов и нарушения порядка?
  9. Что показываем пользователю/оператору, если данные могут быть устаревшими (предупреждение, статус синхронизации, время последнего обновления)?

Если на всё отвечают «строгая везде» — это сигнал, что цену согласованности ещё не обсудили честно.

Что дальше

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

См. также

Обсуждение в Telegram

Присоединиться →

Комментарии