В Badoo я работаю в команде, которая разрабатывает на PHP. Одна из фич, которой мы занимаемся, со временем начала отъедать всё больше и больше железячных ресурсов. В итоге мы едва успевали добавлять серверы под растущую нагрузку. При этом вечера, проведённые с Go дома, подсказывали, что можно сделать на порядки производительнее, не затратив на разработку много времени.
Я расскажу о том, почему наша фича так плохо ложится на PHP и хорошо – на Go, как уговорить всех всё переписать и не показаться сумасшедшим, ну и, конечно же, как из 19 серверов оставить только 4.
7. ✦ 4 больших отдела: Features, Billing, Back Office, Platform
✦ В каждом по ~10-25 человек (разделены на команды)
✦ Вопроса о выборе языка не встаёт (есть куча других
важных проблем!)
PHP в Badoo
30. Реализация #1
✦ Заранее скачиваем с FB друзей текущего пользователя
✦ Заранее скачиваем с FB друзей просматриваемого
пользователя
A X Y BZ Y
31. Реализация #1
✦ Заранее скачиваем с FB друзей текущего пользователя
✦ Заранее скачиваем с FB друзей просматриваемого
пользователя
✦ Пересекаем списки в момент показа
A X Y BZ Y
32. Реализация #1
✦ Заранее скачиваем с FB друзей текущего пользователя
✦ Заранее скачиваем с FB друзей просматриваемого
пользователя
✦ Пересекаем списки в момент показа
✦ PROFIT!!!!
A X Y BZ Y
34. Апдейт FB API v2.0
Было
Friends endpoint
* Зарегистрированные (дали доступ Badoo на FB)
* Незарегистрированные
35. Апдейт FB API v2.0
Было Стало
Friends endpoint
* Зарегистрированные
* Незарегистрированные
Friends endpoint
* Только зарегистрированные
36. Апдейт FB API v2.0
Было Стало
Friends endpoint
* Зарегистрированные
* Незарегистрированные
All Mutual Friends endpoint
* Зарегистрированные
* Незарегистрированные
* Нужно 2 fb_id
Friends endpoint
* Только зарегистрированные
37. Реализация #1 — крах
✦ Предварительно скачать всех друзей мы не можем
✦ => Надо получать общих друзей в онлайне
39. Реализация #2
✦ Предварительно скачиваем списки зарегистрированных
друзей
✦ В клиентском запросе на профайл делаем HTTP-запрос в
Facebook за общими друзьями
44. Реализация #2
✦ Предварительно скачиваем списки зарегистрированных
друзей
✦ В клиентском запросе на профайл делаем HTTP-запрос в
Facebook за общими друзьями
Мы не можем ждать 1с на отрисовку профиля!
45. Реализация #3
✦ Предварительно скачиваем списки зарегистрированных
друзей
✦ В клиентском запросе на профайл отдельном асинхронном
запросе делаем HTTP-запрос в Facebook за общими
друзьями
59. ✦ CPU-bound нагрузка (обычно)
✦ Память
✦ Context Switch’es
✦ Непредсказуемость времени ответа от Facebook (нужно
“перезакладываться” в десятки раз)
Воркеры — ограничения
61. ✦ Предварительно скачиваем списки зарегистрированных
друзей
✦ В клиентском запросе на профайл отдельном запросе
делаем HTTP-запрос в Facebook за общими друзьями
Мы не можем себе позволить долгие запросы!
(вообще)
Реализация #3
65. Реализация #4
✦ В коротком клиентском запросе запускаем скрипт в PHProxyd
66. Реализация #4
✦ В коротком клиентском запросе запускаем скрипт в PHProxyd
✦ Клиент перепроверяет (поллит) результаты
67. Реализация #4
✦ В коротком клиентском запросе запускаем скрипт в PHProxyd
✦ Клиент перепроверяет (поллит) результаты
✦ В скрипте в PHProxyd делаем HTTP-запрос к FB + небольшая
бизнес логика
68. Реализация #4
✦ В коротком клиентском запросе запускаем скрипт в PHProxyd
✦ Клиент перепроверяет (поллит) результаты
✦ В скрипте в PHProxyd делаем HTTP-запрос к FB + небольшая
бизнес логика
Теперь точно PROFIT!!!!!!!
86. PHProxyd — замена
✦ Создание воркера на новый запрос (phproxyd)
✦ Преподготовить заранее кучу воркеров (php-fpm)
87. PHProxyd — замена
✦ Создание воркера на новый запрос (phproxyd)
✦ Преподготовить заранее кучу воркеров (php-fpm)
Не работает
88. PHProxyd — замена
✦ Создание воркера на новый запрос (phproxyd)
✦ Преподготовить заранее кучу воркеров (php-fpm)
✦ Что-то асинхронное (nodejs, React PHP)
89. PHProxyd — замена
✦ Создание воркера на новый запрос (phproxyd)
✦ Преподготовить заранее кучу воркеров (php-fpm)
✦ Что-то асинхронное (nodejs, React PHP)
Мы любим PHP…
90. PHProxyd — замена
✦ Создание воркера на новый запрос (phproxyd)
✦ Преподготовить заранее кучу воркеров (php-fpm)
✦ Что-то асинхронное (nodejs, React PHP)
… но НЕТ!
Мы любим PHP…
91. PHProxyd — замена
✦ Создание воркера на новый запрос (phproxyd)
✦ Преподготовить заранее кучу воркеров (php-fpm)
✦ Что-то асинхронное (nodejs, React PHP)
✦ Go = пишешь “синхронный” код, работает асинхронно
92. PHProxyd — замена
✦ Создание воркера на новый запрос (phproxyd)
✦ Преподготовить заранее кучу воркеров (php-fpm)
✦ Что-то асинхронное (nodejs, React PHP)
✦ Go = пишешь “синхронный” код, работает асинхронно
+ “сложились звёзды”
94. ✦ Предварительно скачиваем списки зарегистрированных друзей
✦ В коротком клиентском запросе запускаем скрипт в PHProxyd
✦ Клиент перепроверяет (поллит) результаты
✦ В скрипте в PHProxyd делаем HTTP-запрос к FB + небольшая
бизнес логика
Реализация #4 (предыдущая)
95. ✦ Предварительно скачиваем списки зарегистрированных друзей
✦ В коротком клиентском запросе запускаем скрипт в PHProxyd
✦ Клиент перепроверяет (поллит) результаты
✦ В скрипте в PHProxyd делаем HTTP-запрос к FB + небольшая
бизнес логика (походы в MySQL, memcache итд)
Реализация #4 (предыдущая)
96. Реализация #5 — отличия
✦ Принимает на вход “образ” http/https запроса
97. Реализация #5 — отличия
✦ Принимает на вход “образ” http/https запроса
✦ Отвечает “in progress”, начинает исполнять запрос асинхронно
98. Реализация #5 — отличия
✦ Принимает на вход “образ” http/https запроса
✦ Отвечает “in progress”, начинает исполнять запрос асинхронно
✦ На повторные реквесты отвечает “in progress” либо данными
99. Реализация #5 — отличия
✦ Принимает на вход “образ” http/https запроса
✦ Отвечает “in progress”, начинает исполнять запрос асинхронно
✦ На повторные реквесты отвечает “in progress” либо данными
✦ Всю остальную бизнес-логику (базы, мемкеши) оставляем снаружи
104. Реализация #5
✦ Глобальный map с заданиями и результатами
✦ HTTP-сервер, принимает “json” образ запроса, шлёт его в канал
105. Реализация #5
✦ Глобальный map с заданиями и результатами
✦ HTTP-сервер, принимает “json” образ запроса, шлёт его в канал
✦ N горутин читают из канала, шлют запрос в FB
106. Реализация #5
✦ Глобальный map с заданиями и результатами
✦ HTTP-сервер, принимает “json” образ запроса, шлёт его в канал
✦ N горутин читают из канала, шлют запрос в FB
✦ После получения результата пишут его в глобальный map
107. Реализация #5
✦ Глобальный map с заданиями и результатами
✦ HTTP-сервер, принимает “json” образ запроса, шлёт его в канал
✦ N горутин читают из канала, шлют запрос в FB
✦ После получения результата пишут его в глобальный map
✦ Повторный запрос отдаёт результат из этого map
108. Реализация #5
✦ Глобальный map с заданиями и результатами
✦ HTTP-сервер, принимает “json” образ запроса, шлёт его в канал
✦ N горутин читают из канала, шлют запрос в FB
✦ После получения результата пишут его в глобальный map
✦ Повторный запрос отдаёт результат из этого map
Дублируем продакшен-трафик!!!