Производительность Go
Профилирование через pprof, анализ узких мест, PGO, стратегии масштабирования, тестирование и мокирование зависимостей.
8 вопросов
JuniorТеорияОчень частоВ чём разница между горизонтальным и вертикальным масштабированием?
В чём разница между горизонтальным и вертикальным масштабированием?
Горизонтальное масштабирование добавляет больше экземпляров сервиса и распределяет нагрузку между ними, что требует stateless-экземпляров и балансировщика. Вертикальное даёт одному экземпляру больше CPU и RAM — проще, но ограничено самой мощной машиной и обычно требует перезапуска.
Типичные ошибки
- ✗Путать определения — горизонтальное добавляет экземпляры, вертикальное добавляет ресурсы одному экземпляру
- ✗Забывать, что горизонтальное масштабирование требует stateless-экземпляров и балансировщика
- ✗Считать, что у вертикального масштабирования нет потолка — оно ограничено самой мощной машиной
Уточняющие вопросы
- →Почему горизонтальное масштабирование обычно требует, чтобы сервис был stateless?
- →Когда вы предпочтёте вертикальное масштабирование несмотря на его потолок?
JuniorТеорияЧастоЧто такое профилирование в Go и каким инструментом снять профиль CPU или кучи?
Что такое профилирование в Go и каким инструментом снять профиль CPU или кучи?
Профилирование сэмплирует работающую программу, чтобы увидеть, где она тратит CPU или память. runtime/pprof в Go (и net/http/pprof) снимает профили CPU, кучи, горутин и блокировок, которые смотрят через go tool pprof как top-список, граф вызовов или flame graph.
Типичные ошибки
- ✗Путать профилирование с детектором гонок
-race - ✗Думать, что в Go нет встроенного профилировщика и работает лишь ручной замер
- ✗Путать профилирование во время выполнения со статическим escape-анализом (
-gcflags=-m)
Уточняющие вопросы
- →В чём разница между профилем CPU и профилем кучи?
- →Как
net/http/pprofпозволяет профилировать работающий сервер?
JuniorТеорияЧастоЧто go test поддерживает из коробки, и какие виды тестов можно писать?
Что go test поддерживает из коробки, и какие виды тестов можно писать?
go test запускает функции с именами TestXxx(*testing.T) в файлах _test.go без сторонних фреймворков. Из коробки поддерживаются модульные тесты, табличные тесты, бенчмарки (BenchmarkXxx), исполняемые примеры и фаззинг (FuzzXxx). Моки обычно — небольшие рукописные подделки за интерфейсом. Полезные флаги: -run, -v, -race, -cover, -bench.
Типичные ошибки
- ✗Думать, что для запуска тестов в Go вообще нужен сторонний фреймворк
- ✗Забывать, что файлы тестов должны оканчиваться на
_test.go, а функции начинаться сTest - ✗Считать, что Go автогенерирует моки, а не писать небольшие подделки руками
Уточняющие вопросы
- →Что такое табличный тест, и почему это идиоматичный паттерн в Go?
- →Что добавляет флаг
-race, и почему не запускать его на каждой сборке?
MiddleТеорияЧастоКак мокировать внешние зависимости при тестировании кода на Go?
Как мокировать внешние зависимости при тестировании кода на Go?
Зависят от маленького интерфейса, а не от конкретного типа, и в тесте внедряют подделку. Объявляют интерфейс у потребителя, передают его через конструктор или поле, а в тесте подставляют рукописную заглушку или сгенерированный mock, который записывает вызовы и возвращает заданные значения. Это изолирует юнит от реального I/O и делает поведение детерминированным.
Типичные ошибки
- ✗Объявлять интерфейс рядом с реализацией, а не у потребителя, которому нужно мокировать
- ✗Делать интерфейс огромным, из-за чего тестовой подделке приходится заглушать десятки неиспользуемых методов
- ✗Хвататься за подмену глобальных указателей на функции вместо внедрения зависимости
Уточняющие вопросы
- →Почему узкий интерфейс, объявленный у потребителя, проще мокировать, чем широкий?
- →Когда рукописная заглушка предпочтительнее сгенерированного mock вроде
gomock?
MiddleТеорияЧастоКак профилировать работающий в production Go-сервис с помощью pprof?
Как профилировать работающий в production Go-сервис с помощью pprof?
Импортируйте net/http/pprof, чтобы отдавать профили по HTTP, или вызывайте runtime/pprof напрямую. Вы собираете профили CPU, heap, goroutine, block или mutex и анализируете их через go tool pprof, который показывает горячие функции списком, графом или flame graph.
Типичные ошибки
- ✗Думать, что профилирование требует особого флага сборки, а не импорта
net/http/pprof - ✗Считать, что Go отдаёт только heap-профиль, но не CPU, goroutine или block
- ✗Забывать, что CPU-профиль работает в окне семплирования, а не снимает мгновенный срез
Уточняющие вопросы
- →Как CPU-профиль Go семплирует программу и с какой частотой?
- →Что профиль goroutine показывает такого, чего не показывает CPU-профиль?
MiddleТеорияЧастоЧего сервис должен избегать, чтобы масштабироваться горизонтально?
Чего сервис должен избегать, чтобы масштабироваться горизонтально?
Он не должен держать состояние, привязанное к экземпляру, от которого зависит запрос — никаких сессий, кешей, счётчиков или локальных файлов в памяти, которых нет у другой реплики. Состояние выносится в общие хранилища (БД, Redis, объектное хранилище), чтобы любая реплика обслужила любой запрос. Добавьте балансировщик, маршрутизацию без sticky-сессий и идемпотентные обработчики, чтобы запрос безопасно повторился на другом экземпляре.
Типичные ошибки
- ✗Держать сессии или счётчики в памяти процесса, из-за чего реплики расходятся и масштабирование ломается
- ✗Полагаться на sticky-сессии как на решение вместо вынесения состояния наружу
- ✗Путать горизонтальное масштабирование с простым использованием большего числа ядер на одном экземпляре
Уточняющие вопросы
- →Как ключи идемпотентности позволяют балансировщику безопасно повторить запрос на другой реплике?
- →Где должен жить счётчик rate-limit на пользователя, если его обязана соблюдать каждая реплика?
SeniorТеорияЧастоКак определить, что сервис упирается в CPU, а не в I/O?
Как определить, что сервис упирается в CPU, а не в I/O?
Сервис, упирающийся в CPU, даёт высокую загрузку CPU и профиль, где доминируют несколько горячих функций; добавление ядер помогает. Сервис, упирающийся в I/O, даёт низкую загрузку CPU, много goroutine, заблокированных в ожидании сети или диска, и задержку, не падающую при добавлении ядер.
Типичные ошибки
- ✗Судить только по задержке, не проверяя загрузку CPU и профили
- ✗Забывать, что троттлинг CPU-cgroup может выдать CPU-bound сервис за I/O-bound
- ✗Считать, что заблокированные goroutine жгут CPU, а не паркуются вне потока
Уточняющие вопросы
- →Какие профили pprof вы бы сравнили, чтобы подтвердить диагноз I/O-bound?
- →Как троттлинг CPU-cgroup искажает картину CPU-bound против I/O-bound?
MiddleТеорияРедкоЧто такое Profile-Guided Optimization (PGO) в инструментарии Go?
Что такое Profile-Guided Optimization (PGO) в инструментарии Go?
Profile-Guided Optimization, стабильна с Go 1.21, передаёт собранный в runtime CPU-профиль обратно в компилятор. Вы кладёте его как default.pgo в main-пакет, и go build подхватывает файл без флагов, оптимизируя горячие пути через inlining и девиртуализацию.
Типичные ошибки
- ✗Путать PGO с JIT или перекомпиляцией в runtime — это оптимизация на этапе компиляции, во время сборки
- ✗Считать, что pprof и PGO — одно и то же; pprof для людей, а PGO отдаёт профиль компилятору
- ✗Думать, что PGO нужен особый флаг CLI, а не файл
default.pgo, которыйgo buildнаходит сам
Уточняющие вопросы
- →Как устаревший или нерепрезентативный профиль влияет на PGO-сборку?
- →Почему девиртуализация вызовов через интерфейс применяется только к горячим точкам вызова?