NetBox API отдаёт не все устройства: разбор пагинации
АйТи Фреш
Linux, Docker и DevOps

Почему REST API и GraphQL в NetBox отдают не все устройства: лимиты и правильная пагинация

Автор: , директор ООО «АйТи-Фреш» · · ~14 мин чтения
Пагинация в NetBox API: часть устройств застревает без обработки страниц, а с постраничным обходом проходят все
Без обработки пагинации API отдаёт только первую страницу устройств — и это ожидаемое поведение, не сбой.

Скрипт запросил список устройств в NetBox, получил 50 штук и решил, что это всё — а в инвентаре их триста. Причина не в сети: REST API отдаёт списки страницами по PAGINATE_COUNT = 50, а GraphQL с версии 4.5.5 тоже режет выдачу — по MAX_PAGE_SIZE. Показываю, как забрать весь список и что изменилось после апгрейда.

Симптом: интеграция видит только часть устройств

Жалоба обычно звучит так: «В NetBox триста с лишним устройств, а наш скрипт синхронизации с системой мониторинга видит пятьдесят и на этом останавливается». Первая мысль — сломался фильтр в запросе, но фильтр тут ни при чём: NetBox отдаёт списочные ответы страницами, и без явной обработки пагинации любой клиент REST API получит только первую страницу — по умолчанию 50 объектов, — и молча решит, что это весь список. Именно с этого я начинаю разбор интеграций, когда веду внедрение NetBox для клиента, у которого уже есть внешние системы, которые должны читать инвентарь.

Второй, менее очевидный вариант того же симптома — GraphQL-запрос, который раньше отдавал полный список, а после обновления NetBox стал внезапно усекать результат. Здесь причина другая: до определённой версии лимит страницы, который годами работал только для REST, на GraphQL не распространялся, а начиная с 4.5.5 распространяется. Разберу оба случая по очереди, потому что чинятся они разными приёмами.

Сравнение лимитов пагинации REST API и GraphQL API в NetBox, включая изменение с версии 4.5.5
У REST и GraphQL разные умолчания и разная история — не переносите логику одного API на другой.

REST API: PAGINATE_COUNT, MAX_PAGE_SIZE и как забрать всё

У REST API NetBox два разных параметра конфигурации, которые часто путают. PAGINATE_COUNT — это размер страницы по умолчанию, когда клиент не указал limit явно; по документации он равен 50. MAX_PAGE_SIZE — это верхний предел, больше которого сервер не отдаст объектов за один запрос, даже если клиент попросит больше; по умолчанию это 1000. Клиент может явно запросить ?limit=100, ?limit=500 — сервер отдаст ровно столько, но не больше значения MAX_PAGE_SIZE.

Списочный ответ REST API всегда содержит четыре поля: count — общее число объектов, удовлетворяющих запросу, next и previous — ссылки на соседние страницы (или null, если страницы нет), и results — сами объекты текущей страницы. Правильная интеграция должна либо идти по next, пока он не станет null, либо, если объём разумный, один раз запросить всё через ?limit=0 — документация прямо описывает это как способ получить все совпадающие объекты одним запросом.

