Представьте: сервис на Go обрабатывает 2000 запросов в секунду, но при пиковой нагрузке в 5000 начинает отдавать 503. Логи чистые, ошибок нет, но latency растёт с 50 мс до 2 секунд. Знакомо? Это классическая ситуация, когда конкурентность в Go реализована «по учебнику», но без production-паттернов. Разберём, как практик решает такую задачу, и где обычно ломается схема.
Почему Go и gRPC — это не просто «ещё один стек»
Go выигрывает там, где нужна предсказуемая работа с конкурентностью. Горутины дешёвые (начальный стек ~2 КБ), планировщик эффективно распределяет их по ядрам, а каналы дают простой способ синхронизации. gRPC поверх HTTP/2 добавляет строгую типизацию через protobuf, мультиплексирование и потоковую передачу. Вместе они позволяют строить сервисы, которые легко масштабируются и быстро обрабатывают запросы.
Но дьявол в деталях. Например, context.Context — обязательный параметр для любого RPC-вызова. Если его игнорировать, таймауты не работают, горутины утекают, а сервис перестаёт корректно завершаться. Это одна из первых ловушек для новичков.
Разбор задачи: от медленного ответа к стабильному сервису
Возьмём типичный сервис заказов. При запросе /orders/{id} он лезет в базу, потом в кэш, потом в платёжный шлюз — всё последовательно. При нагрузке 2000 RPS среднее время ответа 120 мс, но 95-й процентиль уже 800 мс. Добавим сюда gRPC-стриминг для обновлений статуса — и получим утечку горутин, если стрим не закрывается при обрыве соединения.
Что делает практик:
- Параллелит независимые вызовы. База и кэш опрашиваются одновременно через
errgroup.Group. Время ответа падает до 70 мс, 95-й процентиль — до 300 мс. - Добавляет таймауты и ретраи с backoff. Для платёжного шлюза ставит дедлайн 500 мс и 2 ретрая с экспоненциальной задержкой. Это снижает количество ошибок при кратковременных сбоях, но требует идемпотентности операций.
- Включает мониторинг через OpenTelemetry. Трейсы показывают, что 60% времени уходит на сериализацию protobuf при стриминге — неочевидный вывод, который не виден в обычных логах.
Если этого не сделать, при пике нагрузки горутины накапливаются, память растёт, GC начинает чаще срабатывать — и сервис деградирует лавинообразно. Заметить проблему заранее можно по метрике go_goroutines и времени выполнения GC. Если уже поздно — поможет только graceful shutdown с дренажом соединений и принудительным завершением зависших горутин.
Где ломается сама схема
Даже правильно построенный сервис может отказать. Вот три сценария, с которыми сталкиваются в production.
1. Изменились условия: gRPC-стрим не закрывается
Клиент отключился, но сервер продолжает слать сообщения в горутину. Если не слушать ctx.Done() в цикле стрима, горутина живёт вечно. Со временем их накапливаются тысячи. Как заметить: рост go_goroutines без роста нагрузки. Что делать: всегда использовать select с ctx.Done() и закрывать стрим по сигналу.
2. Правило не применяется: middleware для логирования не видит ошибок
Стандартный перехватчик (interceptor) логирует только код ответа gRPC, но не бизнес-ошибки внутри. В итоге в логах «OK», а пользователи жалуются. Как заметить: расхождение между метриками ошибок на клиенте и сервере. Что делать: добавить в middleware извлечение ошибок из контекста или использовать кастомные коды.
3. Инструмент перестал работать: pprof не даёт профиль
При включённом профилировании в production без ограничений pprof начинает сам создавать нагрузку. Или порт pprof закрыт файрволом. Как заметить: профиль не снимается, хотя эндпоинт доступен. Что делать: вынести pprof на отдельный порт с аутентификацией и использовать выборочное профилирование.
Пути освоения: самостоятельный vs структурированный
Многие начинают с документации и статей. Это бесплатно, но требует дисциплины: без обратной связи легко пропустить важные детали — например, что context.WithTimeout нужно отменять, иначе утечка. Практика на pet-проектах даёт опыт, но не моделирует реальные сбои.
Курсы дают структуру и разбор ошибок. Например, в курсе Go для бэкенда (gRPC) разбирают именно такие сценарии: как настроить graceful shutdown, как писать table-driven тесты для конкурентного кода, как избежать утечек в стримах. Альтернатива — менторинг или код-ревью в команде, но это дороже по времени и деньгам.
Кому подходит этот путь
Если вы уже знакомы с основами Go и хотите перейти на уровень production-разработки — этот материал для вас. Если же вы только начинаете программировать, сначала стоит освоить базовый синтаксис и хотя бы один проект. Не ждите, что курс заменит реальный опыт: он даёт инструменты, но применять их придётся самостоятельно.
В финале хочется подчеркнуть: надёжные и быстрые бэкенды на Go строятся не на магии, а на понимании конкурентности, таймаутов и мониторинга. Курс Go для бэкенда (gRPC) помогает системно разобрать эти темы и избежать типичных ошибок — подробности и запись на странице курса.