Практическая работа №18: Профилирование и устранение блокировки TTFB
Цель работы: научиться находить, на каком узле цепочки обработки запроса теряется время до первого байта ответа (TTFB), и устранять задержку за счёт распараллеливания независимых операций и таймаутов на медленные внешние вызовы.
Основные теоретические положения
Section titled “Основные теоретические положения”1. Архитектура backend-приложения и путь запроса
Section titled “1. Архитектура backend-приложения и путь запроса”Типичный запрос проходит цепочку: nginx (реверс-прокси) → приложение (воркер PHP-FPM/Python WSGI или event loop Node.js/asyncio) → база данных / внешние сервисы → обратно клиенту. Задержка может возникнуть на любом из этих узлов, и без измерения каждого участка отдельно невозможно понять, где именно теряется время.
2. TTFB: из каких фаз складывается метрика
Section titled “2. TTFB: из каких фаз складывается метрика”Time To First Byte (TTFB) — время от отправки запроса до получения первого байта ответа. Складывается из сетевой задержки, времени ожидания в очереди перед свободным воркером и времени непосредственной обработки запроса приложением (включая все синхронные и асинхронные операции внутри обработчика). Диагностика начинается с логирования времени выполнения каждого шага внутри обработчика — без этого невозможно понять, какая именно операция “съедает” TTFB.
3. Последовательное и параллельное выполнение независимых операций
Section titled “3. Последовательное и параллельное выполнение независимых операций”Если обработчик запроса выполняет несколько операций, не зависящих друг от друга по данным (например, запрос профиля пользователя и запрос списка его последних заказов), их последовательное выполнение (await a(); await b();) складывает время обеих операций. Если же они выполняются параллельно (asyncio.gather(a(), b()) в Python, Promise.all([...]) в JavaScript), общее время равно времени самой долгой из них, а не их сумме. Операции, зависящие друг от друга по данным (результат одной нужен для вызова другой), распараллелить нельзя.
4. Таймауты и деградация функциональности вместо отказа
Section titled “4. Таймауты и деградация функциональности вместо отказа”Если один из внешних вызовов (например, сервис рекомендаций) может работать медленно или недоступен, вся страница не должна зависеть от его ответа. Решение — обернуть вызов в таймаут (asyncio.wait_for в Python) и, если он не уложился в отведённое время, вернуть страницу без соответствующего блока, а не заставлять пользователя ждать или показывать ошибку целиком.
Алгоритм выполнения работы
Section titled “Алгоритм выполнения работы”Получение исходного кода проекта
Section titled “Получение исходного кода проекта”Для выполнения работы потребуется стартовый проект с эмуляцией сервиса главной страницы.
- Клонирование с GitHub:
Terminal window git clone https://github.com/ghostITwe/optimization-web-apps.gitcd optimize-web-apps/lab18-ttfb
Установите зависимости:
pip install -r requirements.txt1. Изучение и анализ текущей реализации
Section titled “1. Изучение и анализ текущей реализации”- Откройте
starter/main.py— обработчикhandle_homepageвыполняет три операции:get_user,fetch_recommendations,get_recent_orders. - Запустите стартовый сервер:
Terminal window cd starteruvicorn main:app --reload --port 8000 - В другом терминале замерьте текущее время ответа:
Terminal window python ../benchmark.py http://127.0.0.1:8000/ - Зафиксируйте среднее время ответа (должно быть около 1.9–2.0 секунды) и определите по коду, какая из трёх операций вносит основной вклад в задержку.
2. Распараллеливание независимых операций
Section titled “2. Распараллеливание независимых операций”- Определите, какие из трёх операций не зависят друг от друга по данным (
fetch_recommendationsиget_recent_ordersне используют результат друг друга). - Перепишите обработчик так, чтобы эти две операции выполнялись параллельно через
asyncio.gather, а не последовательно через дваawaitподряд. - Сохраните изменения и повторно замерьте время ответа тем же скриптом
benchmark.py. Зафиксируйте новый результат.
3. Таймаут на медленный внешний вызов
Section titled “3. Таймаут на медленный внешний вызов”- Оберните вызов
fetch_recommendationsвasyncio.wait_forс таймаутом 500 миллисекунд. - Реализуйте деградацию: если таймаут сработал, страница должна вернуться без блока рекомендаций (пустой список), а не с ошибкой.
- Убедитесь, что при этом
get_userиget_recent_ordersпо-прежнему отрабатывают полностью.
4. Финальная проверка и сравнение
Section titled “4. Финальная проверка и сравнение”- Запустите автоматическую проверку:
Тест должен пройти — это означает, что ответ укладывается менее чем в 1 секунду.
Terminal window pip install pytest pytest-asyncio httpxpytest tests/test_ttfb.py -v - Сравните итоговое время ответа с результатом из пункта 1 (должно быть в 10+ раз быстрее — около 0.13 секунды у эталонного решения).
- Подготовьте краткий отчёт: время “до”/“после”, какая операция была причиной задержки, почему
get_userнельзя было распараллелить с остальными.
Контрольные вопросы
Section titled “Контрольные вопросы”- Из каких составляющих складывается TTFB? Какая из них оказалась узким местом в этой работе?
- Почему
asyncio.gatherуменьшает именно суммарное время ответа, а не нагрузку на CPU? - По какому признаку определяется, можно ли распараллелить две операции внутри одного обработчика, а какие — нельзя?
- Зачем нужен таймаут на внешний вызов рекомендаций, если можно просто дождаться его ответа?
- Что произойдёт с TTFB, если сервис рекомендаций будет отвечать не за 1.8 секунды, а зависать бесконечно, и почему в решении с таймаутом это не страшно?