December 27, 2019

История одного вебхука: от 0 до 12'500 RPS - Дмитрий Кушников

Расшифровку подготовил канал Hash

Слайд

Меня зовут Дима. Сегодня я расскажу вам одну историю, участником которой являюсь последние несколько лет. К сожалению, в названии доклада вкралась ошибка. История будет не про 12 500 RPS, а про 25 000. Пока я готовил доклад, так успела вырости нагрузка на сервис.

Слайд

Меня зовут Дмитрий Кушников. Я работаю в компании ManyChat, где руковожу разработкой. На примере одного из компонентов нашей системы, я расскажу как, по мере роста нашей компании, продукта и сервиса, изменялась архитектура. Также расскажу каким образом мы реагировали на это и какие практики и подходы, которые мы применяли. Второе название этого доклада «Что делать, когда PHP уже не подходит, но очень хочется».

Слайд

ManyChat — сервис-платформа, которая помогает бизнесам делать поддержку продаж и продвижения через мессенджеры.

Слайд

Наш старый продукт — это сервис Messenger Marketing. За всё время нашего существования более 1 миллиона бизнесов воспользовались нашим сервисом. Они провзаимодействовали более чем с 700 миллионами пользователей по всему миру. И каждый месяц через нашу платформу отправляется больше 8 млрд сообщений.

Слайд

Почему в Facebook Messenger, ведь мы находимся в стране, где побеждает Telegram? Дело в том, что Telegram очень популярен в России и некоторых других странах, но в Европе и Америке одним из самых популярных мессенджеров является именно Facebook. Почти полтора миллиарда пользователей — это месячная аудитория. Огромное количество бизнесов, которые пользуются Facebook. Кроме того, платформа Facebook достаточно большая, над ней работают 300 000 разработчиков. А раз в месяц они отправляют 200 миллиардов сообщений. Поэтому мы примерно 40% делаем воздушные платформы.

Слайд

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

Слайд

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

Слайд

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

Слайд

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

Со стороны бизнеса, ManyChat — web-приложение, которое через 5:44 ??? настраивает ту бизнес-логику, о которой я сказал выше.

Слайд

С технологической точки зрения у нас всё довольно просто. У нас достаточно скромный стэг. В основе PHP, nginx, postgres. В качестве баз данных мы используем Redis, elasticsearch — как дополнительное хранилище, и все это крутиться в облаках Amazon.

Слайд

Facebook Webhook — это обычный запрос, который содержит в себе JSON payload. Основная наша задача — разобрать его, понять, что там происходит, и запроцессить. Вебхуки — это не самая большая часть загрузки нашего сервиса, они занимают примерно 10% от всей нагрузки. Но на примере этого небольшого кусочка кода, можно отследить, как эволюционировала вся наша система.

Вубхук является довольно чувствительной частью нашей системы. Дело в том, что время загрузки сайта влияет на клиентов. Если он долго загружается или виснет, клиент не станет дожидаться загрузки, а сразу уйдет. Когда пользователи взаимодействуют с бизнесом, и бот долго отвечает (например, если не работает приложение или платформа), то клиент также перестает коммуницировать.

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

Слайд

Как рос и развивался наш сервис? Вернемся на 3 года назад, в май 2016 года. На нашей платформе создано 20 ботов, (10 создали мы сами для тестов) и 20 подписчиков.

Слайд

Наша нагрузка составляет, примерно, 0 RPS. У нас стандартное взаимодействие — запрос от Facebook приходит в Nginx, Nginx передает суть запроса PHP-FPM, PHP-FPM запускает приложение и в результате обработки происходит запрос в Facebook.

Слайд

Через месяц мы анонсировали наш сервис на продакт ханте. Теперь у нас 2000 ботов и 7000 подписчиков.

Слайд

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

Слайд

Нам помог один хак. Мы решили, что рано что-то делать асинхронно, но нам нужно быстро научиться отвечать. Вместо того, чтобы заставлять Nginx ждать обработки запроса в Facebook, теперь мы прерываем соединение с Nginx. ??? 10:28 говорим, что нужно закончить процессинг, но оставляем на фоне работать скрипт PHP-FPM. Это позволило нам за несколько минут решить проблему и двинутся дальше.

Слайд

Прошло полгода — мы выпустили несколько фич, у нас стало в 5 раз больше вопросов, а количество подписчиков выросло в 100 раз. После того как мы запустили 2 фичи (LifeChat — это возможность бизнесам отправлять сообщение напрямую пользователю без автоматизации, вторая — это обработка статистики), количество вебхуков, которые мы обрабатываем, выросло примерно в 4 раза.

Слайд

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

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

Слайд

Ещё полгода. Мы снова выросли: 75 000 ботов, 7 млн подписчиков. До этого момента мы отрабатывали только вебхуки, которые были связаны платформой Facebook, нас интересовало только то, что делает пользователь в мессенджере. После мы решили сделать новую фичу. Для того, чтобы она заработала, нам нужно было обработать новые вебхуки, связанные с обновлением ленты страницы Facebook. У обычных лент Facebook обновления происходят достаточно редко, но есть большие страницы.

Слайд

Однажды у нас случился день Кэти Перри. Кэти Перри — известная американская певица, у неё миллионы фанатов по всему миру. Когда ее маркетологи, решили создать бота и выбрали ManyChat в качестве своей странички, то мгновенно нагрузка на систему выросла в три-четыре раза.

Слайд

