Цели модуля
После модуля студент сможет:
- Использовать pprof для CPU и memory profiling
- Анализировать выполнение программы через go tool trace
- Находить и устранять memory leaks
- Оптимизировать CPU-затратные операции
- Проводить нагрузочное тестирование с k6
- Писать и интерпретировать benchmarks
- Применять паттерны оптимизации (sync.Pool, object reuse)
- Устанавливать performance SLO и мониторить деградации
9.1 Performance mindset
Преждевременная оптимизация
"Premature optimization is the root of all evil." — Donald Knuth
Правила:
- Сначала измерь, потом оптимизируй
- Оптимизируй bottleneck, не весь код
- Оптимизация должна быть обоснована метриками
- Простота и читаемость важнее микрооптимизаций
Когда оптимизировать
Оптимизация нужна когда:
- SLO по latency нарушается
- Ресурсы (CPU/memory) на пределе
- Стоимость инфраструктуры растёт
- Пользователи жалуются на медленную работу
Оптимизация НЕ нужна когда:
- Код медленный, но SLO выполняется
- Оптимизация усложняет код без измеримой пользы
- Нет профилирования (оптимизация вслепую)
Цикл оптимизации
1. Измерь (profile, benchmark)
2. Найди bottleneck
3. Оптимизируй bottleneck
4. Измерь снова
5. Повтори если нужно
Без измерения оптимизация — гадание.
Performance SLO
Определите целевые показатели:
| Метрика | Цель | Как измерить |
|---|---|---|
| p50 latency | < 50ms | histogram_quantile(0.5, ...) |
| p99 latency | < 200ms | histogram_quantile(0.99, ...) |
| Throughput | > 1000 RPS | rate(http_requests_total[5m]) |
| CPU usage | < 70% | container_cpu_usage_seconds_total |
| Memory usage | < 512MB | container_memory_usage_bytes |
| GC pause | < 1ms | go_gc_duration_seconds |
9.2 Profiling с pprof
Что такое pprof
pprof — инструмент профилирования в Go. Позволяет увидеть:
- Какие функции потребляют больше всего CPU
- Какие функции аллоцируют больше всего памяти
- Где происходят блокировки (mutex, block)
- Сколько горутин запущено и где они заблокированы
Типы профилей
| Профиль | Что показывает |
|---|---|
| cpu | Использование CPU по функциям |
| heap | Аллокации памяти (текущие) |
| allocs | Все аллокации за время работы |
| goroutine | Стек всех горутин |
| mutex | Конкуренция за мьютексы |
| block | Блокировки на каналах/мьютексах |
| threadcreate | Создание потоков ОС |
Подключение pprof к HTTP серверу
goimport _ "net/http/pprof" func main() { // pprof endpoints на отдельном порту (безопасность!) go func() { log.Println(http.ListenAndServe(":6060", nil)) }() // Основной сервер на :8080 // ... }
Это добавляет эндпоинты:
- /debug/pprof/ — индекс
- /debug/pprof/profile — CPU профиль
- /debug/pprof/heap — heap профиль
- /debug/pprof/goroutine — горутины
- /debug/pprof/mutex — mutex профиль
- /debug/pprof/block — block профиль
ВАЖНО: не expose pprof на публичный порт. Используйте отдельный внутренний порт или аутентификацию.
Сбор CPU профиля
bash# Через HTTP endpoint (30 секунд профилирования) go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 # Сохранить в файл curl -o cpu.prof http://localhost:6060/debug/pprof/profile?seconds=30 go tool pprof cpu.prof
Сбор heap профиля
bash# Текущий heap curl -o heap.prof http://localhost:6060/debug/pprof/heap go tool pprof heap.prof # Все аллокации curl -o allocs.prof http://localhost:6060/debug/pprof/allocs go tool pprof allocs.prof
Интерактивный режим pprof
bash$ go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 (pprof) top # Топ функций по CPU (pprof) top 20 # Топ 20 функций (pprof) list FuncName # Показать код функции с CPU (pprof) web # Открыть граф вызовов в браузере (pprof) peek FuncName # Кто вызывает функцию (pprof) tree # Дерево вызовов
Пример вывода top
(pprof) top
Showing nodes accounting for 5.23s, 85.4% of 6.12s total
flat flat% sum% cum cum%
2.15s 35.13% 35.13% 2.15s 35.13% encoding/json.(*Encoder).Encode
1.23s 20.10% 55.23% 1.23s 20.10% github.com/jackc/pgx/v5.(*Conn).Query
0.89s 14.54% 69.77% 0.89s 14.54% runtime.mallocgc
0.56s 9.15% 78.92% 0.56s 9.15% sync.(*Pool).Get
Чтение:
- flat: время в самой функции
- cum: время в функции + вызванных из неё
- flat%: процент от общего времени
Визуализация через web UI
bash# Веб-интерфейс pprof go tool pprof -http=:8081 cpu.prof
Откроется браузер с:
- Flame graph (огненный граф)
- Graph (граф вызовов)
- Top (топ функций)
- Source (код с аннотациями)
Профилирование в production
Для production используйте непрерывный profiling:
- Pyroscope (Grafana)
- Parca
- Datadog Continuous Profiler
goimport "github.com/grafana/pyroscope-go" func main() { pyroscope.Start(pyroscope.Config{ ApplicationName: "git-server", ServerAddress: "http://pyroscope:4040", }) // ... }
9.3 go tool trace
Что такое trace
go tool trace показывает выполнение программы во времени:
- Горутины и их состояние (running, blocked, waiting)
- Системные вызовы
- GC паузы
- Сетевые операции
- Планировщик горутин
Когда использовать trace
- Диагностика latency (почему запрос медленный)
- Поиск гонок и блокировок
- Анализ работы планировщика
- Понимание параллелизма
Сбор trace
Через HTTP endpoint
bashcurl -o trace.out http://localhost:6060/debug/pprof/trace?seconds=5
Через код
goimport "runtime/trace" func main() { f, _ := os.Create("trace.out") trace.Start(f) defer trace.Stop() // Код для профилирования }
Для тестов
bashgo test -trace=trace.out ./...
Просмотр trace
bashgo tool trace trace.out
Откроется веб-интерфейс с:
- View trace: временная диаграмма всех горутин
- Goroutine analysis: анализ горутин
- Network/Sync/Syscall blocking profiles
- Scheduler latency profile
Чтение trace
Временная диаграмма показывает:
- Ось X: время
- Ось Y: процессоры (P) и горутины (G)
- Цвета: running (зелёный), blocked (красный), waiting (жёлтый)
Ищем:
- Длинные блокировки (красные участки)
- Простой процессоров (пустые участки)
- GC паузы (серые участки)
- Network blocking (долгие сетевые вызовы)
9.4 Memory leaks диагностика
Что такое memory leak в Go
Memory leak — объекты остаются в памяти потому что на них есть ссылки, хотя они больше не нужны.
Частые причины:
- Забытые горутины (goroutine leak)
- Глобальные мапы которые только растут
- Незакрытые ресурсы (connections, files)
- Кэши без eviction policy
- Подписки без отписки
Goroutine leak
Горутина заблокирована навсегда:
go// УТЕЧКА: горутина ждёт на канале без писателя func leak() { ch := make(chan int) go func() { val := <-ch // заблокирована навсегда process(val) }() // выходим из функции, горутина осталась }
Диагностика:
bash# Количество горутин растёт со временем curl http://localhost:6060/debug/pprof/goroutine?debug=1 # Или метрика go_goroutines # должно быть стабильным
Исправление:
go// ПРАВИЛЬНО: используем context для отмены func noLeak(ctx context.Context) { ch := make(chan int) go func() { select { case val := <-ch: process(val) case <-ctx.Done(): return // выходим при отмене } }() }
Глобальные мапы без очистки
go// УТЕЧКА: мапа только растёт var cache = make(map[string]*Data) func AddToCache(key string, data *Data) { cache[key] = data // никогда не удаляется! }
Исправление:
go// ПРАВИЛЬНО: кэш с TTL и eviction type Cache struct { mu sync.Mutex items map[string]CacheItem ttl time.Duration } type CacheItem struct { Data *Data ExpiresAt time.Time } func (c *Cache) Cleanup() { c.mu.Lock() defer c.mu.Unlock() now := time.Now() for key, item := range c.items { if now.After(item.ExpiresAt) { delete(c.items, key) } } }
Незакрытые ресурсы
go// УТЕЧКА: connection не закрыт func query() { conn := pool.Acquire(ctx) conn.Query(ctx, "SELECT ...") // забыли conn.Release()! }
Исправнение:
go// ПРАВИЛЬНО: defer для закрытия func query(ctx context.Context) error { conn, err := pool.Acquire(ctx) if err != nil { return err } defer conn.Release() _, err = conn.Query(ctx, "SELECT ...") return err }
Диагностика memory leak
- Мониторинг memory usage:
# Метрика
process_resident_memory_bytes{job="myapp"}
# Должно быть стабильным. Если растёт — leak.
- Heap профиль:
bash# Снимаем heap профиль в начале curl -o heap1.prof http://localhost:6060/debug/pprof/heap # Ждём 30 минут, снимаем снова curl -o heap2.prof http://localhost:6060/debug/pprof/heap # Сравниваем go tool pprof -diff_base=heap1.prof heap2.prof
- Goroutine профиль:
bash# Если количество горутин растёт — goroutine leak curl http://localhost:6060/debug/pprof/goroutine?debug=1 | head -5 # goroutine profile: version 1, 1543 goroutines
9.5 CPU optimization
Типичные CPU bottlenecks
- Сериализация/десериализация JSON
- Регулярные выражения
- Шифрование/хеширование
- Аллокации памяти (GC overhead)
- Логирование
Оптимизация JSON
go// ПЛОХО: encoding/json медленный для hot paths data, err := json.Marshal(obj) // БЫСТРЕЕ: pre-allocated буфер buf := make([]byte, 0, 1024) encoder := json.NewEncoder(bytes.NewBuffer(buf)) err := encoder.Encode(obj) // ЕЩЁ БЫСТРЕЕ: sonic, jsoniter (внешние библиотеки) import "github.com/bytedance/sonic" data, err := sonic.Marshal(obj)
Оптимизация регулярных выражений
go// ПЛОХО: компиляция regex на каждый вызов func validate(email string) bool { re := regexp.MustCompile(`^[a-z]+@[a-z]+\.[a-z]+$`) return re.MatchString(email) } // ПРАВИЛЬНО: компиляция один раз var emailRegex = regexp.MustCompile(`^[a-z]+@[a-z]+\.[a-z]+$`) func validate(email string) bool { return emailRegex.MatchString(email) }
Оптимизация строк
go// ПЛОХО: конкатенация в цикле (O(n^2) аллокаций) result := "" for _, s := range items { result += s } // ПРАВИЛЬНО: strings.Builder var builder strings.Builder builder.Grow(estimatedSize) // pre-allocate for _, s := range items { builder.WriteString(s) } result := builder.String()
Оптимизация аллокаций
go// ПЛОХО: аллокация на каждый запрос func handler(w http.ResponseWriter, r *http.Request) { buf := make([]byte, 4096) // аллокация // ... } // ПРАВИЛЬНО: sync.Pool для переиспользования var bufPool = sync.Pool{ New: func() interface{} { return make([]byte, 4096) }, } func handler(w http.ResponseWriter, r *http.Request) { buf := bufPool.Get().([]byte) defer bufPool.Put(buf) // ... }
Оптимизация логирования
go// ПЛОХО: логирование в hot path с форматированием for _, item := range items { log.Printf("processing item %s with data %v", item.ID, item.Data) } // ПРАВИЛЬНО: проверяем уровень перед логированием if logger.IsEnabled(slog.LevelDebug) { for _, item := range items { logger.Debug("processing item", "id", item.ID) } }
9.6 Нагрузочное тестирование с k6
Что такое k6
k6 — инструмент нагрузочного тестирования. Пишется на JavaScript, запускается как Go бинарник.
Установка:
bash# macOS brew install k6 # Linux sudo apt install k6 # Docker docker pull grafana/k6
Базовый тест
js// k6/basic-test.js import http from 'k6/http'; import { check, sleep } from 'k6'; export const options = { vus: 50, // 50 виртуальных пользователей duration: '30s', // в течение 30 секунд }; export default function () { const res = http.get('http://localhost:8080/api/v1/repos'); check(res, { 'status is 200': (r) => r.status === 200, 'response time < 200ms': (r) => r.timings.duration < 200, }); sleep(1); }
Запуск:
bashk6 run k6/basic-test.js
Ramp-up тест
js// k6/rampup-test.js import http from 'k6/http'; import { check, sleep } from 'k6'; export const options = { stages: [ { duration: '1m', target: 50 }, // Ramp up до 50 VU за 1 минуту { duration: '3m', target: 50 }, // Держим 50 VU 3 минуты { duration: '1m', target: 200 }, // Ramp up до 200 VU { duration: '3m', target: 200 }, // Держим 200 VU 3 минуты { duration: '1m', target: 0 }, // Ramp down до 0 ], thresholds: { http_req_duration: ['p(99)<500'], // p99 < 500ms http_req_failed: ['rate<0.01'], // < 1% ошибок }, }; export default function () { const res = http.get('http://localhost:8080/api/v1/repos'); check(res, { 'status is 200': (r) => r.status === 200, }); sleep(1); }
Stress тест (поиск предела)
js// k6/stress-test.js import http from 'k6/http'; export const options = { stages: [ { duration: '2m', target: 100 }, { duration: '2m', target: 500 }, { duration: '2m', target: 1000 }, { duration: '2m', target: 2000 }, // Ищем где ломается { duration: '5m', target: 2000 }, ], }; export default function () { http.get('http://localhost:8080/api/v1/repos'); }
Spike тест (пиковая нагрузка)
js// k6/spike-test.js import http from 'k6/http'; export const options = { stages: [ { duration: '10s', target: 100 }, // Нормальная нагрузка { duration: '10s', target: 100 }, { duration: '5s', target: 5000 }, // SPIKE! { duration: '10s', target: 5000 }, { duration: '10s', target: 100 }, // Возврат к норме ], }; export default function () { http.get('http://localhost:8080/api/v1/repos'); }
Тест с POST запросами
js// k6/create-test.js import http from 'k6/http'; import { check } from 'k6'; export const options = { vus: 20, duration: '1m', }; export default function () { const payload = JSON.stringify({ name: 'test-repo-' + __VU + '-' + __ITER, owner_id: 'user-123', is_private: false, }); const params = { headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer test-token', }, }; const res = http.post( 'http://localhost:8080/api/v1/repos', payload, params ); check(res, { 'status is 201': (r) => r.status === 201, 'has id': (r) => JSON.parse(r.body).id !== undefined, }); }
Интерпретация результатов k6
http_req_duration..............: avg=45.2ms min=12ms med=38ms max=450ms
p(90)=78ms p(95)=120ms p(99)=250ms
http_req_failed................: 0.5%
http_reqs......................: 15234 253.9/s
vus............................: 50
Ключевые метрики:
- http_req_duration: распределение задержек
- http_req_failed: процент ошибок
- http_reqs: throughput (запросов в секунду)
- vus: количество виртуальных пользователей
Thresholds (критерии прохождения)
jsexport const options = { thresholds: { // p99 latency < 500ms http_req_duration: ['p(99)<500'], // Error rate < 1% http_req_failed: ['rate<0.01'], // Throughput > 1000 RPS http_reqs: ['rate>1000'], // p95 < 200ms для конкретного эндпоинта 'http_req_duration{endpoint:/api/v1/repos}': ['p(95)<200'], }, };
Если threshold не выполнен, k6 возвращает non-zero exit code. Используйте в CI для автоматической проверки.
k6 в CI
yaml// .github/workflows/load-test.yml name: Load Test on: schedule: - cron: '0 2 * * 1' # Каждый понедельник в 2:00 jobs: load-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Start service run: docker compose up -d - name: Wait for service run: sleep 10 - name: Run k6 uses: grafana/k6-action@v0.3.0 with: filename: k6/rampup-test.js - name: Cleanup run: docker compose down --- ## 9.7 Benchmarks в Go ### Написание бенчмарков ```go // internal/service/repo_service_bench_test.go package service import ( "testing" ) func BenchmarkRepoService_GetRepo(b *testing.B) { // Setup mockRepo := NewMockRepoRepository(b) svc := NewRepoService(mockRepo) ctx := context.Background() // Reset timer после setup b.ResetTimer() for i := 0; i < b.N; i++ { svc.GetRepo(ctx, "repo-123") } } func BenchmarkJSONMarshal(b *testing.B) { data := &Repo{ ID: "repo-123", Name: "test-repo", } b.ResetTimer() for i := 0; i < b.N; i++ { json.Marshal(data) } }
Запуск бенчмарков
bash# Все бенчмарки в пакете go test -bench=. ./internal/service/ # Конкретный бенчмарк go test -bench=BenchmarkRepoService_GetRepo ./internal/service/ # С profiling go test -bench=. -cpuprofile=cpu.prof -memprofile=mem.prof ./internal/service/ # Больше итераций для точности go test -bench=. -benchtime=10s ./internal/service/
Чтение результатов
textBenchmarkRepoService_GetRepo-8 5000000 250 ns/op 128 B/op 2 allocs/op
Чтение:
- -8: GOMAXPROCS (количество CPU)
- 5000000: количество итераций
- 250 ns/op: среднее время на операцию
- 128 B/op: байт аллоцировано на операцию
- 2 allocs/op: количество аллокаций на операцию
Сравнение бенчмарков
Используйте benchstat для сравнения:
bash# Запускаем до оптимизации go test -bench=. -count=10 ./... > old.txt # Запускаем после оптимизации go test -bench=. -count=10 ./... > new.txt # Сравниваем go install golang.org/x/perf/cmd/benchstat@latest benchstat old.txt new.txt
Результат:
textname old time/op new time/op delta RepoService_GetRepo-8 250ns ± 5% 180ns ± 3% -28.0% (p=0.000)
9.8 Optimization patterns
sync.Pool
Переиспользование объектов для снижения аллокаций:
govar bufPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } func process(data []byte) string { buf := bufPool.Get().(*bytes.Buffer) defer func() { buf.Reset() bufPool.Put(buf) }() buf.Write(data) // ... обработка return buf.String() }
Когда использовать:
- Создание объекта дорогое
- Объект используется многократно
- Объект можно переиспользовать после Reset
Когда НЕ использовать:
- Объект содержит состояние которое нельзя сбросить
- Объект используется только один раз
Pre-allocation
go// ПЛОХО: append растёт массив динамически result := make([]Item, 0) for _, item := range items { result = append(result, transform(item)) } // ПРАВИЛЬНО: pre-allocate если знаем размер result := make([]Item, 0, len(items)) for _, item := range items { result = append(result, transform(item)) }
Избегание лишних аллокаций
go// ПЛОХО: передача по значению копирует большую структуру func process(data LargeStruct) { ... } // ПРАВИЛЬНО: передача по указателю func process(data *LargeStruct) { ... } // ПЛОХО: string([]byte) создаёт копию s := string(bytes) // ПРАВИЛЬНО: unsafe conversion (осторожно!) s := unsafe.String(&bytes[0], len(bytes))
Кэширование
gotype CachedService struct { underlying Service cache *Cache } func (s *CachedService) GetRepo(ctx context.Context, id string) (*Repo, error) { // Проверяем кэш if cached, ok := s.cache.Get("repo:" + id); ok { return cached.(*Repo), nil } // Кэш miss — идём в БД repo, err := s.underlying.GetRepo(ctx, id) if err != nil { return nil, err } // Сохраняем в кэш s.cache.Set("repo:"+id, repo, 5*time.Minute) return repo, nil }
Batch operations
go// ПЛОХО: N отдельных запросов for _, id := range ids { repo, _ := repoRepo.GetByID(ctx, id) } // ПРАВИЛЬНО: один batch запрос repos, _ := repoRepo.GetByIDs(ctx, ids) // Реализация func (r *RepoRepository) GetByIDs(ctx context.Context, ids []string) ([]*Repo, error) { query := "SELECT ... FROM repos WHERE id = ANY($1)" rows, err := r.pool.Query(ctx, query, ids) // ... }
Parallelism
go// ПЛОХО: последовательная обработка for _, item := range items { process(item) } // ПРАВИЛЬНО: параллельная обработка с errgroup g, ctx := errgroup.WithContext(ctx) g.SetLimit(10) // максимум 10 параллельных for _, item := range items { item := item g.Go(func() error { return process(ctx, item) }) } return g.Wait()
Избегание lock contention
go// ПЛОХО: один мьютекс на всё type Cache struct { mu sync.Mutex items map[string]interface{} } // ПРАВИЛЬНО: segmented cache (шардирование) type ShardedCache struct { shards [256]CacheShard } type CacheShard struct { mu sync.RWMutex items map[string]interface{} } func (c *ShardedCache) getShard(key string) *CacheShard { hash := fnv32(key) return &c.shards[hash%256] }
Привязка к шаблону проекта
Где performance в шаблоне
k6/
├── bulkhead.js # Нагрузочный тест bulkhead
└── cache-stampede.js # Тест cache stampede
internal/observability/metrics/
└── metrics.go # Метрики для мониторинга производительности
Что изучаем на примере шаблона
- k6 тесты для нагрузочного тестирования
- Метрики latency и throughput
- Мониторинг GC
- Connection pool метрики
Задание: изучите k6/
- Какие сценарии тестируются?
- Какие thresholds установлены?
- Как интерпретировать результаты?
💱 Компромиссы
sync.Poolvs прямые аллокации: переиспользование буферов снимает нагрузку с GC на hot path, но усложняет код и требует аккуратногоReset; обычные аллокации читаемее, однако платятallocs/opи паузами сборщика мусора.encoding/jsonvssonic: стандартная библиотека идёт без внешних зависимостей, но медленна на hot paths;sonic/jsoniterбыстрее, однако тянут зависимость и могут разойтись с stdlib в поведении.- Continuous profiling vs разовый
pprof: Pyroscope в production ловит редкие деградации с полным контекстом, но постоянно платит оверхедом CPU и хранением профилей; профиль по запросу бесплатен, однако проблема к моменту сбора уже прошла. - Stress/spike vs load-тесты: разгон до 2000 VU находит предел и bottleneck, но может уронить staging и испортить его данные; щадящий load-профиль безопасен, однако возможности сервиса остаются невыясненными.
- Кэш с TTL vs чтение из БД: кэш на 5 минут резко снижает latency и нагрузку на БД, но до истечения TTL отдаёт устаревшие данные; прямое чтение всегда актуально, однако платит каждым запросом в базу.
Практическое задание
Задание 1: CPU profiling
Подключите pprof к вашему сервису:
goimport _ "net/http/pprof" go func() { http.ListenAndServe(":6060", nil) }()
Соберите CPU профиль под нагрузкой:
bash# Запускаем нагрузку k6 run k6/basic-test.js & # Собираем профиль curl -o cpu.prof http://localhost:6060/debug/pprof/profile?seconds=30 # Анализируем go tool pprof -http=:8081 cpu.prof
Найдите топ-3 CPU-затратных функции.
Задание 2: Memory profiling
Соберите heap профиль:
bashcurl -o heap.prof http://localhost:6060/debug/pprof/heap go tool pprof -http=:8082 heap.prof
Найдите функции с наибольшими аллокациями.
Задание 3: Goroutine leak detection
Проверьте количество горутин под нагрузкой:
bash# До нагрузки curl http://localhost:6060/debug/pprof/goroutine?debug=1 | head -5 # После 10 минут нагрузки curl http://localhost:6060/debug/pprof/goroutine?debug=1 | head -5
Если количество выросло — найдите утечку.
Задание 4: Trace analysis
Соберите trace:
bashcurl -o trace.out http://localhost:6060/debug/pprof/trace?seconds=5 go tool trace trace.out
Найдите:
- Долгие блокировки
- GC паузы
- Простой процессоров
Задание 5: k6 load test
Напишите нагрузочный тест:
js// k6/load-test.js export const options = { stages: [ { duration: '1m', target: 50 }, { duration: '3m', target: 50 }, { duration: '1m', target: 200 }, { duration: '3m', target: 200 }, ], thresholds: { http_req_duration: ['p(99)<500'], http_req_failed: ['rate<0.01'], }, };
Запустите и проанализируйте результаты.
Задание 6: Stress test
Найдите предел вашего сервиса:
js// k6/stress-test.js export const options = { stages: [ { duration: '2m', target: 100 }, { duration: '2m', target: 500 }, { duration: '2m', target: 1000 }, { duration: '2m', target: 2000 }, ], };
Определите:
- Максимальный RPS до деградации
- Bottleneck (CPU, memory, DB, network)
- Рекомендации по масштабированию
Задание 7: Benchmarks
Напишите бенчмарки для критичных функций:
gofunc BenchmarkRepoService_GetRepo(b *testing.B) func BenchmarkJSONMarshal(b *testing.B) func BenchmarkRepoRepository_List(b *testing.B)
Запустите и запишите baseline.
Задание 8: Оптимизация
Выберите одну CPU-затратную функцию из profiling:
- Запишите baseline бенчмарк
- Оптимизируйте (sync.Pool, pre-allocation, кэширование)
- Запустите бенчмарк снова
- Сравните через benchstat
- Убедитесь что тесты проходят
Задание 9: Performance SLO
Определите performance SLO для вашего сервиса:
textp50 latency: < 50ms p99 latency: < 200ms Throughput: > 1000 RPS CPU usage: < 70%
Добавьте метрики и алерты для мониторинга.
Quiz "Проверь себя"
BenchmarkRepoService_GetRepo-8 5000000 250 ns/op 128 B/op 2 allocs/opvar bufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}✅ Итоги модуля
Студент должен:
- Использовать pprof для CPU и memory profiling
- Анализировать выполнение через go tool trace
- Находить и устранять memory leaks
- Оптимизировать CPU-затратные операции
- Писать и запускать k6 тесты (load, stress, spike)
- Писать и интерпретировать benchmarks
- Применять sync.Pool, pre-allocation, кэширование
- Устанавливать performance SLO
- Ответить на все вопросы Quiz
🔗 Мостик к Модулю 10
Следующий модуль: Модуль 10. CI/CD and Delivery - test pipeline design, security scanning, build artifacts, containerization, infrastructure as code, deployment automation with Terraform / Ansible, GitOps workflow. Привязка к .github/workflows/ и deploy/ шаблона.