Как я перестал бояться и оптимизировал... - Авторский блог

Как я перестал бояться и оптимизировал...

Приветствую Вас дорогие друзья и посетители! Это практическое руководство по решению конкретной задачи — то, что люди ищут в поиске каждый день . Чем уже и больнее проблема, тем лучше.

Как я снизил время ответа API с 2 секунд до 200 мс: пошаговый чек-лист.

Сегодня хочу поделиться историей оптимизации одного из наших API. Было 2 секунды — стало 200 мс. Без полного переписывания кода, без магии — просто системный подход и устранение узких мест по чек-листу.

Сразу скажу: по данным исследований, 80% проблем с медленными API решаются через оптимизацию сети, кэширование и настройку базы данных . Сложные архитектурные изменения нужны реже, чем кажется.

🔍 Шаг 1. Профилирование: найти, где теряется время

Прежде чем что-то менять, я замерил, откуда берутся эти 2 секунды. Установил простое профилирование с замерами каждого этапа запроса :

javascript
const perfLog = {};
async function trackPerformance (label, fn) {
const start = Date.now ();
const result = await fn ();
perfLog[label] = Date.now () — start;
console.log (`${label}: ${Date.now () — start}ms`);
return result;
}

// Оборачиваем каждый этап
const user = await trackPerformance ('fetchUser', () => db.getUser (id));
const orders = await trackPerformance ('fetchOrders', () => db.getOrders (user.id));

// Оборачиваем каждый этап

const user = await trackPerformance ('fetchUser', () => db.getUser (id));

const orders = await trackPerformance ('fetchOrders', () => db.getOrders (user.id));

Что выяснилось (реальные цифры):

  1. Запрос к базе: 1200 мс 😱
  2. Сериализация JSON: 300 мс
  3. Внешний вызов стороннего API: 400 мс
  4. Всё остальное: 100 мс

Главный виновник — база данных. Остальное — тоже не подарок, но начинаем с самого жирного куска.

🗄️ Шаг 2. Оптимизация базы данных

Индексы — первая линия обороны. В нашем основном запросе не было индекса по полю, по которому шёл поиск. Добавил: sql

CREATE INDEX idx_user_status ON users (status);

Запрос с 800 мс упал до 120 мс. Простое решение, которое часто игнорируют .

Пул соединений

Вместо того чтобы открывать новое соединение для каждого запроса, настроил connection pooling. Это сократило накладные расходы на рукопожатие с БД .

yaml

# Пример для Spring Boot

spring:

datasource:

hikari:

maximum-pool-size: 10

connection-timeout: 30000

Шаг 3. Кэширование

Данные, которые редко меняются, но часто читаются (например, справочники, настройки пользователя), вынес в Redis.

Схема работы:

  • Проверяем кэш.
  • Если есть — отдаём (время ответа: 5–10 мс).
  • Если нет — идём в БД, сохраняем в Redis на 5 минут.

javascript

const cached = await redis.get (`user:${id}`);

if (cached) return JSON.parse (cached);

const user = await db.getUser (id);

await redis.setex (`user:${id}`, 300, JSON.stringify (user));

return user;

Это сняло нагрузку с базы примерно на 40% запросов .

🌐 Шаг 4. Сеть и сжатие

Включил Gzip

На уровне Nginx и в заголовках запросов :

text

Accept-Encoding: gzip

User-Agent: my-app (gzip)

Сжатие сократило размер ответа на 60–80%, а время передачи — с 200 до 60 мс для больших JSON-ответов .

Partial Response — отдаём только нужные поля

Раньше API возвращал весь объект целиком. Теперь клиент указывает через параметр fields, что ему нужно .

Запрос:

text

GET /api/users/123?fields=id,name,email

Вместо:

json

{ «id»: 1, «name»: «John», «email»: «j@ex.com», «createdAt»: «...», «updatedAt»: «...», «avatar»: «...», «settings»: {...} }

Отдаём только:

json

{ «id»: 1, «name»: «John», «email»: «j@ex.com» }

Экономия на передаче данных — до 70% .

Шаг 5. Асинхронная обработка

В API были операции, которые не нужны для немедленного ответа: отправка email-уведомлений, запись логов, аналитика. Я вынес их в очередь (использовали RabbitMQ).

Теперь API отвечает сразу, а «тяжёлые» задачи выполняются фоном :

Javascript

// Было (синхронно, +400 мс)

await sendEmail (order.email);

await logAnalytics (order);

// Стало (асинхронно)

queue.publish ('notifications', { type: 'email', data: order });

queue.publish ('analytics', { type: 'order', data: order });

// Ответ клиенту идёт мгновенно

🛡️ Шаг 6. Работа с внешними сервисами

Сторонний API для проверки платежей иногда отвечал долго.

Я добавил: Таймауты (если не отвечает за 1 секунду — отдаём fallback).

Circuit Breaker — если сервис падает, не пытаемся стучаться в него снова и снова, а быстро возвращаем ошибку или кэш .

📊 Итоги по чек-листу

Этап Что сделал Улучшение

  1. Профилирование Нашёл узкое место — база —
  2. Оптимизация БД Индексы + пул соединений 1200 мс → 120 мс
  3. Кэширование Redis для частых запросов−40% нагрузки на БД
  4. Сжатие Gzip + partial response200 мс → 60 мс
  5. Асинхронность Очереди для фоновых задач−400 мс ожидания
  6. Внешние сервисы Таймауты + Circuit Breaker Стабильность

Итоговое время: 200 мс ✅

💡 Главный вывод

Не нужно сразу переписывать архитектуру.

Сначала:

  1. Профилируй — найди главную проблему.
  2. Оптимизируй базу — индексы часто дают 90% эффекта.
  3. Кэшируй частое и неизменяемое.
  4. Сжимай и сокращай передаваемые данные.
  5. Выноси тяжёлое в фоновые задачи.

Вот так я решал эти задачи. С уважением к Вам Веб-программист

 

Добавить комментарий