Какие метрики использовать для оценки пропускной способности без генерации нагрузки
Что проверяют
Умеете ли вы оценить запас мощности по данным из прода, не запуская нагрузочный тест, и понимаете ли, где такая оценка перестаёт работать. Следом обычно спрашивают, сколько RPS выдержит ваш сервис и во что он упрётся первым.
Как отвечать
Идея в том, чтобы измерить стоимость одного запроса и число параллельных обработчиков, а потом посчитать потолок.
- текущая пропускная способность: RPS по маршрутам, пиковые значения за неделю из метрик или access-логов;
- время обслуживания: не только время ответа, но и CPU-время на запрос. Если оно 20 мс, одно ядро теоретически обслужит около 50 запросов в секунду;
- параллелизм: в стандартной сборке CPython из-за GIL Python-код одного процесса исполняется на одном ядре, поэтому считают процессы. У gunicorn с синхронными воркерами потолок примерно
workers × threads / время_обслуживания: 8 воркеров при 100 мс дают порядка 80 RPS; - насыщение: занятость воркеров, очередь соединений на входе, ожидание соединения из пула к базе, задержка event loop в asyncio-сервисах;
- утилизация ресурсов: CPU, память, сеть и те же показатели у зависимостей, прежде всего у базы.
Связывает это закон Литтла: среднее число запросов в системе равно интенсивности входящего потока, умноженной на среднее время пребывания. Если в работе в среднем 6 запросов при 8 воркерах, запас есть; если все 8 заняты постоянно, очередь вот-вот начнёт расти.
Где тратится время, покажет py-spy: он снимает профиль с работающего процесса без перезапуска.
Где спотыкаются
Экстраполируют линейно: 25% CPU при 200 RPS, значит 800 RPS при 100%. Задержка растёт нелинейно задолго до полной загрузки, и при утилизации выше 70% очереди обычно уже заметны.
Считают потоки параллельными ядрами. Для CPU-bound кода потоки упираются в GIL, прирост дают только процессы.
Смотрят только на сервис и не видят узкого места за ним. Часто потолок задаёт база или внешний API, и новые воркеры лишь удлинят очередь к ним.