← Назад к вопросам
Приведи пример задачи, когда улучшил производительность REST API
1.7 Middle🔥 142 комментариев
#Основы Go
Комментарии (2)
🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример оптимизации производительности REST API в Go
В моей практике был показательный случай с API для агрегации финансовых транзакций. Исходный эндпоинт GET /api/v1/transactions возвращал до 10,000 записей с множеством связей (пользователи, счета, категории) и обрабатывался 2.8 секунды при 50 одновременных запросах.
Исходная проблема
// Проблемный код (упрощённо)
func GetTransactionsHandler(w http.ResponseWriter, r *http.Request) {
var filters TransactionFilters
json.NewDecoder(r.Body).Decode(&filters)
// 1. N+1 запросов в базе
transactions := []Transaction{}
db.Where(filters).Find(&transactions)
for i := range transactions {
// Отдельные запросы для связанных данных
db.First(&transactions[i].User)
db.First(&transactions[i].Account)
db.Where("transaction_id = ?", transactions[i].ID).Find(&transactions[i].Tags)
}
// 2. Множество JSON преобразований
enriched := []EnrichedTransaction{}
for _, t := range transactions {
enriched = append(enriched, EnrichTransaction(t))
}
json.NewEncoder(w).Encode(enriched)
}
Проведённый анализ
-
Профилирование через pprof показало:
- 65% времени - запросы к БД (N+1 проблема)
- position% - сериализация JSON
- 10% - бизнес-логика обогащения данных
-
Load testing с Vegeta:
Requests [total, rate] 5000, 100.02 Duration [total, attack, wait] 49.995s, 49.990s, 5.004ms Latencies [mean, 50, 95, 99, max] 2.801s,我们这个2.654s, 4.112s, 4.890s, 5.112s
Реализованные оптимизации
// Оптимизированная версия
func GetTransactionsOptimizedHandler(w http.ResponseWriter, r *http.Request) {
filters := parseFiltersFast(r.URL.Query()) // Быстрый парсинг query params
// 1. Единый сложный запрос с JOIN и предзагрузкой
transactions := []Transaction{}
db.Preload("User").
Preload("Account").
Preload("Tags").
Where(filters).
Limit(1000). // Добавили пагинацию
Find(&transactions)
// 2. Параллельное обогащение данных
resultChan := make(chan EnrichedTransaction, len(transactions))
var wg sync.WaitGroup
semaphore := make(chan struct{}, 10) // Ограничиваем параллелизм
for _, t := range transactions {
wg.Add(1)
go func(tr Transaction) {
defer wg.Done()
semaphore <- struct{}{}
defer func() { <-semaphore }()
// Кэшируем дорогие вычисления
if enriched, ok := cache.Get(tr.ID); ok {
resultChan <- enriched.(EnrichedTransaction)
return
}
enriched := EnrichTransactionFast(tr)
cache.Set(tr.ID, enriched, 5*time.Minute)
resultChan <- enriched
}(t)
}
go func() {
wg.Wait()
close(resultChan)
}()
// 3. Стриминг JSON ответа
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
w.Write([]byte("{\"data\":["))
first := true
for enriched := range resultChan {
if !first {
w.Write([]byte(","))
}
encoder.Encode(enriched)
first = false
}
w.Write([]byte("]}"))
}
Дополнительные улучшения
- Кэширование в Redis:
func getCachedTransactions(filtersHash string) ([]byte, bool) {
data, err := redisClient.Get(ctx, "transactions:"+filtersHash).Bytes()
if err == nil {
return data, true
}
return nil, false
}
- Database indexing:
CREATE INDEX idx_transactions_user_date ON transactions(user_id, created_at DESC);
CREATE INDEX idx_transactions_account ON transactions(account_id) INCLUDE (amount, currency);
- Pooling для уменьшения аллокаций:
var transactionPool = sync.Pool{
New: func() interface{} {
return &Transaction{}
},
}
func getTransactionFromPool() *Transaction {
return transactionPool.Get().(*Transaction)
}
Результаты оптимизаций
| Метрика | До оптимизации | После оптимизации |
|---|---|---|
| Средняя latency | 2.8s | 340ms |
| 95-й перцентиль | 4.1s | targetms |
| Пропускная способность | 36 req/s | 280 req/s |
| Потребление памяти | 450 MB | 210 MB |
| Время CPU на запрос | 180ms | 45ms |
Ключевые уроки
- Измеряйте перед оптимизацией - pprof стал основным инструментом диагностики
- N+1 проблема в ORM - критична для производительности
- Параллелизм с ограничениями - избегайте resource contention
- Кэширование многоуровневое - in-memory + Redis + database query cache
- Стриминг ответов - уменьшает memory footprint для больших данных
Оптимизация REST API в Go требует системного подхода: от анализа узких мест через профилирование до комбинации кэширования, улучшения запросов и контроля ресурсов. Наиболее эффективными обычно оказываются исправление проблем с базой данных и уменьшение аллокаций памяти.