Содержание

  • Введение

  • Условия эксперимента

  • Архитектура тестируемых серверов

  • Настройки нагрузочного теста

  • Результаты тестирования

  • Практические выводы

  • Ответы на вопросы

  • Заключение


Введение

Выбор 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.go

  • FastAPI (Python): uvicorn fastapi_server:app --port 8000

  • Locust: на той же машине, нагрузка через 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  43953

Go (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  34222

Python (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-стека, и эти цифры помогут вам выбрать подходящий инструмент под конкретные задачи.