Концепции

GraphQL

Нагрузочное тестирование GraphQL — запросы и мутации с валидацией по схеме, GraphQL-семантика ошибок и метрики по операциям

Обзор

perfscale нагружает GraphQL-эндпоинты: одна операция на шаг, запросы и мутации через HTTP POST (или GET по желанию), и каждый документ валидируется против схемы самого эндпоинта ещё до отправки.

GraphQL — часть open-source движка: действие std/graphql@v1 доступно на любом плане. Никакого кодогена и клиентских заглушек: документ — обычный текст в YAML (или файл .graphql), переменные — JSON, а ответы приходят как структурированные data/errors, из которых следующие шаги извлекают значения через интерполяцию ${{ … }}.

Действие: 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}" }

Параметры

ПараметрТипПо умолчаниюОписание
urlstringобязателенGraphQL-эндпоинт, например https://api.example.com/graphql
querystringодно из query/query_fileGraphQL-документ прямо в YAML
query_filestring (путь)одно из query/query_fileЧитать документ из файла .graphql. Доступ к файловой системе: требует allow_file_actions, соблюдает fs_root
variablesobjectПеременные запроса. Работает интерполяция ${{ … }}, а токены-генераторы ${…} (${uuid}, ${rand}, ${now}) раскрываются на каждое выполнение
operationstringoperationName для выполнения. Обязателен, если в документе несколько операций; он же — тег метрик по операциям
methodstringPOSTPOST (JSON-тело) или GET (query-параметры URL — для кэшируемых через CDN чтений)
headersobject{ "Name": "Value" }, только строковые значения
timeoutinteger (мс)10000Таймаут на запрос
insecurebooleanfalseПропустить проверку TLS-сертификата
introspectionbooleantrueОдин раз за прогон получить схему эндпоинта и валидировать каждый запрос перед отправкой. При неудаче — прогон без валидации (логируется один раз)
schema_filestring (путь)Валидировать по локальному SDL-файлу вместо интроспекции (запасной вариант для эндпоинтов с выключенной интроспекцией). Файловые правила те же, что у query_file
poolstringper-vuper-vu закрепляет шаг за HTTP-клиентом своего VU (поведение std/http@v1); shared сажает все VU на один общий клиент процесса

Выход

Доступен через outputs / __last__:

{
  "status": 200,
  "data": { "viewer": { "id": "u-1" } },
  "errors": [ { "message": "widgets timed out" } ],
  "body": "{\"data\":…}",
  "duration_ms": 12.31,
  "headers": { "content-type": "application/json" }
}

data и errors — раскодированный GraphQL-ответ (errors присутствует только если сервер его прислал); body хранит сырой текст для проверок body_contains. Извлечение читает структурированный ответ: ${{ create.data.createWidget.id }}.

Валидация по схеме

Каждый запрос парсится до отправки и — когда схема доступна — валидируется по ней. Опечатка роняет шаг (и perfscale lint) с подсказкой «может, вы имели в виду», вместо того чтобы жечь запросы в цель:

fetch viewer: query validation failed: unknown field 'viewr' on type 'Query' — did you mean 'viewer'?

Два источника схемы:

  • Интроспекция (по умолчанию) — движок один раз за прогон отправляет интроспекционный запрос, кэширует схему на весь процесс и валидирует по ней каждый запрос. Один сетевой раунд на эндпоинт, сколько бы ни было VU.
  • schema_file: schema.graphql — валидация по локальному SDL-файлу. Запасной вариант для эндпоинтов, где интроспекция выключена (обычное дело в проде): если интроспекция не удалась и schema_file не задан, шаг всё равно выполняется — без валидации, с одной строкой [sys] в логе об этом. introspection: false отключает получение схемы полностью.

perfscale lint применяет тот же контроль: синтаксис — всегда (офлайн), схему — когда эндпоинт доступен или задан SDL-файл. perfscale lint --offline пропускает сетевой проход.

Что считается ошибкой

GraphQL-ошибки приезжают в теле 200 OK, поэтому HTTP-статус сам по себе не вердикт:

  • HTTP-статус ≥ 400 → шаг падает.
  • Есть errors, а data null или отсутствует → шаг падает (не разрешилось ничего).
  • Частичный data плюс errors → шаг проходит — сервер разрешил, что смог, — а ошибки учитываются в graphql_errors.

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

Стандартные проверки check: работают без изменений: status, duration_ms_lt, body_contains (по сырому телу).

Метрики

  • graphql_req_duration — гистограмма времени каждой операции; раннер выводит из неё graphql_req_failed (rate). Гейт через std/thresholds@v1: "graphql_req_duration": ["p99<200"].
  • graphql_errors — счётчик GraphQL-ошибок, включая те, что пришли с частичными данными и не уронили шаг.
  • graphql_op_<operationName>_duration — гистограмма по операции; пишется только когда операция именована (явный operation или единственная именованная операция), так что кардинальность метрик ограничена самим определением теста.
  • Запрос также питает стандартные агрегаты http_req_duration / http_req_failed / http_reqs, как любой HTTP-шаг.

Пул соединений

pool: per-vu (по умолчанию) закрепляет шаг за HTTP-клиентом своего VU — то же keep-alive-поведение, что у std/http@v1: VU переиспользует свои тёплые соединения между итерациями. pool: shared сажает все VU на один общий клиент процесса: максимальное переиспользование соединений к одному эндпоинту ценой конкуренции за блокировку пула при очень больших числах VU.

Ограничения

  • Подписки не поддерживаются (транспорт — HTTP запрос/ответ); документ с подпиской не проходит валидацию.
  • Батчинг запросов (массивы операций) и инкрементальная доставка (@defer/@stream) не поддерживаются — одна операция на шаг.
  • query_file и schema_file — доступ к файловой системе: требуют allow_file_actions в конфиге прогона и соблюдают ограничение fs_root.

Дальше