Пара 23: Нагрузка, задержки и ограниченные повторы
90 минут · 3 курс, ML.
Содержание и результат
Провести небольшой контролируемый эксперимент и объяснить очередь и timeout.
План занятия
0–10: контекст и исходная задача. 10–40: устройство и механизмы. 40–55: демонстрация команд. 55–80: лабораторная работа. 80–90: разбор результата и фиксация исправлений.
Практика выполняется в своей учебной папке и на localhost. Подготовка окружения описана в lab/README.md.
Эксперимент с нагрузкой
Меняем один фактор и измеряем результат.
Скрипт load.py отправляет ограниченное число запросов к своему localhost и показывает статусы и длительности. Сравниваем concurrency=1 и 4 при одинаковом числе запросов. Это учебное измерение, а не production-бенчмарк: ноутбук, WSL, кеш и фоновые задачи влияют на результат. Для демонстрации DELAY_MS искусственно задерживает вычисление, чтобы увидеть связь нагрузки и времени. Это не имитация настоящей модели и не научная оценка её производительности. Перед опытом фиксируем настройки и версию, после возвращаем delay=0.
Аналогия: сравнение двух очередей возможно, если остальные условия одинаковы.
Среднее и медленный хвост
Один медленный запрос может быть важнее среднего.
Среднее зависит от распределения и может скрывать часть медленных запросов. p50 — медиана, p95 — хвостовая граница для большей части наблюдений. Прямой расчёт по 20 запросам и оценка histogram_quantile по buckets могут отличаться: разная выборка и дискретизация. Это не ошибка арифметики. Черезput и latency связаны с ограничением ресурсов и очередями. Не объявляйте прирост concurrency ускорением отдельного запроса: одновременно может увеличиться число ответов и ухудшиться время каждого. В таблице подписываем n и условия измерения.
Аналогия: среднее время ожидания не гарантирует, что никто не стоял час.
Timeout, retry и идемпотентность
Ограничиваем ожидание и не усиливаем сбой повторами.
Каждый сетевой вызов должен иметь разумный timeout. Retry помогает при некоторых временных ошибках, но повторять неверный вход 400 бесполезно. Число попыток ограничивают, задержку увеличивают, добавляют jitter, чтобы клиенты не повторяли синхронно. Повтор POST может дважды выполнить побочное действие, если сервер уже обработал запрос, но ответ потерялся. В StudyPulse POST сохраняет событие: повтор способен создать дубликат. В рабочем проекте контракт идемпотентности и ключи повторов нужно проектировать отдельно. На курсе не добавляем автоматический retry, который скрывает эту проблему.
Аналогия: если кассир не ответил, повторная оплата может списать деньги второй раз.
Команды и наблюдения
Окружение: Ubuntu / Bash, lab.
DELAY_MS=200 docker compose up -d --force-recreate app
python3 scripts/load.py --requests 40 --concurrency 1
python3 scripts/load.py --requests 40 --concurrency 4
curl --max-time 0.1 -H "Content-Type: application/json" -d '{"hours":4}' http://127.0.0.1:8000/predict
DELAY_MS=0 docker compose up -d --force-recreate app
Нагрузка имеет пределы; скрипт разрешает только loopback-адрес. Изменение delay передаётся через Compose environment, затем обязательно возвращается к 0.
Ожидаемый результат: Запрос curl с коротким timeout может оборваться до ответа; сервис мог уже начать обработку, поэтому повтор не объявляется безопасным автоматически.
Вариант для macOS
Метод эксперимента одинаков, но реальные числа нельзя напрямую сравнивать между ARM Mac и AMD64 WSL: CPU, виртуализация и эмуляция различаются. На каждой машине сравниваем серии при фиксированных условиях.
python3 scripts/load.py --requests 40 --concurrency 1
python3 scripts/load.py --requests 40 --concurrency 4
Практика: Один фактор, две серии
- Запишите версию, delay, число запросов и concurrency.
- Проведите две серии, сравните p50/p95 и суммарное время.
- Сделайте timeout короче delay, найдите событие запроса в логах.
- Объясните, когда retry допустим, а когда может создать дубликат; восстановите delay=0.
Результат: Таблица измерений и объяснение пределов выводов.
Проверка: Нагрузка только на свой ноутбук; задержка убрана; timeout не принимают за доказательство необработанного запроса.
Неисправность для разбора: Количество потоков повышают бесконечно, пытаясь «ускорить» сервис.
Решение и диагностика
Используйте scripts/load.py с максимумом 500 запросов и concurrency ≤ 16; для занятия достаточно 40/4. Запишите фактические результаты, не копируйте выдуманные числа. После curl timeout проверьте request-логи и количество событий: сервер мог завершить вычисление до обнаружения закрытого соединения. Вывод: статус клиента не всегда полностью описывает состояние серверной операции.
Основные выводы
- Операция могла уже выполниться и повтор создаст второе действие.
- Нет, часть запросов может быть медленнее.
Самостоятельная работа
Описать timeout/retry-политику для чтения healthz и для POST с побочным действием.