Автор: Михаил Ашкалунин, Developer/Python support
Материалы статьи подготовлены в рамках внутренней программы менторства и обучения специалистов команды Evergreen.
Сегодня поговорим о Celery — популярном инструменте для создания распределенных очередей задач. Такие очереди позволяют разгрузить работу в другой процесс и выполнять часть задач в фоновом режиме (если они не зависят от дальнейших действий пользователей), пока приложение выполняет другие задачи. Это ускоряет время отклика сайта или программы и улучшает user experience. Также поговорим о Flower — с его помощью мы отслеживаем воркеры Celery и прогресс по таскам.
Коротко о том, что собой являет Celery:
Celery зарекомендовал себя как прекрасный инструмент для:
Рассмотрим коротко, из чего состоит архитектура Celery.
В Celery задачи создают так называемые “продюсеры” (producers). Ими может выступать Celery beat (cron), который будет генерировать таски ежеминутно, ежечасно — как мы запрограммируем, либо же задачи будут генерироваться из приложения (не обязательно Django) по событию: когда пользователь что-то нажал на фронте, на бэкенде сгенерировалась задача.
Таски попадают в очередь в брокер (Redis или RabbitMQ). Мы используем Redis, так как его легче настроить, и он лучше справляется с простыми задачами. В брокере можно организовать несколько очередей и присвоить каждой приоритет, исходя из важности.
Consumers — собственно, воркеры (workers) — это исполнители задач, которые забирают и выполняют таски из очередей в брокере. Для каждого воркера очередь настроена индивидуально. Воркеры могут возвращать или не возвращать результаты. Если результаты выполнения задачи нам не нужны, можем их игнорировать. Если нужны, они будут храниться в бэкенде Celery в виде данных типа success и hash и информации о времени (длительности) выполнения задачи. Как бэкенд также может использоваться Redis.
Делимся нашими наблюдениями и выводами, сделанными в процессе работы с очередями задач в Celery.
База данных не совсем для этого предназначена и имеет свои ограничения: она не настолько быстрая и располагается, в отличие от Redis, не в оперативной памяти, а на жестком диске. Даже если это SSD, все равно требуется гораздо больше времени, чтобы ее использовать.
Чтобы второстепенные таски не забивали все воркеры, нам нужно оставлять отдельный воркер под высокоприоритетную очередь. Так критичные таски, которые нужно выполнить прямо сейчас, будут исполняться мгновенно, а не ждать исполнения низкоприоритетных задач, которыми забита очередь.
Во-первых, все это хранится в брокере в то время, пока задача находится в очереди (в Redis, который “висит” в оперативке) и забивает память. Во-вторых, если мы получили ORM-объект, к тому моменту, когда задача дойдет до выполнения, данные в этом объекте уже могут быть неактуальными. То есть, нужно сделать повторный запрос к БД, получить обновленные данные и их обрабатывать.
Рекомендуем указывать лимит для каждой задачи, в том числе и низкоприоритетных. Это нужно, чтобы предотвратить ситуацию, когда неооднозначные таски зависают на выполнении и стопорят работу всей системы.
Это полезно для модульности, сокращает количество кода (многомодульность), и задачи не висят подолгу на исполнении.
Если мы обрабатываем и храним результаты выполнения задач, по ним должно быть понятно, что происходит с тасками. Конечно, это не должен быть большой объект или JSON, но и не простой ответ true or false.
В момент запуска Redis, где хранятся результаты выполнения задач, эти данные могут быть утеряны. Если мы ведем логи, все сводится в определенный файл, и при необходимости можно проследить, что происходило с тасками не только по результатам, но и в процессе выполнения.
Это must have. Для доступа к мониторингу достаточно браузера — не нужно заходить в консоль, на сервер, при этом управление доступом в Flower гораздо проще и визуально понятнее.
При работе с Celery рекомендуем использовать Flower — небольшое веб-приложение, главными возможностями которого являются:
В настройках мы начали использовать autoscale: если не прописывать автоскейлинг или concurrency, то количество субпроцессов будет равно числу ядер. И это не очень хорошо, потому что в таком случае выполнение задач и обработка запросов веб-сервера будут “съедать” все ядра, и сайт или приложение будет подтормаживать.
Процесс обработки задач можем увидеть на вкладке Tasks. Тут есть сортировки по состояниям, времени выполнения, можно посмотреть сами таски, есть трейсбеки (traceback), где отображается код и информация об ошибках, если задача упадет.
Flower у нас подключен к брокеру и к бэкенду (Redis), но данные по таскам он хранит в себе. Если мы перезагрузим Redis, то результаты выполнения текущих задач все равно будут храниться в процессе Flower до момента его перезапуска. В то же время, если мы перезапустим Flower, то он обновится и не будет искать в Redis, какие таски были выполнены и какие еще ожидают выполнения.
Все параметры, относящиеся к выполнению задач, могут быть настроены заранее в коде. Настройки в Flower предназначены, скорее, для чего-то срочного и для того, чтобы даже тестировщик или ПМ, зашедший в Flower, мог "погасить пожар", который там может произойти.
Сегодня мы познакомились с базовыми возможностями Celery и Flower. Celery позволяет связывать задачи между собой, создавать очереди и переносить таски в фоновую обработку. А при помощи веб-инструмента Flower вы будете в курсе выполнения задач в реальном времени, сможете отслеживать и перезапускать воркеры.
Следите за нашим блогом, чтобы узнать больше о технологиях, которые мы используем в работе над проектами Evergreen. Если же у вас есть готовая идея, и вы ищете решение и команду для ее реализации, напишите нам.