— Расскажи немного деталей о проекте, над которым ты сейчас работаешь?
В данный момент я занимаюсь финтех-стартапом Statusmoney, который анализирует пользовательские финансовые данные и позволяет клиентам сравнивать свои доходы и расходы с другими группами людей, выставлять лимиты по тратам, наблюдать, как растет или падает благосостояние на графиках. Пока проект ориентирован только на североамериканский рынок.
Для анализа финансовой информации мы скачиваем и храним все транзакции пользователей и интегрируемся с кредитными бюро, чтобы получить дополнительные данные по кредитной истории.
Сейчас у нас около 200 тыс пользователей и 1,5 терабайта различных финансовых данных от наших поставщиков. Одних транзакций около 100 млн.
— А каков технологический стек?
Стек текущего проекта — это Python 3.6, Django/Celery и Amazon Web Services. Мы активно используем RDS и Aurora для хранения реляционных данных, ElasticCache для кэша и для очередей сообщений, CloudWatch, Prometheus и Grafana – для алертинга и мониторинга. Ну и, конечно, S3 для хранения файлов.
Мы также очень активно используем Celery для различных бизнес-задач: рассылки уведомлений и массовых рассылок писем, массового обновления различных данных из внешних сервисов, асинхронного API и тому подобного.
На фронтенде у нас React, Redux и TypeScript.
— Какой основной характер нагрузок в вашем проекте и как вы с ними
справляетесь?
Основная нагрузка в проекте ложится на фоновые задачи, которые исполняет Celery. Ежедневно мы запускаем около полумиллиона различных задач, например, обновление и процессинг (ETL) финансовых данных пользователей из различных банков, кредитных бюро и инвестиционных институтов. Помимо этого отсылаем много уведомлений и рассчитываем множество параметров для каждого пользователя.
Еще у нас реализован асинхронный API, который «пулит» результаты из внешних источников и также генерирует множество задач.
В данный момент, после тюнинга инфраструктуры и Celery, справляемся без проблем, но раньше бывало всякое, обязательно расскажу об этом в своем докладе.
— Как вы это всё масштабируете и обеспечиваете отказоустойчивость?
Для масштабирования мы используем Auto Scaling Groups – инструментарий, который предоставляет наша облачная платформа AWS. Django и Celery хорошо масштабируются горизонтально, мы только немного настроили лимиты на максимальное количество памяти используемой воркерами uWSGI/Celery.
— А мониторите чем?
Для мониторинга cpu/memory usage и доступности самих систем используем Cloud Watch в AWS, различные метрики из приложения и из Celery-воркеров агрегируем с помощью Prometheus, а строим графики и отправляем алерты в Grafana. Для некоторых данных в Grafana мы используем ELK как источник.
— Ты упоминал асинхронный API. Расскажи чуть подробнее, как он у вас
устроен.
У наших пользователей есть возможность «прилинковать» свой банковский (или любой другой финансовый) аккаунт и предоставить нам доступ ко всем своим транзакциям. Процесс «линковки» и обработки транзакций мы отображаем динамически на сайте, для этого используется обычный пулинг текущих результатов с бекенда, а бекенд забирает данные, запуская ETL pipeline из нескольких повторяющихся задач.
— Celery — продукт с противоречивой репутацией. Как вам с ним живется?
По моим ощущениям, наши отношения с Celery сейчас находятся на стадии «Принятие» – мы разобрались как фреймворк работает внутри, подобрали для себя настройки, разобрались с деплоем, «обложились» мониторингом и написали несколько библиотек для автоматизации рутинных задач. Некоторой функциональности нам не хватило «из коробки», и мы дописали ее самостоятельно. К сожалению, на момент выбора стека технологий для проекта, у Celery было не так много конкурентов, и если бы мы использовали более простые решения, то нам пришлось бы дописать намного больше.
С багами именно в четвертой версии Celery мы ни разу не сталкивались. Большинство проблем было связано либо с нашим непониманием того, как это все работает, либо со сторонними факторами.
О некоторых написанных внутри нашего проекта библиотеках я расскажу в своем выступлении.
— Мой любимый вопрос. Как вы всю эту музыку тестируете?
Задачи Celery хорошо тестируются функциональными тестами. Интеграцию тестируем с помощью автотестов и ручного тестирования на QA-стендах и стейджинге. На данный момент мы еще не решили пару вопросов с тестированием периодических задач: как позволять тестировщикам их запускать и как проверять, что расписание у этих задач корректное (соответствует требованиям)?
— А тесты на фронтенд и вёрстку? Какое вообще соотношение ручного и
автоматизированного тестирования?
На фронте мы используем Jest и пишем только юнит-тесты на бизнес-логику. 55% бизнес-критикал кейсов у нас сейчас покрыты автотестами на Selenium, на данный момент у нас около 600 тестов в TestRail и 3000 тестов на бекенде.
— О чем будет твой доклад на Moscow Python Conf ++ ?
В докладе я подробно расскажу, для каких задач и как можно использовать Celery, и сравню его с существующими конкурентами. Опишу, как можно избежать различных граблей при проектировании сложной системы с большим количеством задач: какие настройки стоит указать сразу, а какие можно оставить на потом, как задеплоить новую версию кода так, чтобы не потерять задачи при переключении трафика, поделюсь написанными библиотеками для мониторинга задач и очередей.
Также затрону тему реализации ETL-пайплайнов на Celery и отвечу, как описать их красиво, какую использовать retry policy, как гранулярно ограничивать количество выполняемых задач в условиях ограниченных ресурсов. Плюс опишу, какие инструменты мы используем для реализации пакетной обработки задач, которая экономно расходует доступную память.
В общем, если вы жаждите деталей по всем вышеобозначенным пунктам, приходите. Надеюсь, мой доклад покажется вам полезным и интересным.
Комментарии (22)
Bahusss
09.10.2018 19:59Хм… это самописное решение или таки celery?
celery, мы в таски передаем только id-шники, сообщение в таком случае и не должно занять больше 2Кб.
В сообщениях приходится передавать большие объемы информации
Все еще не совсем понятен юзекейс, зачем вы передаете в задачу всю информацию по событию? Событие в sentry все равно посылается асинхронно без задач, а в других случаях разве не нужно сохранять это событие в промежуточный сторадж (база, кэш)? Чтобы потом прочитать его в задаче по ключу? Мы у себя редко передает в параметры задач что-то больше 2Кб. Вся информация, которая нужна в задаче «достается» из каких-либо стораджей.
Задачи выполняются очень долго и все время, пока выполняются висят в очереди (кстати, а у вас так или вы как-то по другому удостоверяетесь в том, что задача не пропала?)
Мы ставим максимальный софт таймаут для некоторых задач на 5 минут, если задача работает больше – значит это ненормально и что-то пошло не так, если внешний сервис лежит, то в задачах у нах предусмотрен exponential backoff и подходящая retry policy. А что значит «не пропала»? Вы имеете ввиду гарантируем ли мы как-то что все задачи действительно выполнены? Нет не гарантируем, да и сам redis такого не гарантирует, но мы знаем об этой особенности, для нашего проекта она пока не критична.
Задачи идут волнами. Например, есть задача «импорт с внешнего источника» и она создает большое количество задач волнами, что как бы приводит к их накоплению.
Буду об этом рассказывать в своем докладе. Мы тоже несколько раз получали task flooding, поэтому поигрались с настройками и написали свой чанкификатор задач, который запускает их определенными порциями и таким образом обеспечивает равномерное накопление задач в очередях и не перегружает внутренние (например базу) и внешние сервисы.
Ну и да, возвращаясь к редису — вы его как-то кластеризируете или он у вас просто так стоит как единая точка отказа?
Пока мы кластеризуем его хуже чем нам хотелось бы, AWS обеспечивает автоматический фейловер в случае если с мастер нодой что-то случилось, но в те несколько секунд пока происходит фейловер – постановка задач фейлится, может быть какие-то окажутся потеряны. Эту проблему мы у себя пока не решили, но можно посмотреть в сторону github.com/dealertrack/celery-redis-sentinel – там есть retry в случае connection error'a.
SirEdvin
09.10.2018 20:17У вас довольно интересная схема, хотя мне и кажется, что она немного читерская, потому что, исходя из того, что я понял, всю инфу по таске вы храните во внешнем источнике, получается цепочка redis -> storage -> task execution, вместо rabbitmq -> task execution. Мне сложно понять выгоду такой схемы, но выглядит интересно)
А так большое спасибо за такой развернутый ответ, довольно познавательно :)
Касательно кластеризации, немного отвратительный опыт с redis sentinel из-за того, что там нельзя разделить bind/advertise адреса. Из-за этого sentinel совсем у нас не работал.
Imrahil
10.10.2018 18:09+1Самая что ни на есть оптимальная схема)
Зачем тянуть в таск то что можно найти уже в воркере?
Понятно что это не панацея и от всех случаев не спасет, но сама идея весьма продуктивна и работает в продакшене как часы.
А выгода быть может тогда когда данные для успешного выполнения таска могли поменяться.
Если мы передадим в таск сразу готовый набор данных то он окажется устаревшим (Страшно подумать, но например отложенная оплата с баланса в системе или другой кошмар на выбор). И ситуация когда мы в таск передаем только тип операции, id юзера ну и id странзакции..(или id таска в БД) Воркер сам все подтянет, проверит возможность и выполнит или задеклайнит таск…
SirEdvin
Интересно, а какие альтернативы celery на питоне есть сейчас? А то большая часть проектов умеет только в redis, что явно не prouduction-ready решение.
eyeofhell Автор
Множество их.
SirEdvin
rq — только redis
tasktiger — только redis
Huey — только redis
WorQ — только redis
dramatiq — вроде неплохо, но не хватает ряда полезных фич, например, событий.
django-carrot — странная штука, просто обертка над rabbitmq
Ну гуглить я тоже умею, интересно все-таки немного отзывов.
eyeofhell Автор
Тада ждем, я пайтон больше для автоматизации использую, с очередями и шинами почти не работал.
Nerevar_soul
apscheduler — вот такое еще есть. Не только redis, правда rabbitmq нет.
vsespb
Что значит "явно не prouduction-ready решение" применительно к редису для очередей?
SirEdvin
Подскажите, что вы будете делать, когда у вас очередь перестанет влазить в память? Это ждет практически любой проект, который использует большое количество фоновых задач.
Ну и редис не скалируется по человечески.
К самому редису претензий нет, но использовать его для очереди задач очень странно, на мой взгляд.
vsespb
Понятно. Используем редис. Пока в память влазят миллионы задач, ну а больше нам и не надо, ибо задачи должны быстро обрабатываться, а не копиться, и ненулевое количество задач в очереди вообще нужно для сглаживания пиков нагрузки.
SirEdvin
И сколько он у вас памяти ест? Мне вот 450 мб не хватало для 5-10 тысяч задач.
vsespb
Точно не могу сказать. Он на машине с 32 гигами, выделенными под редис, но в нём не только очереди, но и другие NoSQL данные (в других БД редиса). Ну допустим на максимум 32гб можно ориентироваться. Обычно он жрёт чуть меньше 10Гб.
SirEdvin
И какой у вас поток задач в секунду? Ну, у нас пока не доходит до десятка в секунду. У вас под сотню или где-то так же?
Просто на мой взгляд, если где-то меньше десятка, то такой поток вытягивает и кролик на 1 ГБ и со слабым диском.
vsespb
Этот вопрос я так понимаю имеет отношение к скорости редиса. Точных цифр бенчмарков на прод не помню, но сразу могу сказать что он медленнее RabbitMQ, и собственно его скорость зависит от той собственно системы очередей, что используется, у нас своя система, а она сильно влияет на бенчмарки.
Из цифр могу сказать что эпизодически ставятся сотни тысяч задач в очередь и этого никто не замечает, если по ошибке поставить 10млн, то да, память кончится. На этот случай есть мониторинг.
SirEdvin
Это довольно странно, так как Redis должен быть быстрее RabbitMQ, в силу того, что кролик далеко не всегда хранит свои очереди в памяти. Если редис еще и медленее кролика смысла использовать его никакого нет, на мой взгляд.
Просто меня довольно давно удивляет, что все берут для очередей редис, учитывая, что есть rabbitmq, который и более надежный, и более гибкий, и еще позволяет делать довольно просто отказоустойчивый сетап. Мне казалось, что единственное преимущество редиса — это скорость. Или вы взяли его по другой причине?
vsespb
Так, по скорости, счас посмотрел у нас явно бывает больше 350 в секунду.
Редис будет быстрее рэббита если написать на нём свою очередь, состоящую из пары команд PUSH/BLPOP, в которой ничего больше нет.
Если сделать так чтобы задачи не терялись в случае любых ситуаций (воркер взял задачу и упал), а так же чтобы не было race condition при различных кейзах, а так же возврат результата работы, + статистика, мониторинг, то будет медленнее.
Проблем с надёжностью редиса не вижу. Гибкость как раз больше у редиса (если самому писать под него систему), т.к. это фактически бэкэнд для системы очередей, а не готовая система очередей.
Сложно сравнивать голый рэббит и некую систему очередей X, которая использует Redis.
По самому редису нет никаких нареканий к надёжности, и гибкости. А скорость обычно пострадает при написании системы очередей под него.
ArsenAbakarov
проблема rabbitmq кластера — split brain, у нас уже было такое несколько раз
SirEdvin
Нет кластера — нет проблем?) split brain можно словить в любом кластере, в master-slave немного сложнее, но тоже вполне можно попробовать.
Или у rabbitmq какая-то серьезная проблема именно со split brain? Вам не подошел autoheal или он не работает в вашем случае?
Bahusss
redis для очередей вполне production-ready решение, проверено уже на многих проектах.
При 50000 задач ежесекундно (при условии что воркеры успевают их разгребать) redis в нашем проекте потреляет 80 Мб памяти.
Если очередь перестает помещаться в память, то скорее всего происходит что-то не то: либо задачи накапливаются – что не очень хорошо, либо вы передаете в сообщениях мегабайты данных, чего тоже делать не стоит.
SirEdvin
Хм… это самописное решение или таки celery?
А как насчет таких случаев:
Ну и да, возвращаясь к редису — вы его как-то кластеризируете или он у вас просто так стоит как единая точка отказа?
Bahusss
Промазал, ответил вам ниже.