Транзакции и согласованность
На складе одна последняя коробка. Два человека нажали «купить» с разницей в тридцать миллисекунд. Оба запроса прочитали остаток - единица, оба решили, что товар есть, оба записали ноль. Продано две коробки, в наличии одна. Дальше кто-то из двоих получает письмо с извинениями, а поддержка - обращение.
Стержень: транзакция делает изменения неделимыми внутри одной базы. Между сервисами такой гарантии нет, там выбирают между строгостью и доступностью осознанно.
// Формулировки: «где нужна строгая согласованность?», «два оператора правят одну заявку», «заказ в одном сервисе, резерв в другом»
Что обязано случиться вместе
Списали деньги, создали заказ. Между этими двумя записями упал сервер. Без транзакции остаётся состояние «деньги ушли, заказа нет», и разбирать его будет человек в поддержке, по одному обращению за раз.
Транзакция даёт неделимость: применилось всё либо не применилось ничего, промежуточного состояния снаружи не видно. Работает эта гарантия внутри одной базы.
// Для аналитика отсюда следует конкретный вопрос к бизнесу: перечисли изменения, которые обязаны произойти вместе. Ответ определяет и границы транзакции, и то, можно ли вообще разносить эти операции по разным сервисам - потому что общей транзакции между ними не будет.
Где отставание стоит денег, а где нет
Отложенная согласованность означает, что копии догоняют источник не мгновенно. Реплика базы отстаёт на десятки или сотни миллисекунд, поисковый индекс - на секунды, витрина отчётов - на часы. Это нормальный режим работы, а не поломка.
Считаем цену отставания. Счётчик просмотров отстал на минуту - никто не заметил. Остаток товара отстал на секунду - продали то, чего нет, и платишь возвратом, доставкой и репутацией. Разница не в технологии, а в том, принимается ли по этим данным решение о деньгах.
// Жалоба, которая приходит первой: «поменял телефон в профиле, обновил страницу - старый». Запись ушла в основной узел, а чтение балансировщик отправил в отставшую реплику. Лечится чтением своих записей с основного узла в течение короткого окна после изменения.
Два оператора и одна заявка
Аня открыла заявку в 10:00 и ушла на созвон. Борис открыл её же в 10:05, поправил сумму, сохранил в 10:07. Аня вернулась, дописала комментарий и сохранила в 10:12 - той формой, которую загрузила в 10:00. Сумма Бориса стёрта, оба уверены, что всё сохранилось. Это потерянное обновление.
Три рабочих правила. Блокировка на время редактирования: просто, но заявка виснет, если человек ушёл на обед. Проверка версии при сохранении: форма помнит номер версии, полученный при открытии, и если в базе версия уже другая - сохранение отклоняют и показывают конфликт. Слияние по полям: когда правки затронули разные поля и обе можно применить.
// Выбор здесь не технический. Решает бизнес, что дороже - заблокированная на час заявка или тихо потерянная правка. Аналитик обязан этот вопрос задать: иначе правило выберет разработчик молча, и это будет «побеждает сохранивший последним».
- потерянное обновление
- две параллельные правки, при которых вторая запись затирает первую без предупреждения
Между сервисами - сага
Единой транзакции через границу сервисов нет. Двухфазная фиксация формально существует, но держит блокировки на всех участниках и подвисает целиком, если координатор упал в середине. В живых системах её обходят стороной.
Вместо неё берут сагу: цепочку шагов, у каждого своя компенсация. Создали заказ, зарезервировали товар, списали деньги. Списание не прошло - снимаем резерв, отменяем заказ. Компенсация не откат, а обратное действие, которое видно пользователю и попадает в историю.
// Работа аналитика здесь не в схеме, а в двух списках: компенсация для каждого шага и статус, который человек видит между шагами. Без второго списка пользователь смотрит на «заказ создан» три минуты и звонит в поддержку.
- сага
- цепочка локальных транзакций в разных сервисах, где у каждого шага описано компенсирующее действие
Как отвечать: «Заказ создаётся в одном сервисе, резерв товара в другом. Как согласовать?»
Сагой с компенсациями, а не распределённой транзакцией: двухфазная фиксация через границы сервисов держит блокировки на всех участниках и виснет, если координатор умер посередине. Расписываю последовательность: создали заказ, попытались зарезервировать товар, не получилось - отменяем заказ либо переводим его в ожидание поставки. Главная часть моей работы тут не схема, а компенсация для каждого шага и промежуточные статусы, которые увидит пользователь. Без них система застревает между шагами: заказ висит в неопределённом состоянии, пока кто-то не разберёт руками. И отдельно проговариваю случай, когда компенсация невозможна - деньги уже ушли поставщику, тогда нужен ручной сценарий с ответственным.
Кандидат отвергает распределённую транзакцию с обоснованием, переводит разговор в свою зону и вспоминает про необратимые шаги.
На чём валятся
- −− Пишут «данные всегда актуальны во всех системах» - требование невыполнимо физически.
- −− Не задают правило разрешения конкурентной правки, и правки исчезают молча.
- −− Тянут распределённую транзакцию между сервисами вместо саги.
- −− Забывают, что чтение из реплики вернёт состояние до только что сделанной записи.
- −− Описывают шаги саги без компенсаций и без промежуточных статусов.
- −− Не предусматривают шаг, который компенсировать нельзя.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.
- Где отложенная согласованность обычно неприемлема?A)В проверке остатка перед списаниемB)В счётчике просмотров страницыC)В ленте новостей приложенияD)В списке рекомендованных товаров
показать ответ и разбор
+A)В проверке остатка перед списанием// разбор: Там, где решение принимается по данным и ошибка стоит денег, нужна строгая согласованность: иначе два параллельных списания пройдут по одному остатку и уведут баланс в минус. Витрины, счётчики и ленты спокойно переживают отставание в секунды, и это стандартный размен.
- Два оператора одновременно редактируют одну заявку. Что должен описать аналитик?A)Размер формы редактированияB)Порядок полей на экранеC)Поведение при конфликте измененийD)Частоту автосохранения черновика
показать ответ и разбор
+C)Поведение при конфликте изменений// разбор: Без явного правила побеждает тот, кто сохранил последним, и правки первого исчезают молча. Варианты решения известны: блокировка на время редактирования, проверка версии записи с сообщением о конфликте, слияние по полям. Выбор зависит от того, насколько часто такие пересечения случаются.
- Что означает оптимистичная блокировка?A)Запись блокируется на всё время правкиB)Конфликт обнаруживается при сохранении по версииC)Изменения применяются без всяких проверокD)Правки разных пользователей сливаются автоматически
показать ответ и разбор
+B)Конфликт обнаруживается при сохранении по версии// разбор: Оптимистичный подход исходит из того, что пересечения редки: запись не блокируют, но при сохранении сверяют версию, которую клиент получил при открытии. Если она устарела, сохранение отклоняется и пользователю показывают конфликт. Так не страдают те, кто открыл форму и ушёл на обед.
- Заказ создаётся в одном сервисе, резерв товара — в другом. Как обеспечить согласованность?A)Общей транзакцией через две базыB)Сверкой раз в сутки по расписаниюC)Блокировкой обоих сервисов на время операцииD)Последовательностью шагов с компенсациями
показать ответ и разбор
+D)Последовательностью шагов с компенсациями// разбор: Единая транзакция через границы сервисов дорога и хрупка, поэтому применяют сагу: создали заказ, зарезервировали товар, при неудаче второго шага отменяем первый. Бизнес обязан описать компенсацию для каждого шага и промежуточные статусы, которые увидит пользователь.
- Почему требование «данные всегда актуальны во всех системах» практически невыполнимо?A)Так запрещают стандарты интеграцииB)Базы не умеют реплицировать данныеC)Передача не мгновенна, узлы отказываютD)Системы используют разные форматы данных
показать ответ и разбор
+C)Передача не мгновенна, узлы отказывают// разбор: Между системами всегда есть задержка, а при отказе сети приходится выбирать: отказать в обслуживании либо продолжить работу с расхождением. Поэтому требование формулируют иначе — задают допустимое отставание и поведение при его превышении: показать предупреждение, запретить операцию, поставить в очередь.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.