Как стать автором
Обновить

Комментарии 37

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

Ни разу не спорю, что Go крут для тяжёлых вычислений под нагрузкой, но… Насколько я понял, микросервис представляет собой простой API с несколькими запросами в СУБД и скромной математикой. Так что тяжёлых вычислений там просто нет…
Согласен, цепочка решений может показаться сумбурной, но это оттого, что описанные кейсы — выхваченные факты из эволюции сервиса.

Тяжелых вычислений в сервисе нет, но кое-что считать ему все же приходится. Но я не могу назвать это проблемной частью, мы и в питоне на математику не жаловались. Переход на Go был скорее шансом еще раз пересмотреть подходы и алгоритмы, что естественно принесло пользу. Кроме того, уменьшить количество инстансов — тоже ценный результат.
Ну так, судя по тексту статьи, вы сравниваете количество инстансов на Go с прошлогодней «чёрной пятницей», когда из-за бесконечной рекурсии вы закидывали проблему железом. Что мягко говоря некорректно.
Не, приведенные в статье кол-во инстансов — это из истории git-репозитория с конфигами, выставленные значения перед ЧП по итогам нагрузочных тестов. Сколько там добавляли инстансов конкретно в день ЧП, к сожалению, не сохранилось.
Расскажите пожалуйста по подробнее вот про это место: «перешли с MySQL на PostgreSQL, что вместе с оптимизацией кода дало сокращение количества обращений к базе с 28 до 4 — 5».
Какие преимущества PostgreSQL вы применили?
Основными причинами перехода были стремление к единообразию (почти все сервисы, которым нужна база, у нас используют PG) и наличие экспертизы в поддержке этой базы. В Мадмине, насколько я помню, ничего из фич, присущих только PG, не используется.
В этом году в самый пик распродажи работало 16 экземпляров приложения, которые обрабатывали в среднем 300 запросов в секунду с пиками до 400 запросов в секунду, что примерно в два раза выше обычной нагрузки. Отмечу, что в прошлом году сервису на Python потребовалось 102 экземпляра.

У вас python приложение было написано на каком-то django или flask, верно?

На django, ага.

Т.е. вы сравниваете синхронную джангу с го. Ооокей.

Скорее мы сравниваем варианты решения бизнесовой задачи
все ждал увидеть примеры кода, или скл-запросы… «было-стало»…
… ато получается:
у нас был проект на питоне+мускл, и тормозил, переписали часть на го+постгрес — и больше не тормозит… пишите на го :)
Расскажите, какие Go-шные либы используете в проекте
Да, с удовольствием:


Кроме того, у нас есть внутренние либы, которые мы переиспользуем от проекта к проекту: автогенерация структур и каркаса сервера по swagger-спецификации и фреймворк для функционального тестирования — уверен, что расскажем про них подробнее в отдельных статьях.
А как насчёт http сервера? Вроде как выше писалось, что Go-шное приложение вместо джанги воткнули.
А, я думал, вы спросили про сторонние либы. Так да, используем штатный HTTP-сервер.
Спасибо. Просто, на мой взгляд, штатный http сервер не особо удобен в использовании. Думал, что вы запускаете на чём-то стороннем, наподобие chi или gin.
Скажите вы использовали, какие-нибудь балансировщики нагрузки для базы данных, например pgbouncer, pgpool
Да, мы используем pgbouncer в режиме пулинга транзакций. Кстати, если вы тоже, и используете lib/pq для подключения к базе из Go, то обратите внимание на параметр binary_parameters=yes, его использование — один из способов избежать проблем с баунсером в lib/pq.
А каких проблем, расскажите пожалуйста?
НЛО прилетело и опубликовало эту надпись здесь
Ответил чуть ниже.
Если в двух словах, то lib/pq неявно делает prepared statements, которые не поддерживаются pgbouncer в режиме пулинга транзакций.

Случается это, если делать вызов с параметрами, например, такой:

_, err := db.Exec("SELECT $1::int + $2::int", 1, 2)

