Вопрос проверяет умение системно исследовать проблемный эндпоинт: от базовой диагностики до оптимизации кода, БД и внешних вызовов.
Если эндпоинт стал медленным, сначала нужно измерить фактическое время выполнения и собрать метрики: латентность, количество запросов, нагрузку на CPU и БД. Затем важно локализовать «узкое место»: это может быть медленный запрос к базе данных, внешнему API, тяжёлые вычисления или неоптимальный код. Для этого используют профилирование, логирование таймингов по шагам внутри обработчика и анализ SQL-запросов (EXPLAIN/ANALYZE). После выявления причины применяют соответствующую оптимизацию: добавляют индексы, кэширование, асинхронные вызовы, выносят тяжёлые операции в фоновые задачи или упрощают бизнес-логику.
Прежде чем оптимизировать, важно понять, как именно проявляется проблема.
Какие симптомы:
Увеличилось среднее время ответа (latency).
Появились таймауты на стороне клиента.
Поднялась нагрузка на CPU/БД.
Уменьшился throughput (запросов в секунду).
Какие данные нужно собрать:
Логи с временем начала и окончания запроса.
Метрики по эндпоинту (p95, p99 latency).
Ошибки и таймауты в логах.
Определение.
Latency (задержка) — время между приходом запроса и отправкой ответа.
После того как ясно, что именно эндпоинт медленный, нужно понять, какая его часть тормозит.
Подход:
Разбить обработчик на логические шаги:
Парсинг входных данных.
Запросы к БД.
Вызовы внешних API.
Обработка, преобразование данных.
Формирование ответа.
Замерять время выполнения каждого шага:
Через логирование.
Через встроенные метрики.
Python
import time
from fastapi import APIRouter
router = APIRouter()
@router.get("/items")
async def get_items():
t0 = time.perf_counter()
items = await load_items_from_db()
t1 = time.perf_counter()
processed = process_items(items)
t2 = time.perf_counter()
# логируем тайминги
print("db:", t1 - t0, "processing:", t2 - t1)
return processed
По этим логам можно понять, где «провал».
База данных:
Медленные запросы без индексов.
Слишком много JOIN.
SELECT * по большой таблице.
Лишние UPDATE/INSERT в цикле.
Внешние сервисы:
Долгий ответ третьей стороны.
Последовательные запросы вместо параллельных.
Отсутствие таймаутов и ретраев.
Тяжёлые вычисления:
Сложные циклы и алгоритмы.
Ненужные преобразования больших структур.
Ошибки в асинхронном коде:
Блокирующие вызовы внутри async (например, requests вместо httpx).
Забытый await, из-за чего корутина не выполняется вовремя.
Логирование:
Лог времени обработки.
Лог SQL-запросов.
Профилирование:
Профайлеры Python (например, cProfile, py-spy).
Профилировщики на уровне инфраструктуры.
Анализ SQL:
EXPLAIN / EXPLAIN ANALYZE для проблемных запросов.
Поиск необходимости индексов.
Метрики:
Prometheus / Grafana для визуализации латентности.
Разделение по эндпоинтам и типам ошибок.
Оптимизация БД:
Добавить индексы по нужным фильтрам.
Упростить запрос (меньше JOIN, меньше данных).
Использовать пагинацию вместо огромных выборок.
Оптимизация кода:
Убрать лишние вычисления.
Не перебирать большие коллекции в Python, если можно сделать это в БД.
Избегать повторных вычислений (кэшировать).
Внешние сервисы:
Добавить таймауты.
Делать запросы параллельно (asyncio.gather).
Кэшировать результаты там, где это безопасно.
Архитектурные изменения:
Вынести тяжёлые операции в фоновые задачи (Celery, RQ, background tasks).
Разделить один «толстый» эндпоинт на несколько более простых.
Ввести кэширование на уровне API (например, Redis).
Python (пример параллельных запросов)
import httpx
import asyncio
async def fetch_all():
async with httpx.AsyncClient() as client:
r1, r2 = await asyncio.gather(
client.get("https://service1"),
client.get("https://service2"),
)
return r1.json(), r2.json()
Диагностика медленного эндпоинта всегда начинается с измерений: нужно понять, где именно тратится время. Затем по шагам проверяются БД, внешние вызовы и собственный код. После локализации проблемы используются конкретные техники оптимизации: индексы, кэширование, асинхронность, фоновая обработка и упрощение логики.