В hh.ru много пользователей, а уведомлений мы отправляем еще больше: о регистрации, о восстановлении пароля, об изменении статуса услуг, о новых сообщениях и т.д. Одних только email-уведомлений мы отправляем около 900 миллионов в месяц, а ведь есть еще пуши и смс.

Меня зовут Кирилл, я — тимлид команды Bonjour в hh.ru. Сегодня я расскажу как у нас устроены рассылки.

Исторически так сложилось, что в hh.ru своя система для отправки уведомлений. Мы думали о том, чтобы перейти на подрядчиков или использование внешних сервисов, но учитывая наши объемы, это было бы либо технически сложно для них, либо экономически невыгодно для нас. 

Email-уведомления

В hh.ru сервисная архитектура, и во время работы многие сервисы отправляют свои email-уведомления. Нам хотелось отправлять все уведомления из одного конкретного места: чтобы в нем была унифицированная логика и чтобы нам было проще добавлять туда новую, общую функциональность. 

За короткий период времени сервисы могут создать большое количество сообщений. Для распределения этой нагрузки мы используем Rabbit-очереди. В этих очередях подчас лежит по несколько миллионов сообщений, и это вполне нормальная для нас ситуация. Когда сервису нужно отправить письмо, он добавляет сообщение в эту очередь, используя переменные, которые специфичны для каждого конкретного письма. 

Некоторые важные письма, вроде регистрации, восстановления пароля и документов, пользователи хотят получать мгновенно. Но если мы будем складывать эти сообщения в общую очередь, время доставки таких писем может достигать нескольких часов. Чтобы решить эту проблему мы используем несколько очередей с различными типами, которые обрабатываются параллельно и независимо друг от друга. 

Эти очереди читают инстансы сервиса, который знает: как собрать письмо по сообщению из Rabbit, как получить его текст, верстку, отправителей etc.

Помимо этого он добавляет туда дополнительную техническую информацию, например, заголовки, которые в дальнейшем помогают разбирать инциденты пользователей. Еще пример дополнительной технической информации — это добавление пикселя в каждое письмо, с помощью которого мы в дальнейшем можем отследить каждое открытие письма.

Также мы бы хотели иметь статистику по отправкам писем, но писать каждую отправку письма в базу было бы довольно накладно, поэтому на этом этапе мы просто записываем все отправки в лог.

Итак, наше письмо готово. Нам осталось только отправить его пользователям. Для этого нам надо открыть SMTP соединение, передать данные, получить ответ, правильно его распарсить и переотправить письмо в случае неудачи. Все эти типовые задачи помогает решать софт, который называется MTA – Mail Transfer Agent.

Раньше мы использовали Exim, но в дальнейшем перешли на Postfix, поскольку он больше соответствует нашим требованиям к производительности. Далее мы получаем логи Postfix и заливаем их в Hadoop вместе с логами из предыдущего этапа, данными по отправкам и открытию пикселя. На основе всех этих данных мы в дальнейшем строим аналитику, а также выводим информацию для техподдержки по статусам отправок и открытий. 

Итоговая схема выглядит приблизительно так:

Пуш-уведомления

Схема отправки пушей практически идентична со схемой отправки email-ов, за исключением того, что в качестве брокера сообщений мы используем Kafka. И здесь мы аналогично используем отдельную очередь для отправки важных уведомлений. Эту очередь слушает сервис, который хранит в себе информацию об устройствах пользователей, ключи отправки и непосредственно отправляет уведомления в нужные сервисы (Android/ios/web etc). 

В какой-то момент мы столкнулись с проблемой дублирования уведомлений. Например, при каждом открытии резюме пользователю отправлялся пуш. Чтобы решить эту проблему, мы стали хранить историю последних отправок и проверять, было ли недавно отправлено подобное уведомление. Это помогло нам избежать дублей. 

Получается, что нам необходимо хранить историю уведомлений, при этом довольно ограниченное количество времени, а также уметь быстро её читать. В качестве такого хранилища идеально подходит память, но поскольку у нас есть несколько инстансов одного и того же сервиса, им нужна общая память. И в качестве такого хранилища мы используем Redis. 

SMS-ки

SMS-уведомлений у нас гораздо меньше, чем email-ов и пушей. У нас есть специальный сервис, куда делают запрос по HTTP все сервисы, которые хотят отправить SMS, а внутри него уже у нас живет очередь на базе. 

В какой-то момент мы столкнулись с проблемой – hh.ru представлен в разных странах, и отправка, например, из России в Казахстан стоит в несколько раз дороже, чем просто по России. Мы решили её так: заключаем договоры с локальными провайдерами, и в момент отправки по профилю пользователя определяем, кого из них нам лучше использовать. 

