Pull to refresh

Comments 68

Ура! Надеюсь, что все кто пришел за “blazingly fast” уйдут наконец писать на Go и пена немного сойдёт.

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

Только почему-то в реальной жизни это работает в обратную сторону, и компании после переноса своего прода с Go на Rust наблюдают 60% прироста в скорости (Cloudflare, Discord и прочие, и это только те, кто сказал об этом публично и был достаточно популярен, чтобы это заметили).

В целом тут не очень оптимально бенч для раста сделан, как минимум один sqlx чего стоит

Мб на досуге покручу код, если время будет

компании после переноса своего прода с Go на Rust наблюдают 60% прироста в скорости

Уже есть ряд решений, которые могут решить одну из причин, а именно:

Ну и нужно знать про различные хаки, как например: _ = some[n], где n - последний элемент слайса.

про различные хаки

как минимум хотелось бы выводить такое из разряда "хаков" в идиоматический код.

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

Это все пробуется до того, как решают перейти на другой язык. Те же Cloudflare писали все свое, выжимали максимум из языка, но все равно голенга было недостаточно. А после перехода на раст это решилось, причем не только по чистым rps, но и по latency

И это только один пример. Дискорд тоже сказали, что у них самая наивная реализация на расте оказалась быстрее супероптимизированной гошной

Дропбокс сказали, что переход был «одним из лучших принятых ими решений»

В общем, реальные кейсы реальных продуктов показывают вполне четкую картину

Upd: опечатка в цитате

Прирост получается часто из-за того что GC больше не беспокоит исполнение. На такой нагрузке они наверняка и не почуяли бы memory pressure. Поднять RPS до 10к+ и посмотреть где начнётся просадка. Опять же местные тесты никак не рисовали, что там с памятью происходит и сколько каждый инстанс кушает памяти. На 100 соединениях зелёные треды вполне ожидаемо выигрывают более тяжеловестным ржавым.

Запустил с параметрами -c 300 -n 100000

Go: 10489 Req/s

Rust: 4327 Req/s

В чем то у Rust явно проблема, возможно в sqlx. Переделаю на diesel и проверю еще раз.

Какой-нибудь flamefraph натравить и посмотреть на что тратится раст.

В проде всё совсем иначе. С Go переходят на Rust. Да и Go - это упор на Web. Rust - мультипродуктовый язык. Но главная особенность - это замена C++ в критических местах.

Ещё ниша у Rust это замена C где пишут либы для более высокоуровневых языков (PHP, Python например).

Результат получается по скорости такой же, но код стабильнее и безопаснее.

Интересно, почему среднее и медиана на Go меньше, чем на расте, но 95-й перцентиль намного больше, чем на Расте? Это как так?

Возможно, в 95 перцентиль попала сборка мусора

Хм. Судя по коду у Rust первые 10 запросоа идут в пустой пул коннектов и ждёт подключения. А в коде на Go первый запрос пойдет в БД с уже подготовленным коннектор.

Вполне возможно это и даёт выигрыш. Создание коннекта - медленная операция.

Тогда получается, что надо вначале прогреть сервис, а потом его уже измерять? В целом, это же звучит логично. Плюс, наверное, надо мерить скорость на Linux машинах. Там же обычно весь бекенд, которому скорость нужна, крутится.

Если этот так, то можно как то в Rust подготовить заранее подключения не выполняя запросов?

https://docs.rs/sqlx/latest/sqlx/pool/struct.PoolOptions.html#method.min_connections

Скорее всего не в этом дело. Я запускал сервис и последовательно выполнял по 3 замера с 10,50 и 100 соединениями не перезапуская сервис (9 замеров). Таким образом пул бы "прогрелся" уже после первого замера

Тогда, возможно, стоит посмотреть в сторону используемых крейтов. Как пример: sqlx in some cases more than 60 times slower than Diesel

Также посмотреть сколько суммарно логов генерируется. Они тоже хорошо так на производительность влияют.

Проверка скорости работы по умолчанию настроенных библиотек

