Мотивация
Микросервисная архитектура позволяет выбирать между технологиями и языками программирования при написании REST api сервисов. Какой язык лучше выбрать, для написании REST api приложения, чтобы обеспечить большее количество одновременных пользователей быстрым и стабильным ответом на одном и том же железе? Чтобы ответить на этот вопрос было бы хорошо увидеть разницу в производительности одного и того же приложения написанного на Java и GO.
Дисклеймер
Все результаты представленные в данном эксперименте являются частным примером использования Java и GO и не должны использоваться для описания производительности этих языков в других условиях.
Условия/Ограничения
- Никаких дополнительных настроек по увеличению производительности. Фреймворки и библиотеки должны использоваться с настройками по-умолчанию.
- Никаких ORM фреймворков. Только драйвера баз данных и одинаковые запросы в обоих приложениях.
План
- Создадим два простых REST api приложения на Java и GO c PostgreSQL базой данных
- Создадим нагрузочные тесты при помощи Jmeter
- Запустим каждое приложение, базу и тесты на отдельных AWS экземплярах
- Прогоним тесты и соберем результаты
Тестируемое приложение
В качестве системы для тестирования я создал два приложения: bank-java и bank-go. Это наверное самое простое банк-приложение в мире. Все что оно умеет делать это создавать новых клиентов с начальным балансом, переводить средства от одного клиента другому и показывать баланс.
API:
- Post /client/new/{balance} — создает клиента с начальным балансом
- Post /transaction — переводит средства от одного клиента другому
- Get /client/{id}/balance — показывает баланс
Фреймворки и библиотеки
При выборе тех стэка я использовал самые новые, популярные и простые фреймворки и библиотеки, чтобы как можно быстрее реализовать нужный функционал.
Bank-java: Java 11, spring boot 2.0.4, spring-web 5.0.8, PostgreSQL JDBC 4.2.4
Bank-go: Go 1.8, gorilla/mux, github.com/lib/pq
Тестовая среда
В качестве тестовой среды был использован AWS.
Следующие EC2 экземпляры были созданы:
- Bank-go t2.micro (Variable ECUs, 1 vCPUs, 2.5 GHz, Intel Xeon Family, 1 GiB memory, EBS only)
- Bank-java t2.micro (Variable ECUs, 1 vCPUs, 2.5 GHz, Intel Xeon Family, 1 GiB memory, EBS only)
- Postgres d2.xlarge (14 ECUs, 4 vCPUs, 2.4 GHz, Intel Xeon E52676v3, 30.5 GiB memory, 3 x 2048 GiB Storage Capacity)
- Bank-test t2.2xlarge (Variable ECUs, 8 vCPUs, 2.3 GHz, Intel Broadwell E5-2686v4, 32 GiB memory, EBS only)
Все экземпляры используют Ubuntu Server 18.04 LTS (HVM), SSD Volume Type
![](https://habrastorage.org/webt/xi/c1/cr/xic1cr8qq516rqlv8jvq6oo4bkw.png)
Тестовый проект
Jmeter тест вызывает каждый API из списка сверху, проверяет что статус ответа равен 200 и тело ответа содержит id. Для каждого приложения я запустил его с числом одновременных пользователей 1000, 2000 и т.д. до 10000.
С полным логом результатов можно ознакомиться здесь.
Агрегированные результаты
![](https://habrastorage.org/webt/yr/-l/jr/yr-ljrctxtijkovq7n9rc-dn1cs.png)
![](https://habrastorage.org/webt/_w/zv/lg/_wzvlgai6xzi0g7uqrwdct3sq8m.png)
![](https://habrastorage.org/webt/n0/6b/yh/n06byhu3gexflfiol66sohpp8e4.png)
Описание результатов
Оба приложения работают отлично при числе пользователей 1000. Начиная с 2000 GO приложение начинает значительно терять в производительности и чуть-чуть в стабильности. У Java этот рубеж начинается с 3000.
Заключение
Используя одно и то же оборудование REST api приложение Java может поддерживать вдвое* больше одновременных пользователей, чем приложение GO с базой данных PostgreSQL.
*Данная цифра верна только при использовании тех же условий испытания что и в данном эксперименте
Комментарии (30)
RPG18
27.09.2018 18:46+2Что бросается в глаза на blank-go:
- стандартный http сервер;
- стандартная работа с json.
Поэтому такие низкие результаты.
Laney1
27.09.2018 19:18+8еще спамятся функции fmt.Println, которые мало того что не буферизованы и с оверхедом из-за конвертации аргументов, там еще и там внутри мьютекс.
В общем, этот "бенчмарк" очень похож на знаменитую статью какого-то сотрудника mail.ru пару лет назад, у которого в коде на rust на каждый запрос выполнялась компиляция регекспа
OlegSchwann
27.09.2018 20:29Про json: в одном из учебных примеров easyjson давал 50-и кратный прирост производительности.
JekaMas
28.09.2018 07:46Есть еще такая прелесть без потребности в генераторах кода https://github.com/json-iterator/go
rudenkovk
28.09.2018 15:16подтвержу, на реальном проекте так и було. Не 50 конечно, но 25-35 выдавали.
time2rfc
27.09.2018 18:54Интересно было бы посмотреть на графики потребления cpu\ram во время тестирования, и на результаты с прогреввом java версии. В любом случаи спасибо!
helgihabr
27.09.2018 19:01+61. Как можно тестировать производительность, если у вас на каждый чих стоит println?
2. Зачем каждый раз открывать/закрывать подключение к бд? Еще и формировать строку подключения тоже каждый раз.
И пара вопросов по коду:
1. В джаве return client; вернет json?
2. В чем заклоючалась нестабильность? Кто и какие ошибки выдавал?
helgihabr
27.09.2018 19:12+2И еще момент:
в го у вас:
db.SetMaxOpenConns(20) // Sane default
зачем это?
db.SetMaxIdleConns(0)
db.SetConnMaxLifetime(time.Nanosecond)
И покажите такое в джаве, а то я не нашел.
astec
27.09.2018 20:10+4Не верю. Был бы свободный день и Go приложение заработало бы в 5-10 раз быстрее как миниму.
Должно быть стыдно делать такие тесты и уж тем более выкладывать как статьи.
OlegSchwann
27.09.2018 20:28Грустно на такое смотреть. Подготовка SQL не используется. Это не сложно совсем, один запрос.
unabl4
27.09.2018 20:32А теперь выложите сюда pprof с go и вполне может оказаться, что ботлнек — это работа с базой.
Алсо, для нагрузочного тестирования есть православные ab / siege.
kolkoni
27.09.2018 21:08Elixir вроде как очень хорош в плане нагрузки, его не рассматривали?
eoffsock
28.09.2018 00:35Мне кажется, что не выиграет. Хотя интересно было бы потестировать. По моему опыту, Elixir больше про стабильность, а не про производительность.
Можно конечно узкие места подправить, скажем нативная JSON либа (jiffy) будет раз в пять быстрее стоковой либы. Если в день через приложение проходят миллионы запросов, разница может оказаться ощутимой.
Monnoroch
27.09.2018 21:15Of course, I'd also suggest that whoever was the genius who thought it was a good idea to
read thingscreate ONE F*CKINGBYTECONNECTIONAT A TIMEPER REQUEST withsystem callsTCP handshakes for eachbyterequest should be retroactively aborted. Who the f*ck does idiotic things like that? How did they noty die as babies, considering that they were likely too stupid to find a tit to suck on?
Linus.
Originalunabl4
27.09.2018 22:58Жаль, что, скорее всего, больше такого в LKML (или где-либо ещё) не увидишь. Остаётся только перечитывать старенькое и пускать скупую ностальгическую слезу :) Это же просто бесценно.
bromzh
27.09.2018 22:17Вот вам бенчмарки
Только Java и Go. Разные фреймворки/библиотеки. Там ещё можно попереключать тип приложения и тачки.
kuftachev
28.09.2018 00:30+2Ну там же написано, что это "из песочницы", тут же видно, что это не инженерная работа, а кто-то сидел в песочнице во дворе, какой-то злой дядя дал ноут с доступом в интернет и получились такие тесты.
Если бы это делал хоть немного инженер, его бы хотя бы волновало почему именно такие результаты? Почему его результаты так отличаются от результатов более разумных существ? Ну и много других вопросов.
А по сути, тут даже комментировать нечего.
helgihabr
28.09.2018 09:59волновало почему именно такие результаты?
Совершенно правильный камент.
Что очень удивляет, так это дикая разница, особенно на первых шагах (почти в 6 раз!).
Хотя, по сути, здесь нечем тестировать сами языки, т.к. никакой обработки данных в самих ЯП не происходит. Только простейщий прием параметров и отправка сырого запроса в базу. Разница в конкретном тесте должна быть минимальна.
Автор же нагородил целый огород из горилл и джейсонов, что в данном тесте просто съест львиную долю ресурсов, а все должно просто упереться в систему ввода-вывода и в базу.
Сами ЯП тут толком и не тестируются.
argonavtt
28.09.2018 09:28Как правило такие тесты провести довольно трудно, нужно очень хорошо разбираться в обеих языках. Если же нет то и результат будет плачевным. Да и смысла этой статьи не вижу, подобных пруд пруди, да и с более правильными тестами. А так holy war на ровном месте.
avraam_linkoln
28.09.2018 09:32Очередной пост, в котором чувак, не разобравшийся нормально в технологии, пытается что то сравнивать
claygod
28.09.2018 10:40-1Имея опыт написания роутера для Go могу сказать совершенно точно, что роутер Гориллы по производительности совсем не айс, смысл брать его в бенчмарк не вижу вообще
hummerd
28.09.2018 15:33Вы бы показали код на GO человеку, умеющиму писать на GO.
Зачем на каждый чих вызывать newDb()?
DB.Close
It is rare to Close a DB, as the DB handle is meant to be long-lived and shared between many goroutines.
Вы бы и в джаве тогда BankPostgresRepository сконфигурили бы как per call.
Throwable
29.09.2018 22:33Bank-java: Java 11, spring boot 2.0.4, spring-web 5.0.8, PostgreSQL JDBC 4.2.4
О боже! В этом мире еще остались люди, которые не пишут на Spring Boot и не ассоциируют его со словом Java?
Golem765
Ставь лайк если тоже пустил скупую мужскую слезу гордости за родной язык