Ядро

Общие переменные

Переменные variables: в perfscale и выходные данные шагов неизменяемы и существуют в рамках одного VU — один VU не видит значений другого, и ничего нельзя обновить в процессе прогона. Общие переменные закрывают этот пробел: единое хранилище «ключ/значение» с областью видимости на весь прогон и атомарными операциями, которое изменяется и читается из шагов через std/set_shared_variable@v1 / std/get_shared_variable@v1. Они выражают паттерны координации, недоступные переменным уровня VU:

  • Общие счётчики — подсчёт событий по всем VU (increment).
  • Очереди «производитель/потребитель» — один VU добавляет элементы работы, остальные извлекают их через pop в порядке FIFO.
  • Барьеры — потребитель блокируется через wait_for, пока не накопится N элементов или не изменится флаг.

Каждая операция — одно атомарное обращение: нет пары шагов «чтение-изменение-запись», которая могла бы привести к гонке, и нет примитива блокировки, который мог бы вызвать взаимную блокировку.

Эта страница — руководство. Параметры и выходные данные шагов описаны в справочнике действий.

Объявление (обязательно)

Общие переменные объявляются в файле конфигурации, рядом с variables:, в виде отображения «имя → начальное JSON-значение». Тип выводится из начального значения и определяет, какие операции допустимы:

# config.yaml
vus: 8
duration: 30s

shared_variables:
  pending_orders: []      # list  → append / pop / length_gte
  approved_count: 0       # number → increment
  last_error: null        # any   → set / get

Шаг, ссылающийся на необъявленное имя, или операция op, несовместимая с объявленным типом (increment для списка), завершается ошибкой валидации конфигурации ещё до запуска любого VU — опечатка никогда не превратится в молчаливый null посреди теста. Динамического создания нет: шаги используют только объявленные имена. При старте прогона объявленные имена (пере)устанавливаются в свои начальные значения.

Очередь «производитель/потребитель»

Один VU (или один тестовый файл) производит элементы работы, остальные их потребляют. append возвращает новую длину списка; pop извлекает и возвращает первый элемент (null для пустого списка — проверяйте это через check:, когда пустота является ошибкой, или используйте wait_for, чтобы вместо этого заблокироваться):

# config.yaml
shared_variables:
  pending_orders: []
# producer.yaml — every iteration enqueues one order
steps:
  - name: enqueue order
    use: std/set_shared_variable@v1
    with:
      name: pending_orders
      op: append
      value: { id: "ord-${seq}", total: "${randf(10,100,2)}" }
# consumer.yaml — block up to 10s for an item, then take it
steps:
  - name: dequeue order
    use: std/get_shared_variable@v1
    with:
      name: pending_orders
      op: pop
      wait_for: { length_gte: 1, timeout_ms: 10000 }
      extract: { order_id: $.id, total: $.total }
    check:
      body_contains: ord-

Полезные данные интерполируются, как в любом другом шаге, — ${seq}, ${randf(10, 100, 2)}, ${{ vars.* }}, выходные данные предыдущих шагов. extract извлекает поля из значения тем же синтаксисом путей через точку ($.foo.bar, $.[0].x), что и extract в std/llm@v1; каждый извлечённый ключ попадает на верхний уровень выходных данных шага, поэтому outputs: + ${{ name.order_id }} работают дальше по цепочке.

Барьер: накопление N элементов

Потребитель, который не должен начинать работу, пока N производителей не внесли свою часть, — классический рандеву нагрузочного тестирования. wait_for блокирует выполнение (опрашивая значение с небольшой асинхронной задержкой, без активного ожидания), пока условие не станет истинным:

steps:
  - name: wait for batch of 10 orders
    use: std/get_shared_variable@v1
    with:
      name: pending_orders
      wait_for: { length_gte: 10, timeout_ms: 30000 }

wait_for принимает ровно одно условие — exists, equals: <json> или length_gte: <int> — плюс timeout_ms (по умолчанию 5000). По истечении таймаута шаг завершается с ошибкой и строкой [err], в которой указано последнее наблюдавшееся значение (тот же контракт, что и у таймаута подписки в std/pubsub@v1): отставший производитель — это именно тот сигнал, для которого вы подбираете размер теста, а не молчаливый успех. Время ожидания попадает в waited_ms и в сэмпл метрики shared_variable_wait_ms, поэтому медленные барьеры видны в сводке.

Общий счётчик естественно сочетается с барьером — increment возвращает новое значение:

# config.yaml
shared_variables:
  approved_count: 0
steps:
  - name: approve
    use: std/set_shared_variable@v1
    with: { name: approved_count, op: increment, value: 1 }

  - name: proceed once 50 approvals are in
    use: std/get_shared_variable@v1
    with:
      name: approved_count
      wait_for: { equals: 50, timeout_ms: 60000 }

Драйверы: memory против redis

Хранилище подключается через драйвер, который выбирается для каждого шага:

ДрайверХранилище
memory (по умолчанию)Глобальное для процесса отображение — общее для всех VU в одном процессе, внешний сервис не требуется

Вендорные драйверы — Redis, … — это pro-capability, регистрируемые на том же драйверном интерфейсе; неизвестное значение driver завершает шаг ошибкой со списком зарегистрированных драйверов, так что всегда ясно, какую сборку вы запускаете.

Хранилище memory локально для процесса: когда распределённый прогон распределяет VU по нескольким агентам perfscaled, у каждого агента своё хранилище — append на агенте A невидим для агента B. Межагентная координация (очередь или барьер на весь кластер) требует сетевого драйвера, например Redis; в пределах одного агента memory — самый быстрый вариант, не требующий никаких запущенных сервисов.

Ограничения

  • Нет динамического создания — необъявленные имена являются ошибками конфигурации, поэтому всё общее состояние теста полностью перечислено в shared_variables:.
  • pop работает по принципу FIFO, и просмотра по индексу (peek) нет; проверки полей полезных данных на стороне потребителя выполняются через extract или шаг std/check@v1.
  • wait_for — это опрос (polling), а не push-подписка, — держите timeout_ms небольшим в горячих циклах, чтобы зависший производитель быстро приводил к ошибке.