Но ведь "настройка" работает для обеих сторон. Т.е. если "донастроить" например библиотеки Go, то он получит еще большее преимущество?

Может да, может нет, если дефолтные настройки удачнее выбраны.

Библиотека https://github.com/lib/pq для Go, не рекомендуема для использования, попробуйте использовать https://github.com/jackc/pgx (причем именно pgxpool)

Hidden text

For users that require new features or reliable resolution of reported bugs, we recommend using pgx which is under active development.

После замены lib/pq на pgxpool показатели увеличились на 52% до Req/s: 9193.11

Сорян, но это сравнение теплого с мягким. Так как языки кардинально отличаются по своим целям.

Если у вас высокая нагрузка и вам нужно её держать и есть ограничения по памяти и времени отклика, ничего лучше С++ и Rust нет.

Если для вас не так важна память (а она сейчас очень дешёвая) и не критичны задержки, то выбирайте Java, C#, Python.

А если нужен вспомогательный сервис, с минимум бизнес логики и достаточно высокими I/O нагрузками и у вас нет под рукой разработчиков на Rust или С++, то тут может подойти Go.

P.S Бенчмарки обычно не гоняются на ПК

Но на Go пишутся не вспомогательные сервисы, а высоконагруженные сложные системы. Например Kubernetes, prometheus, вроде Grafana.

но это же вспомогательные обслуживающие сервисы и там по факту ноль бизнес логики с точки зрения нормального бизнеса, так что человек прав)

Инструменты пишутся на разных языках, это далеко не всегда означает, что эти языки рационально было использовать в этих проектах.

С точки зрения бизнеса, нанять профессиональную команду разработчиков на Go стоит кратно дороже (по деньгам и времени), чем аналогичную команду на python, java и т.д...А профит от команды гоферов не перевешивает затраты на них, сорян

Понятно, что бизнес диктует на чем писать и какой стек использовать. Но рынок Go разработчиков растет, а вот c Rust пока все плохо. Но если писать с нуля, в стартапе например, то выбирают обычно последние инструменты и технологии и в частности часто выбирают между Go и Rust. Java требует очень много памяти, python медленный и т.п. И может лучше вложиться немного в разработчиков, чем потом годами переплачивать за железо и бороться с тормозами.

"то выбирают обычно последние инструменты и технологии"

Технология не должна быть последней или модной, она должна решать свою задачу с приемлемой для бизнеса степенью эффективности и обеспечивать низкую стоимость поддержки)

"Java требует очень много памяти, python медленный и т.п."

1) Память дешёвая, а труд разработчиков дорогой . Java native image и виртуальные потоки если что помогут

2) Python не медленный, на нём спокойно можно писать нагруженные приложения , благодаря своей гибкости, это язык прекрасно работает с нативными расширениями.

"лучше вложиться немного в разработчиков, чем потом годами переплачивать за железо и бороться с тормозами"

А может этим будут заниматься сами разработчики?Курсы ? я выше писал, что нанять квалифицированных прогеров, это сложная задача. А нанять, прогеров на "новых" языках это задача ещё сложнее.

Поэтому "новые" языки, должны обеспечивать такие преимущества, чтобы эту задачку со звездочкой выгодно было решать...К сожалению, преимущества весьма слабые.

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

Вы кажется слабо понимается, что такое "высоконагруженные".

Ну я думаю у Grafana Cloud и нагрузка большая. Или "высоконагруженные" это только Google да Яндекс?

all you have to do to begin creating dashboards and querying metrics data is to configure data sources

Это работает с большими данными, но не обязано делать это в реалтайме. Если вы не делаете какой-нибудь тикеры крипты поверх графаны или если у вас в неё не смотрят несколько тысяч человек единовременно (зачем?), то врядли это можно назвать "высоконагруженным".

Prometheus как БД возможно относится к высоконагруженным, но я не уверен, что оно способно работать на уровне скажем ClickHouse. Хотя, возможно, я как-то неправильно интерпретирую сферу их применения.

