Дедлоки при встречн...
 
Уведомления
Очистить всё

Дедлоки при встречных переводах между кошельками

5 Сообщения
4 Пользователи
3 Реакции
462 Просмотры
GalinaKorneva
Сообщения: 30
Автор темы
Читает чужие PR бэкенд-разработчица
Присоединился: 4 года назад
[#23]

Внутренний баланс пользователей, переводы между кошельками идут джобами Sidekiq. Если одновременно проходят перевод от А к Б и встречный от Б к А, раз в несколько дней ловлю:

PG::TRDeadlockDetected: ERROR:  deadlock detected
DETAIL:  Process 48211 waits for ShareLock on transaction 9120554; blocked by process 48207.
Process 48207 waits for ShareLock on transaction 9120551; blocked by process 48211.

Код перевода:

Wallet.transaction do
  from = Wallet.lock.find(from_id)
  to = Wallet.lock.find(to_id)
  from.update!(balance: from.balance - amount)
  to.update!(balance: to.balance + amount)
end

Ретрай джобы спасает, деньги не теряются, но хочу уйти от этого совсем. Rails 7.0, PostgreSQL 15.


4 ответа
kostya.sokurov
Сообщения: 73
Держит прод бэкенд-разработчик, senior
Присоединился: 4 года назад

Классика: две транзакции берут одни и те же две строки в разном порядке. Первая держит А и ждёт Б, вторая держит Б и ждёт А. Лечится тем, что блокировки всегда берутся в одном порядке, например по id:

Wallet.transaction do
  wallets = Wallet.where(id: [from_id, to_id]).order(:id).lock.to_a
  from = wallets.find { |w| w.id == from_id }
  to = wallets.find { |w| w.id == to_id }
  from.update!(balance: from.balance - amount)
  to.update!(balance: to.balance + amount)
end

Получается SELECT ... ORDER BY id FOR UPDATE, строки блокируются по порядку, и встречный перевод просто подождёт первый.


Ответ
alisher1996
Сообщения: 6
Пишет в irb архитектор
Присоединился: 3 года назад

Я бы ещё менял баланс не чтением и записью из Ruby, а одним UPDATE: Wallet.where(id: from_id).update_all(["balance = balance - ?", amount]). Блокировка держится меньше, окно для гонки тоже. Но порядок всё равно нужен, тут без вариантов.


Ответ
GalinaKorneva
Сообщения: 30
Автор темы
Читает чужие PR бэкенд-разработчица
Присоединился: 4 года назад

Сделала сортировку по id и перенесла проверку баланса прямо в UPDATE, с условием balance >= amount: если обновилось ноль строк, значит денег не хватило, и транзакция откатывается. Две недели под нагрузкой, ни одного дедлока, раньше было по 3-4 в неделю. Ретраи оставила как страховку.


Ответ
Валерий40
Сообщения: 33
Читает чужие PR фулстек-разработчик
Присоединился: 4 года назад

Сохранил. На Laravel ловил ровно то же самое, только там ещё и ретраев не было


Ответ