Некоторые из проблем, с которыми мы сталкиваемся

Борьба провайдеров со спамом

Есть проблема, с которой вы можете столкнуться при самостоятельной отправке email без использования каких-то внешних сервисов - это борьба крупных провайдеров со спамом. Они могут закидывать ваши письма в спам, искусственно завышать время ответа (тем самым замедляя вашу очередь отправки), а могут и просто возвращать ошибки при отправках. Не всегда понятно, почему алгоритм решил, что вот конкретно это письмо являются спамом. Существуют разные причины, по которым это может произойти, и одна из них, с которой мы сталкиваемся довольно часто – это резкое увеличение количества отправок с нашей стороны. 

Нам приходится следить за тем, чтобы наши сервера отправляли определенное количество писем с определенной скоростью и мы не можем добавить новый сервер с новым ip и рассылать по нему с такой же скоростью, поэтому нам приходится увеличивать скорость отправок постепенно. Для крупных сервисов, которые любят внезапно увеличивать время ответа, мы выделяем отдельные инстансы Postfix и благодаря этому вся очередь не замедляется. 

Проблемы на стороне получателя

Помимо траблов с крупными почтовыми провайдерами мы также сталкиваемся с проблемами отправок писем на корпоративных пользователей. Например, это может быть неправильно настроенный сервер на стороне получателя. В выявлении этих проблем нам помогает анализ логов, про которые я рассказывал ранее. 

Публичные спам-листы

Многие компании используют публичные почтовые спам-листы и поэтому нам приходится самостоятельно управлять репутацией наших почтовых серверов. Мы мониторим наши попадания в них и стараемся своевременно из них выходить. В этом нам помогает в том числе бот, которого мы написали. Он вовремя уведомляет нас в Slack о том, что мы попали в новый спам-лист. 

Многое может пойти не так. От момента, когда мы решили отправить письмо пользователю и до момента, когда он его действительно получил. Поэтому мы стараемся мониторить как можно больше, чтобы быстро определять проблемы. Мы мониторим: 

  • Все наши сервисы на стандартные параметры – RPS, потребление памяти, CPU, количество тредов etc.

  • Размеры очередей. Если в очереди слишком много сообщений, это значит, что сервисы перестали справляться с нагрузкой и мы выпустили какое-то направление, которое их замедлило. 

  • Каждый крон, который генерирует письма и разбиваем этот мониторинг на этапы, чтобы понимать, на каком из них произошла проблема.

  • Специфичные ответы от почтовых сервисов и время их ответа, чтобы понять, что нас не замедляют.

  • Время от момента, когда письмо было создано, до момента, когда это письмо было отправлено

  • Количество отправок в целом. Если отправок слишком мало, значит по какой-то причине письма перестали отправляться и это может стать проблемой. Если отправок слишком много — тоже беда. Как-то раз у нас в kafka-клиенте случился баг, из-за которого одно и то же письмо отправлялось бесконечное количество раз, а наши пользователи получили десятки и сотни тысяч одинаковых писем. Не надо так.

Заключение

Как видите, организовать и поддерживать свой сервис уведомлений — довольно сложная задача. Поэтому, если у вас есть возможность использовать готовый сервис, то я рекомендую вам не заморачиваться лишний раз. Но если нет, я надеюсь что наш опыт вам поможет.

Делитесь в комментах вашим опытом по управлению рассылками и уведомлениями, нам интересно!

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


  1. luna_kamenskaia
    14.07.2022 14:54
    -3

    Интересная и нужная статья, благодарю!


  1. druidvav
    15.07.2022 01:12

    У нас всего 20 млн уведомлений и архитектура попроще, но тоже экономического смысла перевода уведомлений подрядчикам не нашлось. Получается на порядки дороже.


  1. p0vidl0
    15.07.2022 03:38
    +1

    Спасибо за статью.

    Подскажите, чем обусловлено использование Kafka, а не Rabbitmq во втором случае?


  1. makar_crypt
    15.07.2022 09:28
    +1

    Очень поверхностная статья, самые главные вопросы в таких статьях - это как вас не банят, поэтому: где описания как у вас проходит кластаризация по сервисам почт (провайдерам)? Соотвественно даже в схеме архитектуры е хватает звеньев в виде хранилища поинтов или какогото глобального менеджера управления. Где как кто хранит поинты отправки по провайдерам? как вы формируете шадалинг на каждого провайдера? где цифры в отношении времени отправки ? где информация на сколько вы "прибавляете" писем в час, год , день , на каких провайдерах, в общем чтобы вас не забанели за буст?

    В этом плане статья не удалась.