Что мы увидим в логе БД?

LOG: duration: 0.402 ms parse <unnamed>: SELECT $1::int + $2::int
LOG: duration: 0.123 ms bind <unnamed>: SELECT $1::int + $2::int
LOG: execute <unnamed>: SELECT $1::int + $2::int

Использование prepared statements — это нормально, говорит контрибьютор, это способ lib/pq подставить значение вместо $-плейсхолдеров.

Что же делает binary_parameters, и почему он помогает для работы через pgbouncer?

Без binary_parameters=yes lib/pq сначала создает prepared statements, затем дожидается ответа от базы, чтобы узнать типы, в которые нужно скастовать параметры, и только потом отправляет их значения вместе с командой выполнить запрос. Такой двойной поход по сети не только делает само исполнение запроса дольше, но и оставляет шанс того, что pgbouncer выполнит PREPARE и EXECUTE в разных сессиях и мы получим что-нибудь вроде ERROR: prepared statements does not exist.

Со включенным binary_parameters=yes, lib/pq использует возможность PG-протокола передавать параметры в «бинарном» виде, при котором ему больше не нужно заранее узнавать их тип. Поэтому пропадает нужда делать два похода в базу, и pgbouncer, как показывает наша практика и отзывы других людей, всегда коммутирует всю цепочку вызовов в одно и то же соединение.
Скажите пожалуйсто сколько экземпляров БД использовалось, и как распределялась нагрузка?
Если коротко, то всего один.

Если развернуть, то с самого начала мы не закладывали для этого сервиса деление запросов на читающие/пишущие, потому что, по нашим прогнозам, нагрузка не планировалась такой, чтобы один экземпляр (мастер) не справился бы.

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

почему это не было сделано раньше?
кеширование выборок это первое что приходит в голову, когда бд становится узким местом
БД в go-приложении стало узким местом на нагрузочных тестах. Обнаружили, оценили риски, сделали оптимизацию.
хм, мне показалось что фраза про 28 запросов на купон относилась к питонячей версии

ps
а насколько много в бд оперативных данных? можно ли все загрузить в рам и в бд лазить только на запись?
А, 28 запросов — это действительно про питон. Увы, я не смогу уверенно сказать, почему тогда приняли такое решение, а не иное.

Да, потенциал для кэширования в этом сервисе довольно большой. Как и писал выше, примерно 50% трафика к базе удалось срезать за счет in-memory-кэша. В базу остались походы за тем, что нельзя кэшировать — например, скидки с ограниченным кол-вом применений, и то, что плохо кэшируется — например, персональные скидки пользователя.
А я не понимаю, почему у кого-то возникают сомнения в том, что статический нативный язык потребляет меньше ресурсов, чем динамический интерпретируемый.
Согласен, это должно быть интуитивно. Другой вопрос — насколько меньше? На него статья ответа не дает, потому что наш кейс уж очень радикальный: мы переезжали не просто с Python, а с django-приложения, которое еще и с багажом фич, которые появлялись, переставали использоваться, но оставались жить в коде.
Ну вообще вот ребята соревнуются — тык. Пока что решение на Java в 2 раза быстрее чем на Go. Почему в сторону Java/C# не смотрели?
Ну и еще, ну тут чистая синтетика — тык
там ребята соревнуются не сколько в языках, сколько в алгоритмах
Присоединяюсь к вопросу, почему не Java. Сейчас уже можно и в native через Graalvm.
У меня скучный, но правдивый ответ: потому что мы умеем Go :)
Скажите го сервис имеет общую базу с питоном? Если нет, то как разделили?
Да, go-сервис использует ту же базу, которую ранее использовал Python.

Более того, django мы не выкинули, потому что кроме API для расчета скидок она была еще и развесистой админкой для конфигурирования акций. Роль API перешла от питона к go, а админка осталась на django, и они используют одну и ту же базу.

Если интересно, могу подискутировать на тему плюсов/минусов такого подхода.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий