🚨 Как перевод денег уронил нам прод
⏰ 19:10
Задеплоили долгожданное - переводы между кошельками юзеров. Списываем с одного, зачисляем на другой, всё в одной транзакции, чтобы деньги не потерялись
На стейдже гоняли, всё зелёное
⏰ 19:40
Посыпались ошибки, часть переводов падает
В логах - "deadlock detected"
Не таймаут, не отвал базы, а взаимная блокировка
Вылезает только когда переводов много и идут они параллельно
⏰ 19:55
Картина складывается
Перевод от Ани к Боре берёт блокировку на строку Ани, потом тянется за строкой Бори
А в ту же секунду перевод от Бори к Ане блокирует строку Бори и тянется за строкой Ани
Оба ждут друг друга, никто не отпустит
База ловит это сама: видит цикл ожидания, убивает одну из транзакций с ошибкой дедлока
Юзер получает "перевод не прошёл", хотя по сути ничего не сломано
⏰ 20:20
Причина не в переводах, а в порядке
Мы блокировали строки как "сначала отправитель, потом получатель"
Направление у переводов разное, вот встречные пары и встают лицом к лицу
Лочим строки в одном и том же порядке, независимо от того, кто кому платит
Взяли сортировку по id: сначала строка с меньшим id, потом с большим
Теперь две встречные транзакции идут за строками одинаково, и вместо взаимной блокировки одна ждёт другую
🧠 Что забрали с собой
- Дедлок - это про порядок захвата ресурсов, а не про их количество
Двум транзакциям хватает взять два одинаковых замка в разной последовательности
- На стейдже такое не ловится, там нет параллельной нагрузки
Дедлок живёт только там, где реально пересекаются одновременные операции
- Трогаешь в транзакции несколько строк - бери их в детерминированном порядке
По id, по ключу, как угодно, лишь бы одинаково у всех
- И держи в голове: база всё равно иногда будет кидать дедлоки, это норма под нагрузкой
Ретрай на такую ошибку - не костыль, а штатный сценарий
Одна строчка сортировки, а стоила вечера и пачки упавших переводов
А вы ловили дедлоки? На чём - переводы, счётчики, обновление связанных таблиц? 🤔
#backend #database #postgres #concurrency #dev #incident
Post #1540
58