- PVSM.RU - https://www.pvsm.ru -

«Сломай голосовалку на РИТ++». Даёшь 1 000 000 RPS

«Сломай голосовалку на РИТ++». Даёшь 1 000 000 RPS - 1

Прошёл второй день РИТ++, и по горячим следам мы хотим рассказать о том, как всем миром пытались сломать нашу голосовалку [1]. Под катом — код, метрики, имена победителей и самых активных участников, и прочие грязные подробности.

Незадолго перед РИТ++ мы задумались, чем можно развлечь народ? Решили сделать голосовалку за самый крутой язык программирования. И чтобы результаты в реальном времени выводились на дашборд. Процедуру голосования сделали простой: можно было с любого устройства зайти на сайт ODN.PW, указать своё имя и e-mail, и проголосовать за какой-нибудь язык. Со списком языков не мудрили — взяли девять самых популярных по данным GitHub, десятым пунктом сделали язык “Other”. Цвета для плашек тоже взяли с GitHub.

«Сломай голосовалку на РИТ++». Даёшь 1 000 000 RPS - 2

Но мы понимали, что накрутки будут неизбежны, причём с применением «тяжёлой артиллерии» — все свои, аудитория продвинутая, это же не голосование на мамочкином форуме. Поэтому мы решили приветствовать накрутки всеми возможными способами. Более того, предложили попробовать сообществу положить нашу голосовалку большой нагрузкой. А чтобы участникам было ещё проще, выложили ссылку на API [2] для накруток с помощью ботов. И заодно решили наградить поощрительными призами первых трёх участников с наибольшим количеством RPS. Отдельная номинация была заготовлена для того силача, который сможет поломать голосовалку в одиночку.

День первый

Голосовалку запустили почти с самого начала работы РИТ++, и работала она до 18 часов. Наше развлечение понравилось посетителям и докладчикам РИТ++. Специалисты по высокопроизводительным сервисам активно включились в гонку за RPS. Гости стенда живо обсуждали способы положить голосовалку. Стихийно возникали команды адептов того или иного языка, которые начинали придумывать стратегии продвижения. Кто-то тут же садился и начинал писать микросервисы или ботов для участия в голосовании.

«Сломай голосовалку на РИТ++». Даёшь 1 000 000 RPS - 3

Некоторые компании, участвующие в РИТ++ и предоставляющие услуги защищённых хостингов, тоже включились в наше соревнование. К самому концу дня совместными усилиями участники всё же смогли ненадолго положить систему. Ну как «положили» — сервис-то работал, просто мы упёрлись в потолок по количеству одновременно регистрируемых голосов. Поэтому к 18 часам мы приостановили голосование, иначе результаты были бы недостоверными.

По результатам первого дня мы получили 160 млн голосов, а пиковая нагрузка достигала 20 000 RPS. Любопытно, что в этот день первое и второе места заняли активный участник РИТ++
Николай Мациевский (Айри) и спикер Елена Граховац из Openprovider.

Ночью мы подготовились к следующему дню, чтобы встретить его во всеоружии: оптимизировали общение с базой и поставили nginx перед Node.js-приложением на каждом воркере.

День второй

Многих заинтересовало наше предложение положить голосовалку, ведь гонка за RPS — задача увлекательная. Утром нас уже «ждали»: едва мы переключили DNS, как количество RPS взлетело до 100 000. И через полчаса нагрузка поднялась до 300 000 RPS.

Забавно, что когда мы только приступали к разработке голосовалки, то решили, что «неплохо было бы поддерживать 100 000 RPS». И на всякий случай заложили максимальную производительность в 1 млн RPS, но при этом даже всерьёз не рассматривали возможность приближения к такому показателю. А к середине второго дня уже практически делали ставки на то, пробьём ли потолок в миллион запросов в секунду. В результате мы достигли порядка 500 000 RPS.

Реализация

Проект мы запилили втроём за 1,5 дня, перед самым РИТ++. Голосовалку разместили в облачном сервисе Google Cloud Platform. Архитектура трёхуровневая:

• Верхний уровень: балансировщик, выступающий в роли фронтенда, на который приходит поток запросов. Он раскидывает нагрузку по серверам.
• Средний уровень: бэкенд на Node.js 8.0. Количество задействованных машин масштабируется в зависимости от текущей нагрузки. Делается это экономно, а не с запасом, чтобы не переплачивать впустую. К слову, проектик обошёлся в 8000 рублей.
• Нижний уровень: кластеризованная MongoDB для хранения голосов, состоящая из трёх серверов (один master и два slave’а).

Все компоненты голосовалки — open source, доступны на Github:

• Backend: https://github.com/spukst3r/counter-store [3]
• Frontend: https://github.com/weglov/treechart [4]

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

Что ж, как показал первый день, прикрутить кеш надо было сразу. Каждый воркер Node.js выдавал не больше 3000 RPS на каждый POST на /poll, а мастер MongoDB тяжело кашлял с LA >100. Не очень помогла даже оптимизация агрегации запросов для получения статистики путём изменения read preference [5] на использование slave'ов для чтения. Ну ничего, самое время реализовать кеш для накрутки счётчиков и для проверки валидности email'а (который был завёрнут в простой _.memoize [5], ведь мы никогда не удаляем пользователей). Также мы задействовали новый проект в Google Compute Engine, с бОльшими квотами.

После включения кеширования голосов MongoDB чувствовала себя превосходно, показывая LA <1 даже в пике загрузки. А производительность каждого воркера выросла на 50% — до 4500 RPS. Для периодической отправки данных мы использовали bulkWrite [6] с отключённым параметром ordered [7], чтобы оставить на стороне базы очередность исполнения запросов для оптимизации скорости.

В первый день на каждом воркере работал Node.js-сервер, создающий через модуль cluster четыре дочерних процесса, каждый из которых слушал порт 3000. Для второго дня мы отказались от такого сервера и отдали обработку HTTP «профессионалам». Опыты показали, что nginx, взаимодействующий с приложением через unix-сокет, даёт примерно +500 RPS. Настройка достаточно стандартная для большого количества соединений: увеличенный worker_rlimit_nofile, достаточный worker_connections, включенный tcp_nopush и tcp_nodelay. Кстати, отключение алгоритма Нейгла [8] помогло поднять RPS и в Node.js. В каждой виртуалке потребовалось увеличить лимит на количество открытых файлов и максимальный размер backlog'а.

Итоги

За два дня ни одному участнику в одиночку не удалось положить наш сервис. Но в конце первого дня общими усилиями добились того, что система не успевала регистрировать все входящие запросы. На второй день мы поставили рекорд в нагрузке ~450 000 RPS. Различие в показаниях RPS на фронте (который высчитывал и усреднял RPS по фактическим записям в базе) и показания мониторинга Google пока остаётся для нас тайной.

«Сломай голосовалку на РИТ++». Даёшь 1 000 000 RPS - 4

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

«Сломай голосовалку на РИТ++». Даёшь 1 000 000 RPS - 5

1 место — { "_id": "ivan@buymov.ru", "count": 2107126721 }
2 место — { "_id": "burik666@gmail.com", "count": 1453014107 }
3 место — { "_id": "256@flant.com", "count": 626160912 }

Для получения призов пишите kosheleva_ingram_micro [9]!

UPD: Зал славы TOP50 [10]

Автор: allexx

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/razrabotka/257255

Ссылки в тексте:

[1] сломать нашу голосовалку: https://habrahabr.ru/company/odin_ingram_micro/blog/330216/

[2] ссылку на API: http://odn.pw/api

[3] https://github.com/spukst3r/counter-store: https://github.com/spukst3r/counter-store

[4] https://github.com/weglov/treechart: https://github.com/weglov/treechart

[5] read preference: https://lodash.com/docs/4.17.4#memoize

[6] bulkWrite: https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/

[7] ordered: https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/#bulkwrite-write-operations-executionofoperations

[8] алгоритма Нейгла: https://en.wikipedia.org/wiki/Nagle's_algorithm

[9] kosheleva_ingram_micro: https://habrahabr.ru/users/kosheleva_ingram_micro/

[10] Зал славы TOP50: http://odn.pw/#/topkek

[11] Источник: https://habrahabr.ru/post/330368/