Prometheus работает по pull модели. То есть он сам ходит по сервисам и собирает с них метрики в удобном для себя темпе. Это точно не хайлоад.

И да, как и с графаной, хайлоадом это становится, когда "смотрят несколько тысяч человек единовременно (зачем?)".

Если для вас не так важна память (а она сейчас очень дешёвая)

Скажите это Apple. У них апгрейд памяти стоит как крыло от Боинга.

Сорри за оффтоп. Просто наболело :)

Кажется у них всё стоит как сам боинг ))

Кроме Боинга, он с наценкой.

Непонятен смысл бенчмарков. Обычно бенчмарк должен дать ответ-сравнение нескольких альтернативных технологий. Чтобы потом можно было из них выбрать.

Здесь же сравнивается одна конкретная программа с другой конкретной программой. Нельзя вычленить из бенчмарков роль сетевой задержки, роль библиотеки обработки json или драйвера базы данных и т.д. Таким образом нельзя ничего определённого сказать.

Ещё непонятно, использовалась ли одна и та же база данных, или запуск постгреса и генерация данных производились заново.

Ну и результаты бенчмарков скриншотиками - фу.

Upd: вполне может быть что сравнивались не программы а prepared vs non-prepared statements. Я бы переделал на явные prepared statement которые инициализируются один раз.

Я бы предложил наоборот - non-prepared statement. Поскольку они привязываются к соединению, а в случае пула соединений не факт, что следующий запрос будет использовать это же prepared statement.

Смысл в том, что вот думает разработчик на чем писать новый проект, выбирает из нескольких вариантов, а для объективного выбора не хватает информации - люди пишут одно, в тестах выходит другое. И вот чем больше будет разных сравнений, тем лучше.

Результаты скриншотами мне показались красивее, чем просто текст с терминала копировать. Или вы про то, что нужно графики сделать?

Данные бенчмарки сравнивают ваши две конкретные программы целиком, а не Rust vs Go или sqlx::postgres vs github.com/lib/pq - чем это поможет разработчику?

Текстовая информация с картинки не копируется, не индексируется поисковиком.

Языки сравниваются через программы на них написанные. В данной статье сравниваются стеки Fiber-lib/pq vs Axum-sqlx как одни из самых популярных библиотек для разработки web-сервисов. Возможно кому то это сравние даст дополнительную пищу для размышлений и сделает перевес в ту или иную сторону.

Пока что я вижу сравнение уровня одного и того же программиста (no offence, мой уровень наверняка хуже) в разных языках/фреймворках.

а для объективного выбора не хватает информации

Как будто этот бенчмарк - для объективного выбора. Искусственные бенчмарки показывают радиус сферы коня и ничего более. К реальной жизни и реальным задачам - всё это не имеет никакого отношения.

Есть очень простой способ проверить от чего зависит скорость в Rust версии - от оверхеда библиотек или он всё же упирается в I/O. Можно добавить LTO в релизную сборку и прогнать тесты ещё раз.

[profile.release]
codegen-units = 1
lto = true

Ещё я использую для синтетических тестов на локальной машине wrk - самый производительный генератор трафика, который я пробовал. Не знаю, какая у вас машина, но может так быть, что Cassowary отбирает у сервисов процессорное время и это влияет на результаты.

Не понятно что все придираются,стандартные стеки\библиотеки. В начале статья я рассчитывал на паритет,не понятно почему go лидер...

Все придираются к тому, что автор не узнал в чём же причина разницы в скорости.

Похоже, что Golang JWT decoder не делает `verifySignature`, как это делает Rust.
Т.е. вместо `return []byte(jwtSecret), nil` должно быть что-то более сложное (например `ParseRSAPublicKeyFromPEM`).

автор вроде написал в конце "В Rust я пробовал менять sqlx на tokio-postgres, убирал расшифровку JWT, результат тот же.", говорит не помогло

@itmind, проблема вся в том, что дизель каждый раз перед "селектом" делает тестирование соединения, то есть обращается к базе в два раза чаще. Чтобы этого избежать нужно поправить код так, как я сделал ниже. Кстати в pgx просто даже настройки такой не нашёл.

