Ядро
Нагрузочное тестирование GraphQL
perfscale нагружает GraphQL-эндпоинты через встроенный движок шагов: одна операция на шаг, запросы и мутации по HTTP POST (или опционально GET), причём запрос валидируется по схеме самого эндпоинта ещё до того, как будет отправлен.
Никакой кодогенерации и клиентских заглушек: документ — это обычный текст в
YAML (или файл .graphql), переменные — JSON, а ответы возвращаются в виде
структурированных data/errors, из которых следующие шаги извлекают данные
подстановкой ${{ … }}.
Эта страница — руководство. Параметры и выходные данные шага описаны в
справочнике действий; готовый к запуску сценарий —
examples/graphql.test.yaml.
Одно действие: std/graphql@v1
steps:
- name: fetch viewer
use: std/graphql@v1
with:
url: https://api.example.com/graphql
query: |
query GetViewer($id: ID!) {
viewer(id: $id) { id name }
}
variables: { "id": "${{ vars.viewer_id }}" }
check:
status: 200
outputs: viewer
- name: rename widget
use: std/graphql@v1
with:
url: https://api.example.com/graphql
query: |
mutation Rename($id: ID!, $name: String!) {
renameWidget(id: $id, name: $name) { id }
}
variables: { "id": "${{ viewer.data.viewer.id }}", "name": "w-${seq}" }
queryзадаётся инлайн (многострочный YAML хорошо читается);query_fileуказывает на файл.graphql— эти два параметра взаимоисключающие.variables— JSON-объект. Подстановка${{ … }}работает везде внутриwith:, поэтому значения, извлечённые на предыдущих шагах, попадают сюда напрямую, а токены-генераторы с одинарными фигурными скобками (${uuid},${rand},${now}) раскрываются при каждом выполнении — каждая итерация может отправлять новое имя или id.- Документ с несколькими операциями требует
operation: GetViewer, чтобы выбрать одну из них, — это тот жеoperationName, который получает сервер. method: GETпереносит операцию в query-параметры URL для чтений, кешируемых на CDN. Мутации остаются на POST (по умолчанию).
Валидация по схеме
Каждый запрос разбирается перед отправкой и — когда схема доступна —
валидируется по ней. Опечатка заваливает шаг (и perfscale lint) с
подсказкой «возможно, вы имели в виду» вместо того, чтобы тратить запросы
на целевой сервис:
fetch viewer: query validation failed: unknown field 'viewr' on type 'Query' — did you mean 'viewer'?
Два источника схемы:
- Introspection (по умолчанию) — движок один раз за прогон отправляет introspection-запрос на эндпоинт, кеширует схему на весь процесс и валидирует по ней каждый запрос. Один сетевой обход на эндпоинт, независимо от числа VU.
schema_file: schema.graphql— валидация по локальному SDL-файлу. Это запасной вариант для эндпоинтов, которые запрещают introspection (типично для продакшена): если introspection не удался иschema_fileне задан, шаг всё равно выполняется — без валидации, с одной строкой[sys]в логе об этом.introspection: falseполностью отключает получение схемы.
perfscale lint применяет тот же барьер: синтаксис — всегда (офлайн), схема —
когда эндпоинт доступен или задан SDL-файл. perfscale lint --offline
пропускает сетевую проверку.
Что считается ошибкой
Ошибки GraphQL приходят в теле ответа со статусом 200 OK, поэтому одного
HTTP-статуса для вердикта недостаточно:
- HTTP-статус ≥ 400 → шаг падает.
- есть
errors, аdataравно null или отсутствует → шаг падает (ничего не разрешилось). - частичные
dataплюсerrors→ шаг проходит — сервер разрешил то, что смог, — а ошибки учитываются вgraphql_errors.
Стандартные проверки check: работают без изменений: status,
duration_ms_lt, body_contains (по сырому телу ответа). Проверки на уровне
полей читают декодированную нагрузку через шаг std/check@v1:
- name: viewer id present
use: std/check@v1
with:
on: viewer.data.viewer
message_contains: "id" # …or assert via outputs interpolation
Метрики
graphql_req_duration— HDR-гистограмма в мс (процентили в сводке); время оборота каждой операции, один сэмпл на реально отправленный запрос, при успехе и при ошибке. Задавайте пороги черезstd/thresholds@v1:"graphql_req_duration": ["p99<200"].graphql_req_failed— доля ошибок, вычисляемая изgraphql_req_duration: один сэмпл 0/1 на вызов (1 = шаг упал: транспортная ошибка, HTTP-статус с ошибкой илиerrorsбезdata— см. выше). Используйте её для SLO-гейтов:graphql_req_failed: ["rate<0.01"]. Подробности вычисления: metrics.md.graphql_errors— счётчик, суммируется за прогон; ошибки уровня GraphQL, включая те, что при частичных данных не заваливают шаг. Эмитируется всегда (0 на чистых ответах), поэтому проверкиgraphql_errors: ["count==0"]разрешаются и на здоровых прогонах.graphql_op_<operationName>_duration— HDR-гистограмма в мс; задержка по каждой операции, публикуется только когда операция именованная (явныйoperationили единственная именованная операция), поэтому кардинальность метрик ограничена определением теста. Каждая именованная операция получает и производную долюgraphql_op_<operationName>_failed.- Запрос также попадает в стандартные агрегаты
http_req_duration/http_req_failed/http_reqs, как любой HTTP-шаг.
Пул соединений
pool: per-vu (по умолчанию) привязывает шаг к шарду HTTP-клиента виртуального
пользователя — то же поведение keep-alive, что и у std/http@v1, поэтому VU
переиспользует свои тёплые соединения между итерациями. pool: shared сажает
всех VU на один глобальный для процесса клиент: максимальное переиспользование
соединений к одному эндпоинту ценой конкуренции за блокировку пула при очень
большом числе VU.
Ограничения
- Подписки не поддерживаются (транспорт — HTTP запрос/ответ); документ, содержащий подписку, не пройдёт валидацию.
- Батчинг запросов (массивы операций) и инкрементальная доставка
(
@defer/@stream) не поддерживаются — одна операция на шаг. query_fileиschema_file— это доступ к файловой системе: они требуютallow_file_actionsв конфигурации прогона и подчиняются ограничениюfs_root.