У Яндекса много самописных сервисов для внутренних задач: Яндекс.Формы, Яндекс.Диск, трекер, календарь. Со временем их решили использовать не только внутри компании, но и за ее пределами. Так появилась платформа Яндекс.Коннект.

Большинство сервисов Коннекта построено на Python V3. В качестве web-фреймворка используется Django, реже Flask и Tornado, а новые чаще пишутся на FastAPI. Сервисы, как и базы PostgreSQL, MySQL и MongoDB, живут в облаке. В качестве очереди сообщений почти везде используется Celery с MongoDB в качестве брокера. Он и стал проблемой.

На Russian Python Week 2020 Владимир Колясинский, разработчик бэкенда сервисов платформы Яндекс.Коннект, рассказал, почему они пользовались связкой Celery MongoDB и почему пришлось отказаться от этого брокера. Он сравнил претендентов: Redis, RabbitMQ и YMQ, с их плюсами и минусами. Подробно разобрал процесс переезда на нового брокера, анализ его состояния и возможные проблемы. И у него получилась пошаговая инструкция, которая пригодится при подборе и настройке брокера. А для любителей разбираться самостоятельно под катом есть расшифровка доклада с конференции.

С развитием платформы Яндекс.Коннект у нас росла нагрузка, и брокер MongoDB больше не справлялся. Работал он нестабильно и от этого сбоили сервисы. При этом Celery мы менять не хотели. Он надежный, отказоустойчивый, умеет масштабироваться и позволяет развязать приложения. Например, наш бэкенд и воркеры могут быть написаны на разных языках, но они умеют общаться через протокол, который предоставляет очередь сообщений. Тогда даже выход из строя отдельных воркеров не влияет на надежность системы и сообщения остаются в очереди до восстановления.

Как Celery работает в Яндекс.Формах

В Яндекс.Формах можно создавать опросы и собирать ответы. После ответа на форму вы можете задать выполнение действий: сообщение на почту, тикет в трекер или отправка информации на Вики-страницу. Действия выполняются с помощью Celery-таски, которая ставится после валидации ответа пользователя и его сохранения в базу. В сервисе используются и периодические таски, например, на открытие и закрытие форм по времени, на обновление различных счётчиков ответов, а также на синхронизацию с внешними сервисами поставки данных.

Почему использовали MongoDB

Когда мы выбрали MongoDB, он единственный умел работать с несколькими дата-центрами. Это позволяло переключаться между зонами доступности при падении одного из дата-центров и поддерживать надежность сервисов. Помимо этого у нас в штате были спецы с опытом работы на Mongo, так как на ней жили некоторые наши сервисы. Они же стали следить за базами, которые мы использовали в качестве брокера Celery. Если ваше приложение уже использует MongoDB в качестве БД, вам легко подключить MongoDB в качестве брокера: достаточно в конфиге передать путь подключения к существующей БД и начать ее использовать.

При небольших объемах MongoDB был стабилен, но со временем стали проявляться его минусы. Когда его убрали из списка поддерживаемых брокеров Celery, получать поддержку от разработчиков стало трудно. Приходилось делать собственные патчи. Что в итоге привело к проблемам с обновлением версий библиотек.

С ростом нагрузки MongoDB начал тупить на больших объемах сообщений. Скакали тайминги работы с очередью, некоторые сообщения долго оставались в очереди и возникали зависания очередей. Встроенный монитор Celery Flower хоть и показывал количество сообщений в очереди с общим количеством успешных/неуспешных задач, не давал общей картины работы брокера. И если при маленьких объемах сообщений можно было подключиться к коллекции сообщений напрямую и провести тесты, то при большой нагрузке эти процессы занимали слишком много времени. В целом, MongoDB изначально не предназначался для использования в качестве очереди, у него для этого не было оптимизации режима работы.

Критерии выбора нового брокера

Мы выбирали брокеры из списка стабильной поддержки Celery с возможностью одновременной работы в нескольких дата-центрах. Особое внимание обращали на соотношение скорость/надежность — сообщения в очередь должны передаваться максимально быстро, но ни в коем случае не теряться.

Из-за того, что планировался одновременный переезд всех сервисов платформы, мы хотели минимизировать работы по настройке и найти конфигурируемый брокер на облаке. Это бы упростило запуск новых проектов, а с нас сняло лишнюю задачу по администрированию.

При рассмотрении средств диагностики опирались на имеющийся опыт и искали максимум настроек аналитики. В итоге наш выбор остановился на трех брокерах: Redis, RabbitMQ и YMQ. Первые два достаточно известны, а про Yandex Message Queue могли слышать не все. Да и в документации Celery его нет.

YMQ — разработка Яндекса, имеющая идентичный Amazon Simple Queue Service API. Этот масштабируемый геораспределенный сервис построен поверх Yandex Database и предоставляется в рамках Яндекс.Облака. Его API позволяет использовать SQS совместимые библиотеки, в том числе Celery. Особенность этой индексной БД —  легкое масштабирование и высокая отказоустойчивость.

