Как обеспечить отказоустойчивость воркера, обрабатывающего задачи?
Что проверяют
Понимаете ли вы, что воркер обязательно будет падать, и знаете ли, как сделать так, чтобы задача при этом не потерялась и не выполнилась дважды с последствиями. Следом обычно спрашивают про идемпотентность и про задачу, которая падает всегда.
Как отвечать
Сильный ответ строится вокруг доставки «хотя бы один раз» и того, как с ней жить. На примере Celery:
- подтверждение после обработки: по умолчанию Celery подтверждает задачу перед выполнением.
task_acks_late=Trueвместе сtask_reject_on_worker_lost=Trueвернут её в очередь, если процесс умер посреди работы; - prefetch:
worker_prefetch_multiplier=1для долгих задач, чтобы упавший воркер не держал пачку задач, которые могли бы взять другие; - идемпотентность: раз задача может прийти повторно, обработчик должен это переживать. Ключ идемпотентности, уникальный индекс,
INSERT ... ON CONFLICT DO NOTHING, проверка статуса перед побочным эффектом; - ретраи:
autoretry_forтолько для временных ошибок (сеть, 503, таймаут),retry_backoff=Trueс джиттером и лимитmax_retries. Ошибки валидации повторять бессмысленно; - dead letter: после исчерпания попыток задача уходит в отдельную очередь, в RabbitMQ через
x-dead-letter-exchange, и на её размер ставится алерт; - таймауты и память:
soft_time_limitиtime_limitпротив зависаний,worker_max_tasks_per_childпротив утечек; - корректная остановка: на
SIGTERMворкер перестаёт брать новые задачи и доделывает текущие. В Kubernetes на это отведеноterminationGracePeriodSeconds, по умолчанию 30 секунд.
Если задача создаётся вместе с записью в базе, упомяните transactional outbox, иначе можно закоммитить заказ и потерять задачу на его обработку. Мониторинг: длина очереди, возраст самой старой задачи, доля ошибок, размер DLQ.
Где спотыкаются
Включают acks_late и забывают про идемпотентность. Письмо уходит дважды, платёж проводится повторно.
Ретраят любые исключения без лимита. «Отравленная» задача крутится бесконечно и занимает воркеры.
Проверяют только, что процесс жив. Воркер, зависший на сетевом вызове без таймаута, выглядит здоровым, а очередь растёт.