Задания Sidekiq потребляются из очереди FIFO. Без каких-либо дополнительных модификаций ситуация довольно ясна: наличие 4 однопоточных процессов каждый позволяет обрабатывать 4 задания одновременно.

Все меняется, когда вы решаете добавить уникальные задания Sidekiq в свой стек. В случае, когда для данного уникального ключа последовательно выполняется несколько заданий, Sidekiq серьезно забивается. В худшем случае не имеет значения, сколько рабочих и потоков у вас в инфраструктуре Sidekiq. Вы получите производительность одного потока Sidekiq. Это потому, что вы не можете заставить Sidekiq пропускать определенные задачи из-за обработки очереди FIFO. Чтобы обойти это ограничение, авторы Sidekiq Unique Jobs ввели #sleep, который будет работать до тех пор, пока ресурс снова не освободится для обработки или пока не истечет время ожидания. Этот подход означает, что если у вас в очереди больше задач, чем процессоров, им придется ждать, пока не будут обработаны все задания с данным уникальным ключом.

Все рабочие будут активно ждать (это означает, что в консоли Sidekiq они будут отмечены как занятые) до тех пор, пока блокировка не будет снята.

Решение: перенести расписание вместо ожидания

Предупреждение: если подобный случай встречается в вашей бизнес-логике довольно часто, возможно, вам лучше выбрать движок, отличный от Sidekiq. Я бы порекомендовал это решение для нечастых краевых / угловых случаев.

Обойти такое поведение довольно просто: если есть блокировка, поместите текущее задание в конец очереди. При этом ваши задания будут проверяться на возможность выполнения и переноситься обратно вместо ожидания. Это немного испортит ваши счетчики очереди (поскольку у вас будет больше заданий, помещенных в очередь и обработанных, чем должно), но с другой стороны, это означает, что Sidekiq будет «активно» искать ресурсы, с которыми он может работать в определенный момент.

Для этого мы можем создать новую стратегию «Уникальная вакансия», которую позже сможем применить. За исключением изменения расписания, наша стратегия не будет отличаться от WhileExecuting, поэтому мы можем использовать ее как основу.

Применять эту стратегию очень просто. Нам просто нужно заменить стратегию while_executing на while_executing_reschedule:

Изначально опубликовано в Running with Ruby.