Здесь важна оговорка: ?limit=0 работает только тогда, когда на сервере MAX_PAGE_SIZE не ограничивает выдачу — то есть значение параметра установлено в 0 или None в конфигурации NetBox. Если администратор сервера оставил MAX_PAGE_SIZE по умолчанию (1000), ?limit=0 не даст вам обойти этот потолок — вы получите не более 1000 объектов за запрос, и на инвентаре в несколько тысяч устройств всё равно понадобится пагинация через next, даже с limit=0 в URL. С NetBox 4.6.0 (5 мая 2026 года, задача #21363) у REST появился и курсорный режим: ?start=0&limit=1000 отдаёт объекты с id не меньше start, отсортированные по id, а ссылка next сама подставляет следующий start. Учтите две детали: start и offset в одном запросе несовместимы (сервер ответит 400), а count в курсорном режиме всегда null — общее число объектов для сверки придётся запрашивать отдельным обычным запросом.

PAGINATE_COUNT (по умолчанию 50) — сколько отдаётся без явного limit. MAX_PAGE_SIZE (по умолчанию 1000) — сколько можно попросить максимум, включая через limit=0.

Экономим трафик: fields, brief и omit

Отдельная, но смежная тема — объём данных на каждый объект. Если для синхронизации с внешней системой нужны только id, имя и статус устройства, а не полная вложенная структура со всеми связями, документация REST API даёт параметр ?fields=id,name,status — сервер вернёт только перечисленные поля вместо полного представления объекта. Для типового устройства это разница на порядок в объёме ответа, что особенно ощутимо при выгрузке нескольких тысяч записей постранично.

Есть и обратный параметр omit — исключить конкретные поля из полного представления, когда нужно почти всё, кроме одного тяжёлого блока. А для случаев, когда клиенту достаточно минимального краткого представления объекта, есть параметр brief=true. Комбинация постраничного обхода через next и урезанного набора полей через fields — это то, с чего я советую начинать любую интеграцию, которая читает инвентарь целиком на регулярной основе, а не разово.

Пошаговая схема курсорной пагинации GraphQL API NetBox по первичному ключу id
Курсор по id — единственный способ обхода GraphQL, который не зависит от значения MAX_PAGE_SIZE на сервере.

GraphQL до и после NetBox 4.5.5: тихое изменение поведения

У GraphQL API своя механика и своя история. Долгое время параметр MAX_PAGE_SIZE, ограничивающий REST, попросту не действовал на GraphQL: сопровождающие NetBox завели это как баг #20385 ещё на версии 4.4.1 — при MAX_PAGE_SIZE = 10 запрос с pagination: {limit: 100} возвращал сто записей. Исправление вошло в 4.5.5 (17 марта 2026 года). Как сформулировано в последовавшем issue #21935: до 4.5.4 включительно базовые списочные запросы вида device_list { id name } были неограниченными, а начиная с 4.5.5 они режутся до MAX_PAGE_SIZE — даже без явной пагинации.

Для тех, кто обновлял NetBox поэтапно, это ощущается как тихая порча интеграции: запрос, который годами возвращал весь список устройств, после апгрейда до 4.5.5 или новее вдруг стал отдавать не больше значения MAX_PAGE_SIZE — по умолчанию 1000 объектов, — без единой ошибки в логах, просто усечённый результат. Формально это исправление бага, а не новая функция, поэтому в release notes оно заняло одну строку в списке багфиксов. При этом документация какое-то время описывала MAX_PAGE_SIZE только применительно к REST — это и зафиксировал issue #21935, закрытый правкой документации в milestone 4.5.9. Сейчас в описании параметра прямо сказано, что он действует на веб-интерфейс, REST и GraphQL.

Правильный способ получить весь список через GraphQL при включённом MAX_PAGE_SIZE — курсорная пагинация по первичному ключу, которая появилась в NetBox 4.5.2 (3 февраля 2026 года, задача #21110). Синтаксис такой: device_list(pagination: {start: 0, limit: 20}) — сервер вернёт объекты с id не меньше указанного start, отсортированные по возрастанию первичного ключа, числом не больше limit. Чтобы получить следующую страницу, в качестве start берётся id последнего полученного объекта плюс один — и так до тех пор, пока страница не вернётся пустой.

GraphQL без пагинации — это не «весь список» с версии 4.5.5 и новее, а до MAX_PAGE_SIZE объектов. Дальше — только курсорная пагинация по id.

Одна деталь, которая ломает автоматический обход GraphQL

Есть нюанс, который стоит проверить отдельно перед тем, как переписывать цикл обхода: поведение pagination: {limit: 0} в GraphQL отличается от ?limit=0 в REST. Если в конфигурации MAX_PAGE_SIZE снят (0 или None), запрос REST с ?limit=0 вернёт все объекты — а вот GraphQL-запрос с pagination: {limit: 0} в тех же условиях вернёт ноль записей. Это прямо противоположный REST результат при формально похожем параметре, и если код обхода писался по аналогии с REST, «оптимизация» через limit: 0 в GraphQL молча даст пустой ответ вместо полного.

Второй тонкий момент — что происходит, если лимит в GraphQL-запросе не указан. Пока MAX_PAGE_SIZE задан (по умолчанию 1000), запрос без limit получает до MAX_PAGE_SIZE записей — это и есть та самая «тихая» обрезка. Если же MAX_PAGE_SIZE снят (0 или None), поведение распадается на три случая: без аргумента pagination вообще сервер вернёт все записи; с pagination, но без limit, сработает умолчание библиотеки Strawberry Django, на которой построен GraphQL-слой NetBox, — 100 записей; а pagination: {limit: 0} вернёт ноль. То есть отсутствие пагинации в запросе — это не универсальный «дай всё», а поведение, зависящее от серверной конфигурации, которую вы, скорее всего, не контролируете, если работаете с чужим инстансом NetBox.

Кейс «Импульс медиа»: как это выглядело на практике

«Импульс медиа» — рекламное агентство, 31 рабочее место, у которого в NetBox заведена сеть офиса плюс стойка с презентационными серверами для клиентских демостендов — в сумме за полтора года набралось 132 устройства. Ко мне обратились с формулировкой «Zabbix не видит часть оборудования после обновления NetBox» — а скрипт автообнаружения незадолго до этого переписали с REST API на GraphQL, потому что так проще получать вложенные данные об интерфейсах одним запросом.

Быстро выяснилось: старый REST-скрипт честно обходил страницы через next и получал все объекты, а новый GraphQL-запрос device_list { id name interfaces { name } } писался без пагинации, пока NetBox стоял на 4.5.4, и тогда отдавал всё. Предыдущий подрядчик когда-то выставил MAX_PAGE_SIZE = 100, чтобы тяжёлые выгрузки из веб-интерфейса не клали маленькую виртуалку, — на REST-скрипт это не влияло, он просто делал больше запросов. После апгрейда до 4.6 тот же GraphQL-запрос начал упираться в эти 100 записей, и 32 устройства из 132 перестали долетать до мониторинга Zabbix через LLD. До того как это заметили, в мониторинге тихо не хватало примерно четверти устройств, включая один из презентационных серверов.

Исправление заняло меньше дня: переписал запрос на курсорную пагинацию pagination: {start, limit: 100} с циклом по возрастанию id, добавил сверку итогового количества с count, который отдельно запрашивается через REST, и вынес версию NetBox с датой апгрейда в комментарий к скрипту — чтобы следующий, кто будет трогать интеграцию, сразу видел, что GraphQL здесь без пагинации не работает начиная с определённой версии, а не полагался на память о том, как было раньше.

На практике таких интеграций у клиентов обычно две: экспорт в систему мониторинга (у кого-то это Zabbix, у кого-то — Uptime Kuma для простого self-hosted контроля доступности) и синхронизация с системой учёта или CMDB, похожая по логике на интеграцию Snipe-IT с Active Directory через REST API. Для REST-интеграций я всегда закладываю обход по next в самой первой версии кода синхронизации, даже если на момент внедрения устройств в NetBox меньше пятидесяти и проблема пагинации ещё не проявилась бы. Инвентарь растёт быстрее, чем кажется на старте проекта, и переписывать рабочий скрипт синхронизации, когда он молча недосчитался половины устройств на проде, — заметно дороже, чем сразу сделать правильно.

Для GraphQL я отдельно фиксирую версию NetBox, под которую написан код, и не полагаюсь на «запрос без пагинации вернёт всё» как на постоянное поведение — особенно если клиент когда-либо будет обновлять NetBox самостоятельно, без согласования со мной. Курсорная пагинация по id, появившаяся в 4.5.2, — единственный способ, который работает предсказуемо независимо от значения MAX_PAGE_SIZE на сервере, и именно её я закладываю в интеграции по умолчанию, а не полагаюсь на неограниченный ответ, который может исчезнуть после следующего апгрейда.

Чек-лист проверки полноты выгрузки устройств через REST и GraphQL API NetBox
Четыре проверки закрывают почти все случаи «интеграция видит не всё» в NetBox API.

Чек-лист: как проверить, что вы получаете все объекты

Если подозреваете, что интеграция читает не весь инвентарь, начните с прямого сравнения: запросите count из первого ответа REST API (или посчитайте объекты через веб-интерфейс NetBox) и сравните с тем, сколько объектов реально долетает до вашей внешней системы. Разница почти всегда означает, что клиент не обрабатывает next и останавливается на первой странице — чаще всего это 50 объектов, значение PAGINATE_COUNT по умолчанию, если администратор его не менял. Если скрипт уже работает в курсорном режиме REST (?start=), count там равен null — общее число берите обычным запросом вида ?limit=1.

Дальше проверьте, какая версия NetBox стоит на сервере — это видно в интерфейсе или в ответе GET /api/status/ (поле netbox-version) — и если она 4.5.5 или новее, отдельно протестируйте все GraphQL-запросы без явной пагинации: они больше не гарантируют полный список. И последнее — не переносите привычки REST на GraphQL вслепую: limit=0 в REST и pagination: {limit: 0} в GraphQL дают противоположный результат, и это стоит проверить один раз на тестовом окружении, а не выяснить на проде.

Сравните count из API с числом объектов, реально дошедших до вашей системы. Если меньше — не сеть виновата, а необработанная пагинация.

Частые вопросы

Почему скрипт получает только 50 устройств из NetBox?

Потому что REST API отдаёт списки страницами, и без явного увеличения limit или обработки next клиент видит только первую страницу — по умолчанию PAGINATE_COUNT равен 50 объектам.

Как получить сразу весь список через REST API?

Запросить `?limit=0` — но это сработает только если на сервере снят MAX_PAGE_SIZE (0 или None). Если MAX_PAGE_SIZE оставлен по умолчанию (1000), вы всё равно получите не больше 1000 объектов за один запрос и должны обходить остальные страницы через поле next.

Что изменилось в GraphQL с версии 4.5.5?

До 4.5.4 включительно MAX_PAGE_SIZE не действовал на списочные GraphQL-запросы (баг #20385) — они могли вернуть весь список без ограничений. С 4.5.5 MAX_PAGE_SIZE ограничивает и GraphQL, включая запросы без явной пагинации.

Как правильно обходить большой список через GraphQL после этого изменения?

Курсорной пагинацией по первичному ключу, появившейся в NetBox 4.5.2: pagination: {start: N, limit: M}. Для следующей страницы start берётся равным id последнего полученного объекта плюс один.

Чем pagination: {limit: 0} в GraphQL отличается от ?limit=0 в REST?

Прямо противоположным поведением: ?limit=0 в REST при снятом MAX_PAGE_SIZE отдаёт все объекты, а pagination: {limit: 0} в GraphQL в тех же условиях возвращает ноль записей.

Как уменьшить объём ответа, если нужны не все поля объекта?

В REST API — параметром ?fields=id,name,status для выбора конкретных полей, ?brief=true для краткого представления или omit для исключения отдельных тяжёлых полей из полного ответа.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи