Гонка событий — ситуация, при которой результат работы системы зависит от порядка выполнения операций
Если несколько процессов одновременно работают с одним состоянием, итог может быть непредсказуемым
Может возникать в:
🟢микросервисах
🟢распределённых системах
🟢очередях сообщений
🟢асинхронных API
🟢frontend-приложениях
🟢потоковой обработке данных
🍃Сложность гонки событий: проблема проявляется нестабильно
Система может работать корректно, а затем случайно выдать ошибку
Когда возникает
⚪️несколько процессов работают с одним состоянием ( одновременно читают и изменяют)
⚪️хотя бы один процесс изменяет данные
⚪️порядок выполнения не контролируется
⚪️нарушение порядка доставки (события отправляются в одном порядке, а доставляются — в другом)
⚪️отсутствие координации между сервисами (каждый сервис видит только локальное состояние)
⚪️deadlock (несколько транзакций блокируют друг друга)
Типичные признаки
➖«плавающие» баги
➖редкие ошибки
➖невозможность стабильно воспроизвести проблему
➖случайные дубли
➖потеря части данных
➖периодическая рассинхронизация
Причины
🍃сетевые задержки
🍃ретраи
🍃повторная доставка сообщений
🍃независимая работа сервисов
🍃параллельные консьюмеры
🍃различная скорость обработки
Гонка всегда связана с принципом:
❕корректность системы зависит от последовательности событий
Если изменение порядка приводит к разному результату — система подвержена гонке
Backend и микросервисы
Частый источник гонок — параллельные запросы к одному ресурсу
Например:
✨несколько сервисов обновляют один заказ
✨несколько обработчиков меняют баланс
✨несколько консьюмеров читают одну очередь
Базы данных
При использовании транзакций гонка не исчезает
Проблемы:
➖Lost Update (когда изменения одного процесса перезаписываются изменениями другого, и часть данных теряется)
➖dirty read (чтение данных, которые были изменены другой транзакцией, но ещё не зафиксированы )
➖ non-repeatable read (повторное чтение одной и той же записи внутри транзакции возвращает разные значения, потому что другая транзакция изменила данные)
➖перезапись изменений
Брокеры сообщений
Не гарантируют отсутствие гонок
Создают условия, при которых она возникает
Проблемы:
⚪️задвоенные события 🤩 дважды изменится состояние
⚪️доставка не по очереди 🤩 итоговое состояние зависит от порядка
⚪️повторная доставка🤩 перезапишется состояние
⚪️параллельные консьюмеры 🤩 если работают с одим ресурсом, то есть риски гонки
Распределенные системы
В распределённых системах гонка — нормальное состояние среды
Причины:
🟢eventual consistency (изменения не применяются на всех серверах мгновенно)
🟢независимые сервисы
🟢сетевые лаги
🟢разные каналы доставки
🟢репликация
Примеры
Микросервисы с общей БД
➖сервис заказов и сервис оплаты работают с одной таблицей
➖сервис оплаты меняет статус заказа
➖сервис заказов одновременно обновляет адрес доставки
Оба сервиса:
1. читают одну запись
2. меняют локальную копию
3. сохраняют объект полностью.
Последний
UPDATE уничтожает изменения другого сервисаКак решать
✨обновлять только нужные поля
✨использовать оптимистическую блокировку
✨отказаться от общей БД
Event-driven архитектура
Сервис публикует:
➖
OrderCreated➖
OrderCancelledПотребитель получает их в обратном порядке
Клиент сначала получает письмо: «Заказ отменён»
А затем: «Заказ успешно создан»
Как решать
✨партицирование по
orderId✨версионирование событий
✨occurredAt timestamp
📎 Материалы
1. Небезопасная многопоточность или Race Condition
2. Что такое состояние гонки
3. Почему стоит проверять приложения на устойчивость к race condition
4. Разница между Data Race и Race Condition
📚 Книги
1. Высоконагруженные приложения - Мартин Клеппман
2. Микросервисы. Паттерны разработки и рефакторинга - Крис Ричардсон
3. Шаблоны корпоративных приложений - Мартин Фаулер
4. Release it! Проектирование и дизайн ПО для тех, кому не всё равно - Майкл Нейгард
#проектирование
➿➿➿➿➿➿➿➿
🧑🎓 Больше полезного в базе знаний по системному анализу