Сравнение брокеров для Celery

Скорость работы

Все измерения проводились на сервисах платформы Яндекс.Коннект со средней нагрузкой около 300 сообщений в очереди в секунду. Каждый из тестируемых брокеров устанавливался на 1 день с полным замером всех данных.

Redis был значительно быстрее конкурентов, что не удивительно для очереди, которая работает с сообщениями в памяти, а не на диске. С включенным режимом персистентного хранения данных «Append only file» и с сохранением данных раз в секунду, 99,9% запросов укладывалось в 8 мс. Даже когда мы отключали хранение данных такого типа, скорость практически не менялось. А вот при включении режима сохранения «Append only file» на каждую операцию, скорость замедлялась до 99,9% запросов в 110 мс.

Когда мы тестировали RabbitMQ с выключенным режимом «Publisher Confirms», 99,9% запросов укладывалось в 80 мс. Но при включении режима подтверждения принятия сообщений скорость падала примерно на 40%. Все режимы существенно повлияли на скорость запросов, что видно в таблице. У YMQ скорость несколько ниже конкурентов. На отправку 99,9% запросов необходимо 150 мс.

Гарантии доставки

В Redis есть только режим «at most once». Он гарантирует доставку сообщений до воркеров не более одного раза. Если воркер вычитал сообщение из брокера и по какой-то причине упал, то сообщение потеряется. При доставке «at most once» подтверждения обработки сообщения воркером нет.

В RabbitMQ помимо режима «at most once», есть режим «at least one», при котором воркер уведомляет систему о получении и обработке сообщения, а также о его последующем удалении.

В YMQ «at least one» включен по умолчанию, поэтому есть гарантия, что сообщение будет доставлено и не потеряется. Правда, при доставке «at least one» возможна многократная отправка сообщений. Допустим воркер вычитал сообщение, успешно его обработал и послал сигнал о том, что сообщение можно удалять. Если в этот момент произойдет сетевая ошибка или воркер упадет, то брокер сообщение не получит. Исходя из своей логики он снова передоставит её другому воркеру.

Простота конфигурации

Врамках Яндекс.Облака у Redis используются преднастроенные конфигурации, которые не позволяют осуществить тонкую настройку. Например, по умолчанию включен режим «Append Only File» каждую секунду, и изменить данную конфигурацию нельзя. Более детальные настройки брокера появляются только при настройке на собственном железе.

У RabbitMQ еще более сложная система конфигурирования. У YMQ нет запуска на собственном железе, поэтому этот пункт мы обойдем стороной.

Данные для аналитики

Redis использует Redis-stat и RedisLive, а также стандартную аналитику Celery Flower.

RabbitMQ мониторит параметры утилитой DataDog или связкой Ganglia+Graphite и тоже работает с Celery Flower.

У YMQ собственные метрики: возраст самого старого сообщения, количество попыток чтения, количество сообщений в обработке, в очереди, в DLQ и время пребывания в очереди, длительность вызова SendMessage, время обработки.

Надежность

Я имею в виду надежность в плане сохранения сообщений в случае возникновения каких-либо проблем с системой. Redis для этого использует режим персистентного хранения данных «Redis Database Backup» и делает снэпшоты системы через установленные интервалы. Однако есть вероятность потери всех сообщений до момента последнего снэпшота. Еще у Redis есть режим «Apend only file», который пишет данные каждую секунду. Но и он может потерять сообщения за последнюю секунду работы. Например, если вы пишете 30 сообщений в секунду, то теоретически можете потерять 30 задач. При включении «сохранения данных на каждую операцию», вы получаете надежность, но падаете в скорости работы.

RabbitMQ по умолчанию живет в одной ноде, но есть возможность объединения нескольких нод в кластер. При этом, опять же по умолчанию, очередь живет на одной из нод, и, если эту ноду выбивает, она становится недоступной для работы. Можно включить режим «High Available Queues», который реплицирует данные в очереди и операции по нодам кластера. Но и тут есть возможность потери сообщений при падении и смене мастера, если узлы находятся в разных дата-центрах. Еще при разрыве сети (опять же когда узлы живут в разных дата-центрах) может произойти split brain и появиться два изолированных кластера, каждый из которых считает, что он мастер. А такую ситуацию система не может решить без вмешательства человека. В официальной документации RabbitMQ не рекомендуются режимы использования с узлами на разных дата-центрах именно из-за проблем связанных с сетевыми ошибками.

YMQ по умолчанию хранит каждое сообщение в трех репликах в трех дата-центрах. Он спроектирован так, чтобы пережить потерю значительного количества своих составных частей: серверов или даже целых дата-центров. При потере одного дата-центра реплики оставшихся удваиваются, что позволяет без потерь пережить возникшие проблемы. Создатели YMQ заявляют, что с момента запуска не было потеряно ни одного сообщения.

Итоговая таблица сравнения:

