Гонки данных в Django и как их предотвращать
Вопрос
Какие гонки данных возникают в Django-приложении и какими средствами вы с ними боретесь?
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Lost update: прочитал, изменил, записал
Атомарные обновления через F-выражения и условные update
`select_for_update` в `transaction.atomic`, порядок блокировок, `nowait` и `skip_locked`
Ограничения в БД: unique, check, как последняя линия защиты
Идемпотентность и уникальные ключи для повторных запросов
Побочные эффекты через `on_commit`
Что могут спросить следом
- Чем
select_for_update(skip_locked=True)полезен для очередей? - Как избежать дедлока при блокировке нескольких строк?
Разбор
Образцовый ответ: «Самая частая гонка это потерянное обновление: два запроса читают объект, меняют поле и сохраняют, и одно изменение пропадает. Для счётчиков и балансов использую атомарные обновления на стороне БД: update(balance=F("balance") - x) с условием balance__gte=x и проверкой, что обновилась одна строка. Когда нужна сложная логика с чтением, беру transaction.atomic и select_for_update, тогда параллельный запрос ждёт блокировку. При блокировке нескольких строк фиксирую порядок, например по id, чтобы не было дедлоков, а для обработки очереди задач в таблице использую skip_locked=True, чтобы воркеры не ждали друг друга. Последняя линия защиты это ограничения в самой БД: уникальные индексы, CheckConstraint, тогда даже при ошибке в коде данные не станут неконсистентными. От повторных запросов клиента спасают ключи идемпотентности. И все побочные эффекты, письма и задачи, отправляю через transaction.on_commit».
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.
Ещё по теме «Django ORM и N+1»
- Задвоение сумм при нескольких annotate
- Посчитать SQL-запросы при ленивой загрузке
- Как prefetch_related склеивает данные
- exists() и count() вместо загрузки строк
- Атомарное увеличение счётчика
- Prefetch с вложенным select_related
- transaction.atomic
- Когда выполняется QuerySet