LIVE

REST и GraphQL: замер задержки запросов под высокой нагрузкой

У REST на простом запросе меньше накладных расходов: сервер получает заранее известный маршрут и обычно быстро переходит к обработчику. GraphQL сначала разбирает и валидирует текст запроса, строит план выполнения, а затем запускает резолверы.

Ратмир Чеботарев·Обновлено: 24 октября 2026 г.·9 мин

REST и GraphQL: замер задержки запросов под высокой нагрузкой

На плоском CRUD-эндпоинте эта дополнительная работа заметна. Но если экрану нужны связанные данные из нескольких ресурсов, REST может заставить клиента сделать несколько сетевых вызовов, и выигрыш на сервере потеряется в round-trip’ах.

Поэтому сравнение производительности REST и GraphQL под нагрузкой не сводится к одному числу из бенчмарка. Нужно смотреть на форму запроса, число обращений к серверу, хвостовую задержку, пропускную способность и расход памяти. Один API может выигрывать на «вернуть строку» и проигрывать на сборке сложного ответа. Это не парадокс, а следствие архитектуры.

Что именно делает сервер

REST обычно связывает HTTP-метод и URL с конкретной операцией: например, GET /users/42. Для простого ответа серверу не требуется разбирать универсальный язык запросов. В GraphQL клиент отправляет структуру данных, которую хочет получить, а сервер проверяет её синтаксис, валидирует поля и типы, после чего вызывает соответствующие резолверы.

Универсальность GraphQL имеет цену. Парсинг AST и валидация добавляют работу до получения результата; резолверы могут запускать дополнительные обращения к базам или внутренним сервисам. Для простого запроса это лишний оверхед. Для сложного ответа такая же архитектура даёт клиенту возможность запросить связанные сущности одним обращением и избежать серии HTTP-вызовов.

На практике многое зависит от реализации. Плохо настроенный REST может упереться в базу, сериализацию или сетевые задержки. GraphQL с DataLoader и эффективной пакетной загрузкой способен не допустить N+1 обращений, а без этих механизмов один запрос клиента может породить десятки запросов к базе. Название архитектуры на графике не заменяет разбор профиля нагрузки.

Под задержкой здесь важно различать как минимум два показателя. Средняя latency показывает типичный отклик, но сглаживает редкие зависания. Максимальная задержка в коротком тесте, напротив, может сильно зависеть от единичного выброса. Для продакшена обычно полезнее смотреть на распределение задержек и перцентили, особенно на высоких: именно там видны запросы, которые портят пользовательский опыт при общей «хорошей средней».

Плоский запрос: преимущество REST видно сразу

В синтетическом тесте Autocannon с 100 параллельными соединениями и длительностью 10 секунд на простом запросе «hello world» средняя задержка REST составила 7,68 мс, GraphQL — 13,51 мс. Максимальная достигла 60 мс у REST и 893 мс у GraphQL. В сценарии возврата одной строки пропускная способность составила около 11 972 запросов в секунду для REST против примерно 7 085 для GraphQL.

Разрыв заметный. Но он отвечает на узкий вопрос: сколько стоит обработка простого запроса в конкретной тестовой конфигурации. Он не доказывает, что REST быстрее в любом API или что GraphQL обязательно будет давать почти секундные пики в рабочей системе. Синтетический тест хорошо выявляет оверхед на простой операции, но не описывает автоматически запросы с авторизацией, базой данных, кэшем и реальной сетевой топологией.

СценарийRESTGraphQLПрактический смысл
Простой запрос, средняя задержка7,68 мс13,51 мсПри минимальной работе дополнительный этап обработки GraphQL заметнее
Простой запрос, максимальная задержка60 мс893 мсХвост теста резко различается; одиночный максимум нельзя считать универсальным прогнозом
Возврат одной строки, пропускная способностьоколо 11 972 req/sоколо 7 085 req/sВ этом сценарии REST обработал больше запросов в секунду

Для простых операций такая картина ожидаема. Если клиенту нужна одна сущность или результат стандартного CRUD-действия, REST-эндпоинт часто проще кэшировать, анализировать и масштабировать. GraphQL при этом выполняет работу по разбору запроса, даже если итоговый ответ короткий. На фоне тяжёлой базы эта разница может затеряться; на лёгком обработчике она становится почти всем измеряемым временем.

На плоском запросе GraphQL платит за гибкость, которой клиент в этот момент почти не пользуется.

Есть и эксплуатационный нюанс: GraphQL часто возвращает HTTP 200 OK даже тогда, когда в выполнении запроса произошла внутренняя ошибка и часть данных недоступна. Это не означает, что GraphQL «прячет» ошибки, но мониторинг, который считает только HTTP-коды, может показать бодрый зелёный экран при проблемах в поле errors ответа. Для корректной оценки нужны метрики уровня приложения: ошибки резолверов, частичные ответы, таймауты и фактическая задержка выполнения.

Вложенные данные: меньше поездок, иногда быстрее ответ