Тогда мы поняли, что без нормальной реализации очередей больше не обойтись и начали использовать Redis. Это было удачное решение для нас, ведь сейчас мы делаем на Redis не только очереди, но и мониторинг, блокировки. Он стал просто незаменимым инструментом нашей системы.

Слайд

Я уже рассказывал об этом на докладе на метапе, примерно, год назад. Если интересно, вы можете посмотреть. В докладе есть примеры библиотеки для работы с Redis, которые мы написали и используем. Сейчас через наш эддиткластер каждую секунду приходит 1 млн событий.

Слайд

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

Слайд

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

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

Слайд

При помощи Redis удалось реализовать сложносоставную в систему каскадных очередей. На слайде показана первая её реализация. И мы решили, что будем делать очередь для каждого бота. То есть, когда нам приходит сообщение, для каждого бота мы складываем его задачи в отдельную очередь. Для того, чтобы знать, что какая-то очередь не пуста, мы сделали отдельную, контрольную очередь. Таким образом, когда приходит вебхук, в очередь ввода складывается одно сообщение, в контрольную — другое, а воркер забирает задачу из контрольной очереди и начинает работать.

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

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

Слайд

Следующий шаг, ещё полгода. В тот момент через платформу в месяц мы преодолели отметку отправки 1 млрд сообщений.

Слайд

Нагрузка, пример, 5К RPS. Мы снова столкнулись с доступностью и снова на входе воронки на уровне PHP-FPM. С этим нужно было что-то делать. Примерно в это время все начали говорить про асинхронный PHP. Тогда мы посмотрели на react PHP и написали за 3 часа тест, который показал что один инстанс react PHP увеличивает пропускную способность в 4 раза.

Слайд

Поэтому мы убрали PHP-FPM из процессинга и заменили его на react PHP. Мы не стали ничего делать по рекомендациям react PHP и просто запустили ie-application ??? 19:38 внутри react PHP. Даже это позволило получить прирост в пропускной способности.

Слайд

Чтобы получить ещё больший прирост, мы начали отмасштабировали процессинг react PHP. В какой-то момент, у нас было до 30 воркеров. Но здесь возникает проблема, если ты расширил воронку на входе, значит, начал страдать процессинг.

Слайд

Что делать с процессингом? Мы сделали его по кластерам. Кластер —логическая единица, которая состоит из Redis, из базы данных и из никого набора сервиса, которые отвечают за процессинг. Кластеры, объединенные в физические структуры, состоящие из Redis, Instance, Postgres, Instance Application мы стали называть галактиками.

Соответственно, react PHP знал в какой галактике, в каком кластере лежит бот и и лежит ??? 21:10 бота, и получающая ??? 21:13, раскладывал это по очередям.

Слайд

Мы начали масштабировать наши галактики. Вселенная расширяется, по мере расширения нашей системы мы добавляли новую галактику (и продолжаем это делать до сих пор).

Слайд

Прошло еще какое-то время. У нас 650 000 ботов, 200 млн подписчиков, 3 млрд сообщений отправляется в месяц. Размеры ??? 22:00 сервиса очень тяжело мерить и понимать их масштаб, но попробуйте себе представить сайт с 200 млн пользователей.

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

Слайд

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

После мы вспомнили, что в Nginx появились модули. Наше внимание привлекла библиотека, которая называется Open Resty. У нее оказалась поддержка как у языка программирования, так и Redis.

Оптимизация нашей системы дошла до то, что единственную задачу, которую выполнял react PHP — задача складывания данных в Redis.

Слайд

Мы снова сделали тест. То, что делал скрипт react PHP, мы заменили конфигом Nginx. 30 сервисов на react PHP заменил 1 конфиг Nginx. Он выполняет всю логику — там происходит получение тела запроса, парсинг и складывание в Redis. По сравнению с тем, что делали сервисы react PHP ,прирост был, примерно, в 10 раз.

Слайд

Это позволило держать нагрузку. Мы смогли отмасштабировать и входящие обработчики вебхука. Так выглядела наша система в тот момент когда мы применили Nginx.

Что общего у нас приходило? Nginx складывал входящие вебхуки в Redis, происходила первичная обработка распределение вебхуков по типам систем, и процессинг направлялся в одну ветку, а отложенные операции — в другую.

Слайд

Следующая, о которой я хочу рассказать — это последний этапы эволюции нашей системы. Мы достигли показателей в 7 миллиардов сообщения месяц, 25 000 запросов в секунду. Мы поняли, что LUA и Nginx круто справляются, и начали переносить часть логики процессинга в LUA. Каждый перенос кусочков процессинга давал существенный прирост производительности, уменьшал связанность компонентов системы и позволял отдельно мониторить и обрабатывать.

Чтобы понимать примерный масштаб, вся система состоит из 50 амазонских инстансов разных типов: Nginx, Redis, Postgres.

Слайд

Вот такая история. Она продолжается. Примерно раз в месяц мы растем на 10%, поэтому если я буду ещё раз рассказывать это доклад, там будут ещё несколько слайдов «Как выглядит наша система сейчас».

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

От Nginx + PHP-FPM с небольшой нагрузкой, мы дошли к очередям на Postgres, а после к очередям на Redis. Попробовали react PHP, отказались от оказались react PHP, перешли на LUA и сейчас пишем некоторую логику на LUA.

На нашем примере мы показываем, что делать highload сейчас можно на маленьком стэке и даже на PHP.

Слайд

Спасибо.