После настройки разница в скорости около 10%

UPD. протестировал в сотню потоков, Rust даже обогнал Go

let pool = Pool::builder()
        .max_size(10)
        .min_idle(Some(10))
        .test_on_check_out(false) //!!!!!!
        .build(manager)
        .expect("can't connect to database");

Да, помогает здорово, у меня GO 25k rps, Rust 30k rps, на 20% быстрее.
а если переделать на юниксокет, и lto=true, то раст становится на 40% быстрее (31.8k vs 44.6k)

Но тест все равно неправильный, надо в контейнерах с лимитами запускать, а лучше на выделенных тачках или виртуалках.

И дизель там синхронный.

Я не поленился, переписал пример на tokio-postgres. Получил разницу производительности в 10% на 50 пользователях. Впрочем, оба приложения грузят процессор на 15% и упираются не то в контейнер с БД, не то в контейнер с cassowary. Но вариант на rust кушает 9мб памяти, а go целых 19мб. Необоснованно много, я считаю. В общем и целом паритет.

Обоснованно вполне. Разные механизмы работы с памятью. В golang работает GC, а в раст всё завязано на механизм владения, и кажется благодаря этому в куче "мусор" никогда не собирается.

У меня на tokio-postgres показатели почему то как на sqlx. Какой пул использовали или без пула?

deadpool-postgres для пула. Но я не настоящий растовщик, почему выбрал его и что есть ещё не скажу. А вот tokio-posgtres выбирал вполне осознанно - так подход получается 1 в 1 как с go, без ORM и прокладок (даже если они бесплатные, как дизель). Разве что есть важный нюанс - tokio-postgres не использует prepared statements по умолчанию, а в go либе они используются всегда, я даже не уверен что их можно отключить.

Добавил в git стек ntex + deadpool-postgres + tokio-posgtres (с кешированием statements).

Без lto проигрывает Go на 0,5%, c lto выигрывает 0,5%. В общем на фоне погрешности. Замена axum на ntex преимущества не дало.

Преимущество Rust только в потреблении памяти получается.

Изменил указанный параметр и провел замеры. По итогу Go и Rust сравнялись у меня. Дополнил статью.

Мир-Дружба-Жевачка! :)

А вообще я давно понял, что делать бенчи очень сложно. И если получился неожиданный результат, то дело 146% в неправильных тестах.

Делать перформанс тесты - это целая наука.

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

С чего бы это? Каждый язык предоставляет несколько разных библиотек для решения одной и той же задачи. Какие-то фичастее, какие-то быстрее, другие медленнее, но удобней в использовании. Выбор это хорошо.

В текущем примере автор выбрал не самую быструю библиотеку.

Это никак не означает, что только малое количество программистов на Rust способны "разогнать" скорость путем выбора другого крейта. :)

Вчера поигрался с кодом - уже с pgx и diesel, но ещё без тюнинга. Получил такие результаты - rust - 35k rps, go - 42k rps.

Но, когда добавил замеры времени работы именно http хендлера, то в обоих случаях время выполнения было в пределах 120-205 мкс. Т.е., похоже, на скорость, в основном, влияет Axum, а не sqlx или парсинг jwt. А вот настройка diesel эту разницу компенсирует.

Помимо количества и времени работы запросов хотелось бы увидеть потребление процессора и памяти.
Сдаётся мне что в условиях достаточного количества коннектов к пг, но не достаточного количества процов Rust обгонит Go.

У меня получились похожие результаты, тоже подозревал коннектор к бд.
Причем с коннектором редиса ситуация обратная

Небольшой выигрыш в rust дает, если вместо использования pool запускать несколько инстансов сервиса на одном порту (каждый в своем отдельном потоке, потоков по количеству процессоров ) и в каждом инстансе отдельное соединение к postgresql.

Правильно понимаю, что tokio в этой схеме места нет?

Tokio используется. Через него создаются инстансы. Пример rust_axum_pg в github из статьи.

Sign up to leave a comment.

Articles