Представим экран, которому нужны пользователь, его заказы и позиции в каждом заказе. В REST это может быть несколько вызовов: получить пользователя, запросить список заказов, затем получить детали по каждому заказу. Конкретный дизайн API может объединить часть данных, но на практике отдельные ресурсы нередко превращаются в цепочку запросов. Каждый дополнительный round-trip добавляет сетевую задержку и усложняет поведение клиента при сбоях.

GraphQL позволяет описать нужную клиенту вложенную структуру в одном запросе. Это может сократить общее время отклика за счёт уменьшения количества обращений по сети. В исследовании Университета Вашингтона Apollo Server показывал на 25–67% меньшее среднее время кругового обращения по сравнению со стандартным REST для большинства рассмотренных операций. При экстремально высокой параллельной нагрузке Apollo Server уступал REST в масштабируемости.

Эти результаты не превращают GraphQL в автоматический ускоритель. Один HTTP-вызов с глубокой вложенностью может оказаться тяжелее нескольких простых REST-ответов: сервер всё равно должен получить и собрать данные, выполнить резолверы и сформировать JSON. Если резолвер для каждого элемента отдельно обращается к базе, GraphQL не устраняет сетевой оверхед внутри системы, а только прячет его за одним запросом клиента. Классическая ловушка N+1 просто меняет место, где её замечают.

Для микросервисов это особенно существенно. GraphQL-шлюз может собрать данные из нескольких сервисов и уменьшить число вызовов со стороны приложения. Но он же становится точкой агрегации, где сходятся задержки нижележащих систем. Если один сервис отвечает медленно, общий запрос может ждать его результат, а резолверы создают дополнительную конкуренцию за соединения и память. Поэтому в оценке производительности GraphQL в микросервисах полезно отдельно измерять время шлюза, каждого источника данных и ожидание в очередях.

Ключевые вопросы для сложного запроса вполне конкретны:

  • Сколько обращений к базе и сервисам порождает один запрос клиента?
  • Есть ли пакетная загрузка связанных сущностей, например через DataLoader?
  • Ограничены ли глубина и сложность запроса, чтобы клиент не сформировал чрезмерно тяжёлую структуру?
  • Что происходит при таймауте одного резолвера: весь ответ падает или клиент получает частичный результат?
  • Как меняются хвостовые задержки, когда одновременно приходят запросы с разной глубиной?

Здесь не нужна религиозная война REST против GraphQL. Нужна трассировка. Один запрос GraphQL может выглядеть экономным на уровне браузера и дорогим внутри сервера. Несколько REST-вызовов могут быть приемлемы при хорошем кэше и параллельном выполнении, а могут стать медленной цепочкой, если клиент ждёт каждый ответ последовательно.

Пиковая нагрузка: RPS не рассказывает всю историю

В сравнительном исследовании RESTful-архитектура показала наибольшее суммарное число обработанных запросов, но сопровождалась повышенной частотой отказов при пиковой нагрузке по сравнению с GraphQL. Это важное уточнение: максимальный RPS сам по себе не означает, что сервис лучше переживает пик. Если сервер быстро принимает много запросов, но заметная часть заканчивается ошибками, цифра пропускной способности выглядит бодро только до разбора отказов.

Другая сторона тоже не бесплатна. Исследование на портале DiVA зафиксировало, что при стрессовой нагрузке потребление памяти контейнером GraphQL могло быть почти вдвое выше, чем у стандартного REST. В качестве причин отмечались валидация запросов и работа резолверов. Это не универсальный коэффициент для каждого стека: результат зависит от реализации и сценария. Но для сервиса с ограниченными ресурсами такой рост нельзя списать на косметику.

Под нагрузкой растёт цена не только самого вычисления, но и очередей. Если запросы GraphQL сильно различаются по сложности, несколько тяжёлых операций могут занять ресурсы, нужные для множества лёгких. Ограничения глубины, сложности, времени выполнения и размера ответа становятся частью защиты производительности. Без них публичный API получает неприятный режим: клиент формально отправляет один запрос, а сервер исполняет большой граф работы.

Для REST тоже есть характерные точки отказа. Большое количество отдельных вызовов увеличивает конкуренцию за соединения и нагрузку на сеть. При этом более простая форма эндпоинтов облегчает кэширование и настройку лимитов на конкретные операции. В обоих вариантах важны таймауты, пул соединений, контроль параллелизма и наблюдаемость. Архитектура задаёт форму нагрузки, но не отменяет необходимость управлять ею.

Практический замер стоит строить на нескольких сценариях, а не на одном «hello world»: простой ответ, типичная операция продукта, глубокий связанный запрос и пик конкурентности. Для каждого нужны latency по перцентилям, RPS, доля ошибок и память контейнера. Если тестировать только среднюю задержку, можно пропустить редкие зависания. Если смотреть только RPS, можно не заметить, что часть запросов отваливается. Если сравнивать разные объёмы ответа, результат будет говорить скорее о размере данных, чем об API.

Кэширование: у REST проще путь, у GraphQL есть обходной маршрут

