Списания в Redis теряются при восьми воркерах
Баланс бонусов хранится в Redis, списание сделано так:
$balance = (int) Redis::get("balance:{$userId}");
$balance -= $amount;
Redis::set("balance:{$userId}", $balance);
Очередь обрабатывают восемь воркеров параллельно. При активных пользователях часть списаний исчезает: баланс выше, чем должен быть по сумме проведённых операций.
В чём причина?
- Redis однопоточный, поэтому гонок здесь быть не может: списания теряются из-за отсутствия персистентности и сброса данных на диск с задержкой
- Причина в приведении к int: значение приходит из Redis строкой, и при пустом ключе часть списаний обнуляется приведением к нулю
- Чтение, изменение и запись — три отдельные операции: между get и set другой воркер записывает своё значение, и оно затирается. Нужна атомарная операция или блокировка по ключу
- Достаточно уменьшить число воркеров до двух: вероятность одновременного обращения к одному ключу станет пренебрежимо малой