По надежности лидируют Yandex Message Queue и RabbitMQ, но Redis тоже достаточно отказоустойчив. Надо понимать, что является критерием надежности. Если вы живете в одном дата-центре, то RabbitMQ вам отлично подойдет, так как не будет вероятности разрыва сетей и других потерь данных как при использовании разных дата-центров. Но для платформы Яндекс.Коннект одним из важных критериев отбора была именно возможность работы на нескольких дата-центрах.

По скорости первое место занимает Redis. И если потеря некоторого количества сообщений не является для вас критическим фактором, например при внедрении логики с высоким рейтом, то Redis отлично подойдет.

Если говорить про стоимость, то отталкивайтесь от своих ресурсов и нагрузки на вашу систему. Например, RabbitMQ бесплатен, но затраты на его разработку и администрирование могут оказаться выше облачной подписки, не говоря уже про стоимость железа для него. Тогда как стоимость месячной подписки на YMQ за 10 сообщений в секунду в течении месяца — 800 рублей.

Мы остановились на Yandex Message Queue, так как он обещал устраивающие нас параметры скорости и гарантировал отличную надежность по сравнению с тем, что у нас было на MongoDB. У него достаточно простой интерфейс конфигурирования. А совместимость Yandex Message Queue с Amazon SQS позволяет использовать с ним не только Celery из коробки, но и любые совместимые библиотеки, например, Boto3 для работы с очередью. Плюс у YMQ множество метрик и алертов из коробки для легкой диагностики проблем.

Переезд на новый брокер

Изменение конфигурации проекта

Чтобы переехать на Yandex Message Queue надо зарегистрироваться в системе и сгенерировать ключи доступа. А затем передать ключи доступа в конфиг Celery.

После переезда на YMQ в настройках изменяется только URL и добавляются специфичные для данного брокера показатели:

MongoDB
CELERY_BROKER_URL =  (
         f'mongodb://{MDB_USER}:{MDB_SECRET}@'

         f'{MDB_HOSTS}/{MDB_DBNAME}?replicaSet={MDB_RS}'
)
YMQ
CELERY_BROKER_TRANSPORT_OPTIONS = {

        'is_secure': True,

        'region': 'ru-centrall'

        'visibility_timeout': 30 * 60, # 30 minutes

}
CELERY_BROKER_URL = (

        f'sqs://{YMQ_BROKER_KEY}:{YMQ_BROKER_SECRET}@'

        f'{YMQ_ENDPOINT}'

)

Гарантированная доставка сообщений (Visibility timeout)

Если говорить об изменениях в нашем коде, наши сервисы с радостью, что греха таить, обновились до свежих версий Celery, выкинув самописные патчи. И нам нужно было учесть правки для механизма «visibility timeout», который гарантирует доставку «at least one» в YMQ. Расскажу, как он работает.

Представим, что мы записали какое-то количество сообщений в систему. После того как воркер вычитал сообщение, оно помечается скрытым и задается промежуток «visibility timeout». Скрытые сообщения не передаются другим воркерам, но брокер не удаляет их, а сохраняет скрытыми. После того, как воркер выполнил задачу, он должен явно прийти в систему и удалить сообщения. Если этого не произошло, то через промежуток «visibility timeout» (по умолчанию 30 секунд) сообщения возвращаются в очередь и будут доступны другим воркерам. 

Так как параметр «visibility timeout» конфигурируемый, то в разных сервисах Яндекс.Коннект у него разные значения. Например, в Яндекс.Формах его значение 30 минут из-за механизма «retry уведомлений». В других сервисах Яндекс.Коннект значения зависят от их специфики. 

После правок нам осталось только переключиться на использование Yandex Message Queue. 

Процесс выкатки

Тут было все достаточно просто. Мы выкатили компоненты сервиса на YMQ, а несколько воркеров оставили на старом конфиге MongoDB, чтобы на момент переключения они смогли обработать сообщения, оставшиеся в очереди. После того как они достаточно быстро обработали сообщения, которые были в MongoDB, мы убрали эти дополнительные воркеры и погасили саму инсталляцию MongoDB.

Подведение итогов

После перехода платформы Яндекс.Коннект на YMQ, надежность брокера по сравнению с MongoDB выросла, а скорость доставки сообщений осталась такой же высокой. У нашего Celery снова появилась официальная поддержка. Мы полностью переехали на облачный сервис потому, что для многих наших сервисов брокер MongoDB был последним компонентом, который жил на физическом железе. Теперь у нас достаточно простая конфигурация и легкое поднятие брокера для новых проектов. Время разработки посвящено не администрированию очереди, а решению текущих задач. А благодаря штатным графикам и алертингу мы всегда можем понимать стабильно ли у нас работает система.

Профессиональная конференция для Python-разработчиков пройдет 27 и 28 сентября в Москве. Расписание готово, выбрать самые интересные доклады можно уже сегодня.

Билеты можно купить здесь. До встречи в офлайн-сентябре!

Комментарии (1)


  1. Insurgent2018
    24.08.2021 13:51

    YMQ — разработка Яндекса, имеющая идентичный Amazon Simple Queue Service API

    нет, это не так. Например, нет tags, как они есть в AWS SQS.