GET-запросы REST естественно сочетаются с HTTP-кэшированием и CDN. Ключ кэша обычно строится вокруг URL и учитываемых заголовков, поэтому отдельные ресурсы можно кэшировать на стандартном уровне инфраструктуры. Для часто запрашиваемых публичных данных это снижает число обращений к приложению и помогает выдерживать пики.

GraphQL по умолчанию часто использует POST, а CDN не всегда кэширует такие запросы как обычные GET. Значит, кэширование требует дополнительной настройки. Один из вариантов — Persisted Queries, то есть заранее сохранённые запросы, которые клиент вызывает по идентификатору; для них можно организовать GET и использовать кэш на границе. GraphQL не лишён кэширования, но «из коробки» путь обычно менее прямой.

Нужно учитывать и семантику кэша. В REST разные ресурсы часто имеют отдельные адреса и политики, а в GraphQL разные клиенты могут запрашивать разные комбинации полей на одной точке входа. Чтобы кэширование работало предсказуемо, приходится аккуратно определить идентификаторы запросов, вариативность ответа и срок актуальности. Если данные персонализированы, нельзя бездумно положить ответ в общий CDN-кэш: экономия на latency быстро обернётся утечкой данных.

Сходная дисциплина нужна, если API обслуживает уведомления или клиентские коммуникации: здесь полезен отдельный разбор того, как устроена интеграция сервисов рассылки сообщений, поскольку сетевой контур и обработка событий влияют на общую картину задержек не меньше выбора формата API.

Как сравнивать варианты для своего API

Оценка задержки REST и GraphQL имеет смысл только при одинаковых условиях. Сравнивать следует сопоставимые ответы, одинаковые источники данных, авторизацию, сериализацию, конфигурацию кэша и уровень конкурентности. Иначе один вариант может «выиграть» лишь потому, что делает меньше работы.

Полезно разделить тесты на два класса. Первый проверяет стоимость самого API: простой обработчик и минимальный ответ. Второй отражает продуктовую операцию: реальные связанные данные, типичный объём результата и обычный путь через базу или микросервисы. Первый покажет архитектурный оверхед. Второй даст более практичную картину для пользователя.

Что измерятьЗачем
Среднюю latency и высокие перцентилиПонять типичный отклик и поведение медленного хвоста
RPS вместе с долей ошибокНе принять отказы за высокую производительность
Память контейнера и загрузку CPUУвидеть цену парсинга, резолверов и формирования ответа
Число обращений к базе и внутренним сервисамНайти N+1 и скрытый оверхед агрегации
Результат с включённым кэшированиемПроверить не только вычисление, но и реальный сетевой путь
Поведение на пике конкурентностиУстановить, где система начинает деградировать

Тесты надо запускать с прогревом и повторениями, а результаты фиксировать вместе с конфигурацией сервера. Один удачный прогон не является характеристикой архитектуры. Особенно осторожно стоит относиться к максимальной задержке из короткого теста: полезный сигнал, но слабое основание для обещаний о поведении продакшена.

Вердикт инженера

REST обычно рациональнее для плоских операций, типовых ресурсов и сценариев, где важны простые правила HTTP-кэширования. На лёгких запросах его меньший оверхед действительно проявляется в измерениях. GraphQL оправдан, когда клиенты часто собирают связанные данные, число round-trip’ов становится проблемой, а команда готова следить за сложностью запросов, резолверами и ресурсами.

Решение следует принимать по профилю нагрузки, а не по моде и не по одному красивому графику. Для REST измеряйте цепочки запросов и ошибки на пике. Для GraphQL — стоимость AST и резолверов, память, глубину запросов и хвостовую latency. Если команда не умеет наблюдать эти параметры, смена архитектуры добавит продакшену новый класс сюрпризов. Если умеет, оба подхода рабочие; выбор определяется тем, где именно система тратит время и ресурсы.

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

Какая средняя задержка у REST и GraphQL на простом запросе?
В синтетическом тесте Autocannon с 100 параллельными соединениями на простом запросе hello world средняя задержка REST составила 7,68 мс, а GraphQL — 13,51 мс.
Почему GraphQL может потреблять больше памяти под нагрузкой?
При стрессовой нагрузке потребление памяти контейнером GraphQL может быть почти вдвое выше из-за валидации запросов и работы резолверов.
Как GraphQL влияет на количество сетевых запросов при получении связанных данных?
GraphQL позволяет описать нужную вложенную структуру в одном запросе, что может сократить общее время отклика за счет уменьшения количества обращений по сети.
Каковы особенности кэширования для REST и GraphQL?
GET-запросы REST естественно сочетаются с HTTP-кэшированием и CDN на уровне инфраструктуры, в то время как GraphQL по умолчанию часто использует POST, из-за чего его кэширование требует дополнительной настройки.
Как меняется пропускная способность REST и GraphQL на простых операциях?
В сценарии возврата одной строки пропускная способность составила около 11 972 запросов в секунду для REST против примерно 7 085 для GraphQL.