Комментарии 37
Кажется, вы натянули сову на глобус ради статьи на трендовую тему.
Несколько лет подряд были проблемы с базой и с алгоритмами, вы их наконец исправили, но почему-то пишете, что именно переход на Go решил все проблемы и позволил уменьшить количество инстансов.
Ни разу не спорю, что Go крут для тяжёлых вычислений под нагрузкой, но… Насколько я понял, микросервис представляет собой простой API с несколькими запросами в СУБД и скромной математикой. Так что тяжёлых вычислений там просто нет…
Несколько лет подряд были проблемы с базой и с алгоритмами, вы их наконец исправили, но почему-то пишете, что именно переход на Go решил все проблемы и позволил уменьшить количество инстансов.
Ни разу не спорю, что Go крут для тяжёлых вычислений под нагрузкой, но… Насколько я понял, микросервис представляет собой простой API с несколькими запросами в СУБД и скромной математикой. Так что тяжёлых вычислений там просто нет…
+6
Согласен, цепочка решений может показаться сумбурной, но это оттого, что описанные кейсы — выхваченные факты из эволюции сервиса.
Тяжелых вычислений в сервисе нет, но кое-что считать ему все же приходится. Но я не могу назвать это проблемной частью, мы и в питоне на математику не жаловались. Переход на Go был скорее шансом еще раз пересмотреть подходы и алгоритмы, что естественно принесло пользу. Кроме того, уменьшить количество инстансов — тоже ценный результат.
Тяжелых вычислений в сервисе нет, но кое-что считать ему все же приходится. Но я не могу назвать это проблемной частью, мы и в питоне на математику не жаловались. Переход на Go был скорее шансом еще раз пересмотреть подходы и алгоритмы, что естественно принесло пользу. Кроме того, уменьшить количество инстансов — тоже ценный результат.
0
Ну так, судя по тексту статьи, вы сравниваете количество инстансов на Go с прошлогодней «чёрной пятницей», когда из-за бесконечной рекурсии вы закидывали проблему железом. Что мягко говоря некорректно.
0
Расскажите пожалуйста по подробнее вот про это место: «перешли с MySQL на PostgreSQL, что вместе с оптимизацией кода дало сокращение количества обращений к базе с 28 до 4 — 5».
Какие преимущества PostgreSQL вы применили?
Какие преимущества PostgreSQL вы применили?
+1
В этом году в самый пик распродажи работало 16 экземпляров приложения, которые обрабатывали в среднем 300 запросов в секунду с пиками до 400 запросов в секунду, что примерно в два раза выше обычной нагрузки. Отмечу, что в прошлом году сервису на Python потребовалось 102 экземпляра.
У вас python приложение было написано на каком-то django или flask, верно?
0
все ждал увидеть примеры кода, или скл-запросы… «было-стало»…
… ато получается:
у нас был проект на питоне+мускл, и тормозил, переписали часть на го+постгрес — и больше не тормозит… пишите на го :)
… ато получается:
у нас был проект на питоне+мускл, и тормозил, переписали часть на го+постгрес — и больше не тормозит… пишите на го :)
+5
Расскажите, какие Go-шные либы используете в проекте
0
Да, с удовольствием:
Кроме того, у нас есть внутренние либы, которые мы переиспользуем от проекта к проекту: автогенерация структур и каркаса сервера по swagger-спецификации и фреймворк для функционального тестирования — уверен, что расскажем про них подробнее в отдельных статьях.
- go.uber.org/zap — логируем через него во всех наших гошных проектах
- github.com/getsentry/raven-go — логирование в Sentry
- github.com/kelseyhightower/envconfig — так как вся конфигурация приложения делается через переменные среды, то очень полезная либа, чтобы поддерживать код чистым
- github.com/shopspring/decimal — для хранения цен и операций над ними
- github.com/prometheus/client_golang — экспорт метрик для prometheus
- github.com/patrickmn/go-cache — немного in-memory кэша
Кроме того, у нас есть внутренние либы, которые мы переиспользуем от проекта к проекту: автогенерация структур и каркаса сервера по swagger-спецификации и фреймворк для функционального тестирования — уверен, что расскажем про них подробнее в отдельных статьях.
+1
А как насчёт http сервера? Вроде как выше писалось, что Go-шное приложение вместо джанги воткнули.
0
А, я думал, вы спросили про сторонние либы. Так да, используем штатный HTTP-сервер.
0
Скажите вы использовали, какие-нибудь балансировщики нагрузки для базы данных, например pgbouncer, pgpool
0
Да, мы используем pgbouncer в режиме пулинга транзакций. Кстати, если вы тоже, и используете lib/pq для подключения к базе из Go, то обратите внимание на параметр binary_parameters=yes, его использование — один из способов избежать проблем с баунсером в lib/pq.
0
А каких проблем, расскажите пожалуйста?
0
НЛО прилетело и опубликовало эту надпись здесь
Если в двух словах, то lib/pq неявно делает prepared statements, которые не поддерживаются pgbouncer в режиме пулинга транзакций.
Случается это, если делать вызов с параметрами, например, такой:
Что мы увидим в логе БД?
Использование 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, как показывает наша практика и отзывы других людей, всегда коммутирует всю цепочку вызовов в одно и то же соединение.
Случается это, если делать вызов с параметрами, например, такой:
_, 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, как показывает наша практика и отзывы других людей, всегда коммутирует всю цепочку вызовов в одно и то же соединение.
0
Скажите пожалуйсто сколько экземпляров БД использовалось, и как распределялась нагрузка?
0
Если коротко, то всего один.
Если развернуть, то с самого начала мы не закладывали для этого сервиса деление запросов на читающие/пишущие, потому что, по нашим прогнозам, нагрузка не планировалась такой, чтобы один экземпляр (мастер) не справился бы.
Но на нагрузочных тестах перед BF мы увидели, что в пики читающих запросов столько, что есть риск негативно повлиять на другие базы, находящиеся в том же кластере. Стал выбор: либо отделять читающие запросы от пишущих и направлять читающий трафик на реплики, либо уменьшать использование базы из приложения. Мы пошли по второму пути, закэшировав некоторые результаты запросов в памяти приложения, сократив QPS примерно вполовину.
Если развернуть, то с самого начала мы не закладывали для этого сервиса деление запросов на читающие/пишущие, потому что, по нашим прогнозам, нагрузка не планировалась такой, чтобы один экземпляр (мастер) не справился бы.
Но на нагрузочных тестах перед BF мы увидели, что в пики читающих запросов столько, что есть риск негативно повлиять на другие базы, находящиеся в том же кластере. Стал выбор: либо отделять читающие запросы от пишущих и направлять читающий трафик на реплики, либо уменьшать использование базы из приложения. Мы пошли по второму пути, закэшировав некоторые результаты запросов в памяти приложения, сократив QPS примерно вполовину.
0
Мы пошли по второму пути, закэшировав некоторые результаты запросов в памяти приложения
почему это не было сделано раньше?
кеширование выборок это первое что приходит в голову, когда бд становится узким местом
0
БД в go-приложении стало узким местом на нагрузочных тестах. Обнаружили, оценили риски, сделали оптимизацию.
0
хм, мне показалось что фраза про 28 запросов на купон относилась к питонячей версии
ps
а насколько много в бд оперативных данных? можно ли все загрузить в рам и в бд лазить только на запись?
ps
а насколько много в бд оперативных данных? можно ли все загрузить в рам и в бд лазить только на запись?
0
А, 28 запросов — это действительно про питон. Увы, я не смогу уверенно сказать, почему тогда приняли такое решение, а не иное.
Да, потенциал для кэширования в этом сервисе довольно большой. Как и писал выше, примерно 50% трафика к базе удалось срезать за счет in-memory-кэша. В базу остались походы за тем, что нельзя кэшировать — например, скидки с ограниченным кол-вом применений, и то, что плохо кэшируется — например, персональные скидки пользователя.
Да, потенциал для кэширования в этом сервисе довольно большой. Как и писал выше, примерно 50% трафика к базе удалось срезать за счет in-memory-кэша. В базу остались походы за тем, что нельзя кэшировать — например, скидки с ограниченным кол-вом применений, и то, что плохо кэшируется — например, персональные скидки пользователя.
0
Тоже не понял, причём тут Go. На Perl можно всё сделать.
+3
А я не понимаю, почему у кого-то возникают сомнения в том, что статический нативный язык потребляет меньше ресурсов, чем динамический интерпретируемый.
0
Согласен, это должно быть интуитивно. Другой вопрос — насколько меньше? На него статья ответа не дает, потому что наш кейс уж очень радикальный: мы переезжали не просто с Python, а с django-приложения, которое еще и с багажом фич, которые появлялись, переставали использоваться, но оставались жить в коде.
0
Скажите го сервис имеет общую базу с питоном? Если нет, то как разделили?
0
Да, go-сервис использует ту же базу, которую ранее использовал Python.
Более того, django мы не выкинули, потому что кроме API для расчета скидок она была еще и развесистой админкой для конфигурирования акций. Роль API перешла от питона к go, а админка осталась на django, и они используют одну и ту же базу.
Если интересно, могу подискутировать на тему плюсов/минусов такого подхода.
Более того, django мы не выкинули, потому что кроме API для расчета скидок она была еще и развесистой админкой для конфигурирования акций. Роль API перешла от питона к go, а админка осталась на django, и они используют одну и ту же базу.
Если интересно, могу подискутировать на тему плюсов/минусов такого подхода.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Как Go спасал нашу «Чёрную пятницу»