Однажды днем у нас обрушился сайт. Сразу после ребута он падал снова. Мы знали, что это не DDOS, а органический трафик: к нам поступали типичные запросы, но сервера не справлялись. Увеличение мощности железа не помогало. Стало ясно, что пора оптимизировать нашу систему.
Молодым стартапам может быть интересно, как справляться с возросшими нагрузками на еще неокрепшее серверное ПО.
Кабинет Дримкас — облачный сервис для владельца кассы
В прошлой публикации я рассказал, как Кабинет Дримкас использует вебхуки. В этой статье я остановлюсь на том, как появился Кабинет и какие проблемы нам пришлось решать по мере развития сервиса.
Дримкас производит онлайн-кассы. В 2016 году приняли новую версию закона о контрольно-кассовой технике. Одно из главных нововведений в том, что каждая касса должна отправлять данные о продажах в налоговую в реальном времени. Для нас это значило, что теперь все кассовые аппараты должны быть подключены к интернету, чтобы передавать чеки в ФНС.
Раз наши кассы все равно шлют данные о продажах, кажется логичным собрать эти данные в облаке, чтобы владелец всегда имел к ним удаленный доступ. Так и появился Кабинет Дримкас.
Как был устроен Кабинет вначале
Приступили к реализации. Первую идею — создать один логический сервер, к которому напрямую будут обращаться все кассы — разработчики отвергли.
Сложность в том, что у нас в рынке несколько моделей касс на Linux, одна на Windows 10 и фискальные регистраторы вообще без операционной системы. В планах были и другие устройства, но тогда еще никто не знал, на чем будут работать они. Это означало, что нужно поддерживать разные версии протоколов и форматов данных — разработка новых фич чрезмерно усложнялась.
Решили создать промежуточный узел — «Хаб», который будет поддерживать разные версии, модели касс, транспорты общения и даже кодировки, если понадобится. Сайт — «Кабинет Дримкас» — получит нормализованные данные и обобщенный протокол общения со всеми устройствами.
Хаб нормализует данные от разных моделей касс, а Кабинет общается по API
Чтобы проверить гипотезу — нужен ли вообще такой сервис пользователям? — запустили первую версию. Получили обратную связь — у нас просили отчеты, выгрузку данных в Эксель, работу с товарами, открытый API для интеграторов. Проект оказался востребован, мы выпускали новые фичи.
Хаб с Кабинетом общался стандартными HTTPS-запросами с таймаутом 5 секунд:
- Кассы отправляли на Хаб информацию об открытии/закрытии смены и новых чеках. Раз в минуту они проверяли, есть ли для них новые задания.
- Хаб получал задания от касс, хранил их у себя и раз в 10 секунд пачкой по 100 заданий засылал в Кабинет. Получал задания от Кабинета и отправлял их кассе, когда она в очередной раз к нему постучится.
- Кабинет принимал и обрабатывал чеки, смены и отправлял на кассы изменения старых товаров и создание новых.
Если Кабинет по какой-либо причине не смог принять хотя бы один чек из пачки, Хаб предполагал, что не принята ни одна задача, и засылал всю пачку целиком ещё раз, пока Кабинет не отчитается об успешном принятии всей пачки.
Чеки — самые тяжелые из всех видов заданий. Нужно пробежаться по всем позициям, создать новые товары в Postgres, а затем положить весь чек в MongoDB. Почему чеки решили хранить в монге — отдельный вопрос, есть свои плюсы и минусы. Особенно это актуально сейчас, когда данных стало очень много и нужно делать сложные выборки с агрегациями.
Так система работала, пока к Кабинету было подключено около двух тысяч касс, а доля чеков среди остальных заданий была не больше 15%.
Когда пользователей стало больше, мы перестали справляться с нагрузками
Интересно, как на нас отражался нестабильный интернет наших пользователей. Когда в магазинах пропадает интернет, касса продолжает продавать, собирая чеки. Как только интернет появляется — отправляет все данные сразу и в ОФД, и в Хаб. Нагрузка на наши сервера подскакивала. С ростом количества касс подобный график нагрузки стал настоящей угрозой.
Мы стали замечать подлагивания днем, которые со временем усиливались. Потом сервер просто упал и не мог подняться, потому что его сразу заваливало новыми заданиями.
У Хаба не было проблем — он складывал все данные в БД без тяжелой логики и отправлял дальше. Вся беда заключалась в том, что пачка задач с большой долей чеков могут обрабатываться дольше таймаута запроса на nginx. Это приводило к 500 timeout от Кабинета, и Хаб сразу же пытался скормить те же данные повторно, хотя Кабинет всё ещё обрабатывал предыдущие. В итоге Хаб просто начинал DOS Кабинета, пока последний не упадет.
Увеличим таймаут на nginx или уменьшим количество отправляемых за раз заданий — получим небольшую отсрочку и через месяц вернемся к той же проблеме, но масштаб последствий вырастет. Подключенных касс станет больше, мы подведем больше людей.
Чтобы не возникало давки, мы организовали очередь
Если смотреть на усредненную статистику — мощности железа хватало. Самих данных было немного, и в течение дня за пиковыми нагрузками следовал спад. Нужно было сгладить нагрузку, чтобы не падать во время пиков.
Решение — очередь заданий, куда можно складывать задания с какими угодно интервалами и скачками, чтобы обрабатывать их по мере возможности. Так как проблема была на стороне Кабинета, решили это прикрутить внутри него. В качестве очереди выбрали rabbitMQ. Все задания от Хаба по одному попадали туда и воркеры их обрабатывали.
Идея оказалось настолько удачной, что Хаб захотел сделать то же самое у себя, ведь мы тоже иногда могли выслать разом сто тысяч заданий. Так мы решили использовать rabbitMQ в качестве транспорта между Хабом и Кабинетом.
Если раньше при сохранении товара на сайте Хаб лежал, то мы выкидывали ошибку «Повторите позже». Сейчас данные сохранятся в БД. Задача кладется в очередь намного быстрее, потому что мы не дожидаемся подтверждения от Хаба, и он сам ее забирает по мере возможности. Если вдруг Кабинет не закончил обработку задачи, а связь с воркером прервалась, то эта задача снова появится в очереди. Всё это Кролик делает из коробки, никаких обработок ошибок на нашей стороне не требуется.
Увидев, как это круто работает, решили узнать верхнюю границу прочности транспорта и провели нагрузочное тестирование. Офисная сеть сопротивлялась, но все-таки упала. А Кролик все задачи просто накопил у себя и ждал, пока воркеры их обработают. Отсюда вывод — возможностей кролика нам хватает с избытком, главное дать ему немного SSD и побольше RAM.
Вывод
Нельзя считать, что первоначальное общение через пачки заданий было неверным. На ранних этапах разработки основной ресурс — время. Если бы мы задумывались о слишком далекой перспективе и оптимизировали все наперед, то до релиза могло бы и не дойти.
Таким образом, главное — вовремя понять, что ваш сервис перерос старые решения, и для стабильной работы и последующего роста нужно притормозить развитие новых фич и пересмотреть архитектуру проекта.
Автор: ilnuribat