Доставка и воркер¶
At least once¶
Задание может выполниться больше одного раза. Обычная причина — воркер пропал посреди запуска: другой воркер видит, что его heartbeat истёк, возвращает задание в очередь и тратит попытку. Делайте задачи идемпотентными, как с acks_late в Celery, и закладывайте max_attempts и на падения. Задаче, которую нельзя выполнить дважды (списание, SMS), нужен ключ идемпотентности в ваших таблицах, а если второй запуск хуже, чем никакого, — max_attempts=1.
Аренда¶
sequenceDiagram
participant W as Воркер A
participant DB as Postgres
participant R as Воркер B
W->>DB: claim (FOR UPDATE SKIP LOCKED), state = running, run_count + 1
loop каждые heartbeat_interval (5 с)
W->>DB: heartbeat_at = now()
end
W--xDB: падение / потеря сети
R->>DB: rescue: heartbeat старше heartbeat_ttl (30 с)
R->>DB: задание обратно в queued, попытка потрачена
W-->>DB: запоздалая запись исхода (устаревший run_count) отклоняется
Каждый claim увеличивает run_count строки. Воркер записывает исход, только пока в строке тот run_count, с которым он её забрал, поэтому вернувшийся воркер, чьё задание уже подобрали, не перезапишет новый запуск.
Цикл воркера¶
На каждом круге воркер отправляет heartbeat, подбирает задания мёртвых воркеров, чистит старые строки (примерно раз в минуту), ставит наступившие запуски расписаний и забирает задания на свободные слоты в каждой очереди.
Если claim занял все свободные слоты, следующий claim идёт сразу. Когда заняты все слоты очереди, воркер делает claim, как только завершается одно из этих заданий, не дожидаясь опроса. Иначе воркер ждёт до poll_interval (1 секунда), не меньше половины, с джиттером, чтобы простаивающие воркеры не опрашивали базу синхронно.
Параметр Worker(...) |
По умолчанию |
|---|---|
queues |
["default"] |
slots |
1 (на очередь) |
heartbeat_interval |
5 с |
heartbeat_ttl |
30 с |
poll_interval |
1 с |
shutdown_timeout |
10 с |
fair |
False |
Остановка¶
По Ctrl-C или SIGTERM воркер перестаёт брать задания и ждёт выполняющиеся до shutdown_timeout. Всё, что не успело, возвращается в очередь без траты попытки. Поток синхронной задачи не останавливается.
Ошибки базы¶
Note
Раздел описывает 0.6.0. В 0.5.0 ошибка базы останавливает воркер.
Транзиентная ошибка (потеря соединения, рестарт или failover сервера, слишком много соединений, deadlock, ошибка сериализации) пишется в лог и повторяется с backoff. Воркер продолжает работу и не делает claim, пока heartbeat не проходит. Другие ошибки базы, например непримененная схема, его останавливают.
Если heartbeat не проходит дольше heartbeat_ttl, воркер останавливается и возвращает незавершённые задания в очередь без траты попытки. Если база всё ещё недоступна, их подберёт другой воркер, когда истечёт heartbeat; это тратит попытку, как любой потерянный запуск. akadze worker тогда завершается с кодом 1.
Если запись исхода запуска упала с транзиентной ошибкой, воркер повторяет запись того же исхода, пока владеет заданием; само задание заново не выполняется. Исход, который сохранить нельзя вообще (например, символ NUL в результате), завершает задание в failed без новых попыток.