Вопрос проверяет, понимаете ли вы, из чего складывается latency, и умеете ли проектировать быстрый путь обработки: минимальные зависимости, кэш/предрасчёт, ограничения параллелизма и контроль хвостовых задержек (p95/p99).
Сначала нужно “вписаться” в бюджет времени: сеть, сериализация, бизнес-логика, БД и внешние вызовы. Для <100 мс обычно убирают тяжёлые операции из запроса: предрасчёт, кеш, асинхронная обработка. Затем ограничивают вариативность: таймауты, лимиты, отказ от лишних сетевых прыжков. Обязательно оптимизируют доступ к данным (индексы, короткие запросы, батчи) и следят за p95/p99, а не только за средним временем. Без наблюдаемости и нагрузочного теста это не удержать.
100 мс — это общий бюджет на весь путь: клиент → сеть → сервис → зависимости → ответ. Обычно “съедают” время именно зависимости, поэтому быстрый сервис — это сервис с минимальным количеством непредсказуемых вызовов.
Нужно сразу определить:
целевой перцентиль (например, p95 < 100 мс, p99 < 200 мс)
максимальное число сетевых hops (чем меньше, тем лучше)
Определение: Percentile (p95/p99) — время, быстрее которого укладывается 95%/99% запросов; это главный показатель “хвостовой” задержки.
Типовые приёмы:
предрасчёт и хранение готовых результатов
кэширование (частей ответа или всего ответа)
перенос тяжёлых операций в фон (очередь/воркеры)
Если данные “почти всегда уникальны”, кэш может быть не по полному ответу, а по его частям:
справочники/права/настройки
данные профиля
результаты дорогих вычислений, которые переиспользуются между запросами
С БД важно:
короткие запросы (без лишних join/scan)
индексы под реальные фильтры
ограничение объёма выборки (пагинация, лимиты)
пул соединений адекватного размера
Под высоким RPS легко “убить” БД лавиной параллельных операций.
Практика:
лимит параллельных запросов к БД/внешним сервисам
таймауты на каждый вызов
деградация (лучше частичный ответ, чем 500)
Пример таймаута и отмены через context:
ctx, cancel := context.WithTimeout(r.Context(), 80*time.Millisecond)
defer cancel()
// db.QueryContext(ctx, ...)
В Go типичные источники потерь:
лишние аллокации на горячем пути
большие JSON payload и частая сериализация
слишком подробное логирование на каждый запрос
Практика:
профилировать pprof (CPU/heap)
избегать лишних преобразований структур
аккуратно с middleware, которые делают много работы
Чтобы удерживать SLA, измеряйте:
latency p50/p95/p99
долю таймаутов и ретраев
latency по каждой зависимости
насыщение пулов (goroutines, DB pool)
Стабильные <100 мс при высоком RPS достигаются не “быстрым кодом”, а архитектурой быстрого пути: минимум зависимостей в запросе, предрасчёт/кэш, строгие таймауты и лимиты, плюс контроль p95/p99 и постоянное профилирование.