Unique VU index per scenario or data parameterization in multiple scenarios

Hey, I have a question which as far as I see, was already asked long time ago here: Shared state or unique sequential vu index per scenario

But it looks like there’s still no answer) Or maybe I just couldn’t find it.
I need to use data parameterization per scenario. Using VU indexes works fine if working with 1 scenario. But when it comes to several scenarios - I couldn’t find the answer how to do this.

Let’s say I have 4 scenarios. Each scenario uses 25 users. So in the setup() I create arrays of users and share them. It’s not a problem to access the needed array itself. The question is - how do I make each VU working with only 1 user from the shared array? Especially assuming those VU indexes are random and scenario 1 might get VU index 90 etc. So I can’t use VU index to access the array. Some VU index per scenario would work but it looks like there’s no any.

Or maybe there is a way to read some file per scenario and use only 1 line per virtual user inside that scenario? It’s possible in jMeter but I can’t figure out how to do this in K6.

Please help

Hi @eugene_tst,
Welcome back to the community forum :tada:

did you try to use exec.scenario.iterationInTest index as explained in the Data Parameterization guide?

If it is not enough, can you clarify better your case, please?

Hi, @codebien , the scenario.iterationInTest is not what I needed because it’s not connected to the VU itself. I ended up using 2 instances of k6 for what I need…

The case is the following:
First of all, I’m using more than 1 scenario (let’s say, 2 scenarios).
I register users during the setup and put them to arrays so each array is made for it’s own scenario (scenario a1 users might have some own properties).
What I need is being able to work with exact array for exact scenario (array a1 is used in scenario a1 only). As VUs indexes are allocated randomly, I can’t rely on the VU index and use it as an array index. Of course, I could use iteration indexes in some simple cases but in my specific situation this would lead to re-login each user which is absolutely not what I wanted. Having some kind of scenario VU indexes would help me because I need to use each VU with the same app user during the whole run.

Also, being able to modify the shared objects would help here so I could use 1 shared array and set needed properties during the test runtime but it’s not possible, I can only read the shared data from setup and not modify it…

Hi @eugene_tst,
sorry for the late reply but I was on PTO.

Can you share the code or explaing how you’re setting the arrays? Do you use the Setup data? What do you put in the arrays (e.g tokens)? How many items do you set?

I’m not getting why you reference exec.scenario.iterationInTest as a per-VU index as the API report it is a per-scenario index.

Can you clarify why in your case it would re-login the users?

This is typically achieved using an external database. For example, Redis is a common solution for this kind of problem. You can interact with a Redis server using the dedicated k6 ihttps://k6.io/docs/javascript-api/k6-experimental/redis module.

Hi everybody,

I found this discussion which was a long time ago. But maybe the new version of k6 (V1.7.1) provides the solution of the issue?
I want to assign unique user data to each VU (for login). The user data doesn’t change after each iteration. It should be fix assigned to the VU the whole test time.
I can provide you a sample below. In the console output you’ll find the lines with error messages, i.e.
INFO[0054] ‘scnB’: VU-IT = 10-6 source=console
INFO[0054] Error in “scnB”: Error: Pool poolB is too small! source=console

What I need is a running number (starting with 0) which increments by 1 for each VU of the scenario. This number should be available with the VU data.

Here is my sample code:

import http from 'k6/http';
import { check, sleep, group } from 'k6';
import exec from 'k6/execution';

///////////////////////////////////////////////////////////////////////////////
/// DEFINITIONS
interface MyUser {
  id: string;
}
let currentUser: MyUser | undefined;

const pools: { [key: string]: MyUser[] } = {
  poolA: [...Array(11).keys()].map((i) => ({ id: `A${i}` })),
  poolB: [...Array(6).keys()].map((i) => ({ id: `B${i}` })),
  poolC: [...Array(4).keys()].map((i) => ({ id: `C${i}` })),
};
// Mapping Scenario → Pool name
const scenarioToPool: { [scenario: string]: string } = {
  scnA: 'poolA',
  scnB: 'poolB',
  scnC: 'poolC',
};

