Гонка событий (Race Condition)
Гонка событий — ситуация, при которой результат работы системы зависит от порядка выполнения операций. Если несколько процессов одновременно работают с одним состоянием, итог может быть непредсказуемым.
Одна из самых распространённых проблем в современных системах. Может возникать в:
- микросервисах;
- распределённых системах;
- очередях сообщений;
- асинхронных API;
- frontend-приложениях;
- потоковой обработке данных.
Сложность гонки событий: проблема проявляется нестабильно. Система может работать корректно, а затем случайно выдать:
- двойное списание денег;
- отрицательный остаток;
- потерю обновления;
- дублирование операции;
- рассинхронизацию между сервисами.
Пример гонки событий
На складе остался один товар. Два пользователя одновременно нажимают кнопку «Купить». Оба запроса читают остаток как 1. Оба успешно проходят проверку и уменьш ают остаток.
В результате:
- продано два товара вместо одного;
- остаток становится отрицательным;
- система теряет консистентность.
Data Race vs Race Condition
- Data Race — низкоуровневая проблема многопоточного доступа к памяти.
- Race Condition — логическая проблема порядка выполнения операций.
В enterprise-системах аналитики и архитекторы в основном работают с Race Condition.
Когда возникает
несколько процессов работают с одним состоянием (например, одновременно читают и изменяют);
- хотя бы один процесс изменяет данные;
- порядок выполнения не контролируется;
- нарушение порядка доставки (события отправляются в одном порядке, а доставляются — в другом.);
- итог зависит от случайных факторов;
- отсутствие координации между сервисами (каждый сервис видит только локальное состояние.)
- deadlock (несколько транзакций блокируют друг друга)
Типичные признаки
- «плавающие» баги;
- редкие ошибки;
- невозможность стабильно воспроизвести проблему;
- случайные дубли;
- потеря части данных;
- периодическая рассинхронизация.
Race condition — не обязательно ошибка разработчика.
В монолитной синхронной системе вероятность гонки ниже. В распределённой архитектуре становится практически неизбежной.
Причины:
- сетевые задержки;
- ретраи;
- повторная доставка сообщений;
- независимая работа сервисов;
- параллельные консьюмеры;
- различная скорость обработки.
Ключевая проблема — порядок выполнения нельзя гарантировать.
Гонка всегда связана с одним принципом:
корректность системы зависит от последовательности событий.
Если изменение порядка приводит к разному результату — система подв ержена гонке.
В архитектуре
Backend и микросервисы
Самый частый источник гонок — параллельные запросы к одному ресурсу.
Например:
- несколько сервисов обновляют один заказ;
- несколько обработчиков меняют баланс;
- несколько консьюмеров читают одну очередь.
Особенно опасны:
- shared state;
- общие таблицы;
- асинхронные обработчики;
- фоновые задачи.
Базы данных
При использовании транзакций гонка не исчезает.
Типовые проблемы:
- Lost Update (ситуация, когда изменения одного процесса перезапи сываются изменениями другого, и часть данных теряется);
- dirty read (чтение данных, которые были изменены другой транзакцией, но ещё не зафиксированы (могут быть отменены);
- non-repeatable read (повторное чтение одной и той же записи внутри транзакции возвращает разные значения, потому что другая транзакция изменила данные);
- перезапись изменений;
- конфликт версий.
Классическая ошибка:
SELECT balance FROM accounts;
UPDATE accounts SET balance = ?;