
Содержание
Введение
Условия эксперимента
Архитектура тестируемых серверов
Настройки нагрузочного теста
Результаты тестирования
Практические выводы
Ответы на вопросы
Заключение
Введение
Выбор backend-языка почти всегда упирается в компромисс между скоростью разработки и производительностью. Python с FastAPI ценят за простоту, читаемость и быстрый старт. Go - за предсказуемость, высокую производительность и эффективную работу под нагрузкой.
В обсуждениях часто звучат общие тезисы: «Go быстрее», «FastAPI достаточно быстрый для большинства задач». Но за этими фразами редко стоят конкретные цифры и воспроизводимые тесты.
В этой статье я решил уйти от теории и провести простой, но показательный эксперимент: сравнить Go (net/http) и Python (FastAPI + Uvicorn) в одинаковых условиях с помощью нагрузочного тестирования Locust.
Мы посмотрим:
Сколько запросов в секунду способны выдержать оба сервера
Как ведут себя задержки под высокой нагрузкой
Где появляются медленные ответы, которые и портят пользовательский опыт
Это не попытка доказать, что один язык «лучше» другого. Цель статьи - показать реальные цифры и помочь понять, какой инструмент лучше подходит под конкретные требования и нагрузки.
Условия эксперимента
Аппаратные характеристики:
CPU: AMD Ryzen 5 4500U (2.38 GHz)
RAM: 16 GB DDR4 (3200 MHz)
Диск: NVMe SSD
ОС: Windows 11 Pro
Версии ПО:
Go: go1.25.5
Python: 3.12.10
FastAPI: 0.127.1
Uvicorn: 0.40.0
Locust: 2.42.6
Запуск серверов:
Go (net/http):
go run server.goFastAPI (Python):
uvicorn fastapi_server:app --port 8000Locust: на той же машине, нагрузка через HTTP (без TLS)
Настройки теста:
1000 одновременных пользователей
Spawn rate 100 пользователей в секунду
Продолжительность теста: 30 секунд
Пауза между запросами: 10–20 мс
Тестируется один GET-эндпоинт
/, без БД, кэша и бизнес-логики
Допущения и ограничения:
Один процесс сервера, без нескольких воркеров
Сценарий aggressive, чтобы увидеть пределы производительности
Оценк�� идёт по RPS, средней задержке и percentiles
Такой набор условий обеспечивает сравнимость серверов и делает результаты воспроизводимыми.
Архитектура тестируемых серверов
Для эксперимента мы сравниваем два минимальных HTTP-сервера: один на Go, другой на Python с FastAPI + Uvicorn. Цель - протестировать базовую производительность HTTP-стека и асинхронности, без влияния базы данных, кэшей или бизнес-логики.
Go сервер (net/http)
Для эксперимента важно было сравнивать чистую производительность HTTP-стека и runtime, а не фреймворки или дополнительные оптимизации. В случае с Go выбор пал на стандартный пакет net/http. Он входит в стандартную библиотеку, не требует сторонних зависимостей и использует модель goroutine, которая обеспечивает высокую конкурентность «из коробки». Использование минималистичного кода без middleware и лишней логики позволяет увидеть реальную производительность Go runtime без посторонних накладных расходов. Выбор net/http вместо популярных фреймворков вроде Gin или Echo обусловлен именно этим: эксперимент должен показать возможности языка, а не фреймворка.
Используется стандартный пакет Go
net/httpОдин endpoint
/, который возвращает простой текстВсе запросы обрабатываются в goroutine, что обеспечивает лёгкую конкурентность
Код сервера:
package main
import "net/http"
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Go-Server"))
})
http.ListenAndServe(":8080", nil)
}Запуск:
go run server.goБез дополнительных middleware и оптимизаций - чистый минимальный сервер
Python сервер (FastAPI + Uvicorn)
Для Python был выбран FastAPI вместе с Uvicorn. FastAPI - современный web-фреймворк, популярный для API, который поддерживает асинхронную обработку через async/await. Uvicorn обеспечивает выполнение ASGI-приложений и играет роль HTTP-сервера. Вместе они позволяют тестировать Python в условиях, максимально приближенных к реальным production-сценариям. При этом выбран один процесс Uvicorn, без Gunicorn и множества воркеров, чтобы условия для обоих серверов были сопоставимыми. Выбор минимального кода - один endpoint /, без базы данных, кэшей, аутентификации и middleware - сделан намеренно: мы хотим измерить скорость и стабильность обработки HTTP-запросов, а не бизнес-логику.
Используется современный web-фреймворк FastAPI поверх ASGI-сервера Uvicorn
Один endpoint
/, который возвращает простой текстАсинхронная функция
async defдля демонстрации async/awaitКод сервера:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def hello():
return "Python-Server"Запуск:
uvicorn fastapi_server:app --port 8000Один процесс Uvicorn, без Gunicorn и без нескольких воркеров
Такой подход обеспечивает честное сравнение: мы видим, как ведут себя Go и Python под нагрузкой, без влияния фреймворков, дополнительных библиотек и внешних сервисов. Минимальная архитектура серверов позволяет выделить именно различия в runtime, модели конкурентности и эффективности обработки запросов, что и является целью данного эксперимента.
Настройки нагрузочного теста
Для проверки производительности серверов Go и Python использовался инструмент Locust, который позволяет создавать воспроизводимую нагрузку с тысячами одновременных пользователей.
from locust import HttpUser, task, between
class TestUser(HttpUser):
wait_time = between(0.01, 0.02) # пауза 10–20 мс между запросами
@task
def hello(self):
self.client.get("/")В нашем эксперименте сценарий был одинаковым для обоих серверов: каждый пользователь делает GET-запрос к единственному endpoint / с паузой 10–20 мс между запросами.
Тест запускался на той же машине, что и серверы, чтобы исключить влияние сети и внешних факторов. Мы использовали 1000 одновременных пользователей, с скоростью добавления 100 новых пользователей в секунду, а продолжительность теста составляла 30 секунд. Таким образом, нагрузка была достаточно высокой, ч��обы выявить различия в обработке запросов и устойчивости серверов.
Сценарий был intentionally минималистичным: без базы данных, кэшей, аутентификации и других дополнительных слоёв. Это позволило измерять именно чистую производительность HTTP-стека и runtime, не искажённую логикой приложения.
Запуск Locust осуществлялся в headless-режиме, то есть без веб-интерфейса, с выводом только итоговой сводки. Так мы получили сравнимую статистику, которая показала разницу в скорости откликов и стабильности работы серверов под одинаковой нагрузкой. Для каждого сервера использовалась одинаковая команда, менялся только порт:
Для Go:
locust -f locust_test.py --host=http://localhost:8080 --users 1000 --spawn-rate 100 --run-time 30s --headless --only-summaryДля FastAPI:
locust -f locust_test.py --host=http://localhost:8000 --users 1000 --spawn-rate 100 --run-time 30s --headless --only-summary
Такой подход гарантирует, что нагрузка на оба сервера была абсолютно идентичной, а результаты можно напрямую сравнивать по количеству запросов, средней задержке и percentiles.
Результаты тестирования
После запуска Locust мы получили статистику по обоим серверам при одинаковой нагрузке: 1000 одновременных пользователей, spawn rate 100 в секунду, пауза между запросами 10–20 мс, продолжительность теста - 30 секунд.
Результат Go
Type Name # reqs # fails | Avg Min Max Med | req/s failures/s
--------|-----|-------|-------------|-------|-------|-------|-------|--------|-----------
GET / 43953 0(0.00%) | 408 6 20596 300 | 1470.28 0.00
--------|-----|-------|-------------|-------|-------|-------|-------|--------|-----------
Aggregated 43953 0(0.00%) | 408 6 20596 300 | 1470.28 0.00
Response time percentiles (approximated)
Type Name 50% 66% 75% 80% 90% 95% 98% 99% 99.9% 99.99% 100% # reqs
--------|-----|--------|------|------|------|------|------|------|------|------|------|------|------
GET / 300 380 430 450 510 540 640 4000 18000 20000 21000 43953
--------|-----|--------|------|------|------|------|------|------|------|------|------|------|------
Aggregated 300 380 430 450 510 540 640 4000 18000 20000 21000 43953Go (net/http) обработал 43 953 запроса за 30 секунд, с медианной задержкой 300 мс и средним RPS около 1470. При этом верхние percentiles показывают небольшое увеличение времени отклика: 98-й процентиль - 640 мс, 99-й - 4000 мс, а 99.9-й - 18 000 мс. Отказы и ошибки отсутствовали.
Результат Python
Type Name # reqs # fails | Avg Min Max Med | req/s failures/s
--------|-----|-------|-------------|-------|-------|-------|-------|--------|-----------
GET / 34222 0(0.00%) | 539 13 20802 410 | 1140.30 0.00
--------|-----|-------|-------------|-------|-------|-------|-------|--------|-----------
Aggregated 34222 0(0.00%) | 539 13 20802 410 | 1140.30 0.00
Response time percentiles (approximated)
Type Name 50% 66% 75% 80% 90% 95% 98% 99% 99.9% 99.99% 100% # reqs
--------|-----|--------|------|------|------|------|------|------|------|------|------|------|------
GET / 410 470 500 510 550 590 2400 5500 19000 21000 21000 34222
--------|-----|--------|------|------|------|------|------|------|------|------|------|------|------
Aggregated 410 470 500 510 550 590 2400 5500 19000 21000 21000 34222Python (FastAPI + Uvicorn) за тот же период обработал 34 222 запроса, медиана задержки составила 410 мс, средний RPS - 1140. Задержки на верхних percentiles значительно выше: 98-й процентиль - 2 400 мс, 99-й - 5 500 мс, 99.9-й - 19 000 мс. Ошибок запросов также не было.
Сравнительная таблица
Метрика | Go | FastAPI | Разница |
Всего запросов | 43,953 | 34,222 | Go +28% |
RPS (средний) | 1,470 | 1,140 | Go +29% |
Медиана (50%) | 300ms | 410ms | Go +27% |
90% запросов | 510ms | 550ms | Go +7% |
95% запросов | 540ms | 590ms | Go +8% |
98% запросов | 640ms | 2,400ms | Go +73% |
99% запросов | 4,000ms | 5,500ms | Go +27% |
99.9% запросов | 18,000ms | 19,000ms | Go +5% |
Сравнение показывает, что Go выдерживает примерно на 29% больше нагрузки, а медиана отклика ниже на 27%. Разница особенно заметна на верхних percentiles: 98-й процентиль для FastAPI почти в 4 раза выше, что означает, что небольшая часть пользователей будет получать значительно более медленный отклик при пиковых нагрузках.
В целом, оба сервера стабильно работали под экстремальной нагрузкой, но Go продемонстрировал лучшее использование ресурсов, обработав на 9 731 запрос больше за тестовый период. Эти результаты подтверждают, что Go обеспечивает более высокую производительность и стабильность откликов при одинаковых условиях.
Практические выводы
Эксперимент показал, что оба сервера стабильно обрабатывают нагрузку, но есть важные различия, которые влияют на выбор технологий для проекта.
Go (net/http)
Обрабатывает на 29% больше запросов за тот же период
Медиана отклика на 27% меньше, чем у FastAPI
98-й percentиль — 640 мс, что значительно лучше, чем у Python
Идеально подходит для проектов с высокой нагрузкой: соцсети, торговые площадки, стриминговые сервисы
Эффективнее расходует серверные ресурсы и обеспечивает стабильность при росте нагрузки
Python (FastAPI + Uvicorn)
Быстрая разработка и удобство создания API
Подходит для маленьких и средних проектов
Нагрузка до ~1000 RPS (~1000 онлайн-пользователей)
Асинхронная обработка позволяет выдерживать умеренную нагрузку без критических задержек
Отличный выбор, если важнее скорость выхода продукта на рынок и простота поддержки
В целом, выбор между Go и FastAPI зависит от приоритетов проекта: если важны производительность и масштабируемость - Go; если важна скорость разработки и гибкость экосистемы - FastAPI. Эксперимент показал, что уже на чистом минимальном сервере разница в откликах и устойчивости может быть заметной, поэтому правильный выбор технологии на раннем этапе помогает избежать проблем при росте нагрузки.
Когда выбирать Go:
Большие проекты: соцсети, торговые площадки, стриминговые сервисы
Максимальная производительность и стабильность откликов
Ограниченные серверные ресурсы
Проект предполагается к росту и масштабированию
Когда выбирать FastAPI:
Быстрая разработка и простота поддержки
Маленькие и средние проекты
Нагрузка < 1000 RPS (~1000 пользователей онлайн одновременно)
Ответы на вопросы
Любой эксперимент вызывает вопросы - от простых до придирчивых. В этом разделе я собрал самые вероятные вопросы, которые могут возникнуть у читателей.
Вопрос: Почему тестировали только один GET-эндпоинт / без базы данных и логики?
Ответ: Цель эксперимента - сравнить чистую производительность HTTP-стека и runtime. Любая логика, база или кэш могли бы исказить результаты и скрыть различия между Go и Python.
Вопрос: Почему выбран минимальный код для Go и Python?
Ответ: Чтобы показать возможности чистого runtime и минимального фреймворка без влияния middleware или оптимизаций. Это позволяет честно сравнивать языки, а не фреймворки.
Вопрос: Почему Go net/http, а не Gin или Echo?
Ответ: net/http — стандартный пакет, без зависимостей, использует goroutine для конкурентности. Это демонстрирует возможности языка, а не стороннего фреймворка.
Вопрос: Почему Python FastAPI + Uvicorn, а не Flask или Django?
Ответ: FastAPI поддерживает async/await, современный API и высокую производительность. Uvicorn как ASGI-сервер позволяет сравнивать Python runtime максимально честно.
Вопрос: Почему тест запускался на одной машине?
Ответ: Чтобы исключить влияние сети и внешних факторов. Мы хотели измерить именно производительность серверов, а не пропускную способность сети.
Вопрос: Почему Locust, а не JMeter, Artillery или wrk?
Ответ: Locust удобен для сценариев с тысячами пользователей, легко повторяем и позволяет писать Python-сценарии, что согласуется с условиями эксперимента.
Вопрос: Почему 1000 одновременных пользователей и spawn rate 100?
Ответ: Это было сделано, чтобы создать достаточно высокую нагрузку, выявить разницу в производительности и стабильности, но не перегрузить систему полностью.
Вопрос: Почему тест длился только 30 секунд?
Ответ: Достаточно для измерения пиковых нагрузок и percentiles. Продление теста увеличило бы время, но не дало бы новых инсайтов.
Вопрос: Почему в сценарии Locust пауза всего 10–20 мс?
Ответ: Чтобы эмулировать aggressive сценарий, где сервер работает почти без отдыха, что позволяет увидеть пределы производительности.
Вопрос: Почему не использовались несколько воркеров/процессов для серверов?
Ответ: Чтобы условия были сопоставимыми: один процесс Go и один процесс Uvicorn. Добавление воркеров усложнило бы сравнение чистого runtime.
Вопрос: Почему RPS и медиана - главные метрики?
Ответ: Они показывают пропускную способность и качество отклика, а percentiles показывают стабильность для 98–99% пользователей.
Вопрос: Почему Go быстрее на 27% и держит +29% нагрузки?
Ответ: За счёт легковесных goroutine, оптимизированного runtime и отсутствия глобальных блокировок, что даёт более эффективную работу под высокой нагрузкой.
Вопрос: Почему верхние percentiles у FastAPI такие высокие (например, 98-й — 2400 мс)?
Ответ: В Python есть GIL и overhead async runtime, что при высокой конкуренции создаёт медленные отклики для небольшого процента пользователей.
Вопрос: А что ес��и нагрузка <1000 RPS?
Ответ: Тогда FastAPI вполне достаточен, разница с Go будет минимальной, а скорость разработки может быть важнее.
Вопрос: Почему выбор языка влияет на «узкие места» при росте нагрузки?
Ответ: Go обеспечивает более предсказуемую и стабильную обработку запросов. Python может допустить длинные задержки для части пользователей при пиковых нагрузках.
Вопрос: А если использовать FastAPI с Gunicorn и несколькими воркерами?
Ответ: Производительность улучшится, но условия сравнения станут неравными, так как Go тестировался на одном процессе.
Вопрос: Почему использовался HTTP, а не HTTPS?
Ответ: Для чистоты эксперимента: шифрование добавило бы дополнительную нагрузку, мешающую оценке runtime.
Вопрос: А что насчёт многопоточности Python?
Ответ: В эксперименте использовался один процесс Uvicorn без Gunicorn, чтобы сравнение было честным. Многопоточность или несколько воркеров изменили бы результаты.
Вопрос: Можно ли делать выводы о производительности реальных проектов по этим данным?
Ответ: Выводы верны только для HTTP-стека и runtime. Реальные проекты с бизнес-логикой, базами и кэшами будут иметь другие показатели, но тренд сохранится.
Вопрос: Почему Go так хорошо масштабируется?
Ответ: Goroutine легковесные, планировщик эффективный, нет GIL, что позволяет обрабатывать тысячи конкурентных запросов без значительных задержек.
Вопрос: Почему статья не затрагивает безопасность, аутентификацию и бизнес-логику?
Ответ: Они не нужны для эксперимента, цель — чистая производительность HTTP-стека.
Вопрос: Можно ли повторить эксперимент с другими сценариями?
Ответ: Да, Locust позволяет менять сценарий, нагрузку и endpoints. Эксперимент легко воспроизводим.
Вопрос: Почему выводы ориентированы на выбор языка под проект?
Ответ: Потому что разные проекты имеют разные требования: скорость разработки, нагрузка, стабильность и масштабируемость.
Вопрос: Почему автор не приводит сравнение с другими языками или фреймворками?
Ответ: Цель - показать чистый Go vs Python/FastAPI, без отвлечения на сторонние решения.
Заключение
Эксперимент показал, что выбор между Go и FastAPI нельзя сводить только к общим словам «быстро» или «медленно». Go обеспечивает более высокую производительность и стабильность под нагрузкой, а FastAPI выигрывает в скорости разработки и удобстве поддержки. Понимание сильных и слабых сторон каждой технологии позволяет делать обоснованные решения: когда проект небольшой или критична скорость выхода на рынок - FastAPI; когда нагрузка высокая, ресурсы ограничены и важна масштабируемость - Go. Тесты на чистых минимальных серверах дают реальную картину возможностей runtime и HTTP-стека, и эти цифры помогут вам выбрать подходящий инструмент под конкретные задачи.