// ----------------------
// Scenario-Config
// ----------------------
export const options = {
  scenarios: {
    scnA: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '10s', target: 11 },
        { duration: '20s', target: 11 },
        { duration: '13s', target: 0 },
      ],
      gracefulStop: '20s',
      gracefulRampDown: '20s',
      exec: 'scnA',
    },
    scnB: {
      executor: 'constant-vus',
      vus: 6,
      duration: '60s',
      gracefulStop: '20s',
      exec: 'scnB',
    },
    scnC: {
      executor: 'per-vu-iterations',
      vus: 4,
      iterations: 5,
      maxDuration: '60s',
      gracefulStop: '20s',
      exec: 'scnC',
    },
  },
};

// ----------------------
// Scenario-Functions
// ----------------------
export function scnA() {
  console.debug(`'scnA': >>>> Start | VU=${__VU} | Iteration=${__ITER}`);
  console.log(`'scnA': VU-IT = ${exec.vu.idInInstance}-${__ITER}`);
  const start = Date.now();
  try {
    const user = getUserForVU();
  } catch (err) {
    console.log('Error in "scnA": ', err);
    sleep(9);
    return;
  }
  sleep(1);
  group('scnA', function () {
    let url = 'https://quickpizza.grafana.com/';
    let response = http.get(url);
    sleep(1);
    url = 'https://quickpizza.grafana.com/api/delay/11';
    response = http.get(url);
  });
  console.debug(`'scnA': >>>> END | VU=${__VU} | Iteration=${__ITER}`);
}

export function scnB() {
  console.debug(`'scnB': >>>> Start | VU=${__VU} | Iteration=${__ITER}`);
  console.log(`'scnB': VU-IT = ${exec.vu.idInInstance}-${__ITER}`);
  const start = Date.now();
  try {
    const user = getUserForVU();
  } catch (err) {
    console.log('Error in "scnB": ', err);
    sleep(9);
    return;
  }
  sleep(2);
  group('scnB', function () {
    let url = 'https://quickpizza.grafana.com/';
    let response = http.get(url);

    url = 'https://quickpizza.grafana.com/api/delay/6';
    response = http.get(url);
  });
  console.debug(`'scnB': >>>> END | VU=${__VU} | Iteration=${__ITER}`);
}

export function scnC() {
  console.debug(`'scnC': >>>> Start | VU=${__VU} | Iteration=${__ITER}`);
  console.log(`'scnC': VU-IT = ${exec.vu.idInInstance}-${__ITER}`);
  const start = Date.now();
  try {
    const user = getUserForVU();
  } catch (err) {
    console.log('Error in "scnC": ', err);
    sleep(9);
    return;
  }
  group('scnC', function () {
    const url = 'https://quickpizza.grafana.com/';
    const response = http.get(url);

    const checkRes = check(response, {
      'status is 200': (r) => r.status === 200,
      'response time < 2000ms': (r) => r.timings.duration < 2000,
    });
    console.debug(`Response time: ${response.timings.duration} ms`);
  });
  sleep(4);
  console.debug(`'scnC': >>>> END | VU=${__VU} | Iteration=${__ITER}`);
}

/// the goal of this function is to provide a unique user
///   to each VU; the user data come from the user pools
export function getUserForVU() {
  const scenarioName = exec.scenario.name;

  if (!currentUser) {
    const poolName = scenarioToPool[scenarioName];
    const pool = pools[poolName];
    const index = exec.vu.idInInstance - 1;

    if (index >= pool.length) throw new Error(`Pool ${poolName} is too small!`);

    currentUser = pool[index];
    console.log(
      `'getUserForVU': Scenario ${scenarioName} | VU ${exec.vu.idInInstance} → User ${currentUser.id}`
    );
  }
  return currentUser;
}

I would be happy about any ideas I can validate or maybe there is a solution available. To use 3rd party apps / services / servers is not possible.

Thank you in advance,

Niko