# N+1 запросов в списках (изменение 019) Находка ревью № 9, серьёзность — низкая (производительность). ## Context Счётчики и связанные данные догружаются отдельными запросами на каждую строку: `_device_out` (2 запроса на устройство — до 1 000 на странице), `_isp_out` (организация), `_vrf_out`, `_type_out`, `_org_out` (счётчики), «Обзор» (`_prefix_outs` для всех активных префиксов с `_depths`/`_capacities` по организациям). ## Решение 1. **Пакетные выходные функции** — принимают список строк и делают фиксированное число запросов: - устройства: один запрос адресов `WHERE device_id IN (…)` + словарь типов (типов мало — один `SELECT` всех); - операторы: `selectinload(Isp.networks)` + словарь организаций по `IN`; - VRF / типы / организации: счётчики одним `GROUP BY` по `IN (…)`; - одиночные эндпоинты (`GET /devices/{id}` и т.п.) вызывают пакетную функцию со списком из одного элемента. 2. **Обзор:** считать ёмкость и загрузку одним SQL-запросом по листовым активным IPv4-префиксам (лист — префикс без детей) вместо полной сборки `PrefixOut` по всем префиксам; `top_prefixes` — `_prefix_outs` только для 4 выбранных. 3. Формат ответов не меняется. 4. Контроль: в тестовом режиме — счётчик запросов через событие `before_cursor_execute` (фикстура), чтобы зафиксировать «не больше K запросов на список». ## Файлы `app/api/v1/refs.py`, `app/api/v1/overview.py`, `app/api/v1/prefixes.py` (при необходимости), `tests/test_api.py`. ## Тест Один тест уровня функций (сессия SQLAlchemy к БД стенда, счётчик через `before_cursor_execute`): пакетный вывод 20 устройств — не больше 3 запросов. Внешние API-тесты счётчик не видят, поэтому тест вызывает функции напрямую. ## Проверка `pytest -q`; сравнение ответов API до/после на демо-данных (скрипт диффа JSON в scratchpad) — идентичны; замер времени `GET /devices?limit=500` на синтетических данных.