Контекст: почему этот вопрос вообще возникает
Наша команда два года назад мигрировала ключевые микросервисы с Python (FastAPI) на Go. Это была не религиозная война, а конкретное решение конкретной проблемы: мы упирались в производительность при 50 000 RPS. Делюсь честным опытом без евангелизма.
Производительность: реальные цифры
Go быстрее Python в разы — это факт. Но насколько именно, зависит от задачи. В наших бенчмарках простого HTTP-сервиса с JSON-сериализацией:
- Python (FastAPI + uvicorn): ~12 000 RPS, 45 мс p99
- Python (FastAPI + uvicorn + uvloop): ~18 000 RPS, 30 мс p99
- Go (net/http): ~85 000 RPS, 8 мс p99
- Go (fasthttp): ~140 000 RPS, 5 мс p99
Важно понимать: если ваш сервис тратит 90% времени на запросы к базе данных, разница в производительности языка практически незаметна — будет упираться в БД.
go// Простой HTTP-сервер на Go с нулевыми аллокациями
package main
import (
"encoding/json"
"log"
"net/http"
)
type Response struct {
Status string `json:"status"`
Message string `json:"message"`
}
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{Status: "ok", Message: "hello"})
}
func main() {
http.HandleFunc("/health", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Экосистема и скорость разработки
Здесь Python выигрывает. Экосистема библиотек для Python значительно богаче, особенно в области ML/AI, работы с данными и интеграций. Если вам нужно быстро сделать прототип или работать с нестандартными API — Python продуктивнее.
Go компенсирует это отличной стандартной библиотекой. Для большинства backend-задач сторонние библиотеки вообще не нужны: net/http, database/sql, encoding/json — всё уже есть.
В нашей команде новый разработчик с Python-бэкграундом становился продуктивным в Go примерно за 3–4 недели. Язык намеренно прост — в этом его сила.
Конкурентность: горутины vs asyncio
Go создан для конкурентности — горутины дёшевы (2 КБ стека), планировщик эффективен. Python asyncio мощный, но сложнее в отладке и имеет ограничения GIL для CPU-bound задач.
python# Python: нужно явно использовать async/await
async def fetch_all(urls: list[str]) -> list[dict]:
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
return await asyncio.gather(*tasks)
go// Go: горутины и каналы — идиоматичный подход
func fetchAll(urls []string) []Result {
results := make(chan Result, len(urls))
for _, url := range urls {
go func(u string) {
results <- fetch(u)
}(url)
}
out := make([]Result, len(urls))
for i := range out {
out[i] = <-results
}
return out
}
Когда выбирать что
Выбирайте Go, если: высокие требования к latency и throughput, система с большим количеством одновременных соединений, сервис долгосрочной эксплуатации с минимальными накладными расходами, нужны маленькие бинарники и контейнеры.
Выбирайте Python, если: работаете с ML/AI, нужен быстрый прототип, команда уже знает Python, основная нагрузка — I/O и работа с внешними API, а не CPU.
Мы не мигрировали всё на Go — оставили Python-сервисы, работающие с ML-моделями. Разумный гибридный подход лучше религиозного следования одному языку.
Добавлю с инфраструктурной точки зрения: Go-бинарники без зависимостей — счастье для Docker. Образ в 8 МБ против 200 МБ для Python — это ощутимо на большом кластере.
С точки зрения работы с PostgreSQL: sqlx и pgx в Go очень хороши. SQLAlchemy в Python более гибкий и богатый. Для OLTP-нагрузки оба варианта нормально справляются.
Спорное утверждение насчёт «3–4 недели на изучение Go». Может для простых CRUD-сервисов, но идиоматичный Go с интерфейсами, горутинами и правильной обработкой ошибок — это месяца 2-3 минимум.
Реальный кейс: мигрировали сервис нотификаций с Python на Go. Потребление RAM упало с 800 МБ до 60 МБ. При той же нагрузке. Экономия на железе окупила миграцию за полгода.