Load test using one user ID at a time for each user and iteration

Hi!

I need to do a load test for the following scenarios:

I have an endpoint that checks if the user has an account. I want to make a ramp to simulate my day-to-day users who make requests to this endpoint.

Something like that:

    { duration: '10s', target: 35 }, 
    { duration: '1m', target: 35 }, 
    { duration: '20s', target: 75 }, 
    { duration: '1m', target: 75 }, 
    { duration: '20s', target: 100 }, 
    { duration: '1m', target: 100 },
    { duration: '20s', target: 75 }, 
    { duration: '1m', target: 75 },
    { duration: '10s', target: 35 },
    { duration: '1m', target: 35 },

To do the test I would like to use a .json that contains the ID of 100 users (maximum of my ramp). So, to be able to simulate 1 user at a time making the request, it would need to be 1 iteration per VU on the ramp, and each iteration be of a .json ID.

I don’t know if I’m doing it the right way, because I see that 100 users making requests at the same time should be 1:1 (vu:iteration).

Hi @marianafiori

Welcome to the community forums :wave:

I’m not sure exactly what’s the scenario you need to simulate. So I’ll share a couple of approaches.

For example, if you use a ramping-vus executor, with a script like the one below (reading users and passwords from a csv file users.csv):

import exec from 'k6/execution';
import http from 'k6/http';
import { SharedArray } from 'k6/data';
import { sleep } from 'k6';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import { randomIntBetween } from "https://jslib.k6.io/k6-utils/1.0.0/index.js";

const sharedData = new SharedArray("Shared Logins", function () {
    let data = papaparse.parse(open('users.csv'), { header: true }).data;
    return data;
});

export const options = {
    discardResponseBodies: true,
    scenarios: {
        contacts: {
            executor: 'ramping-vus',
            startVUs: 35,
            stages: [
                { duration: '10s', target: 35 },
                { duration: '1m', target: 35 },
                { duration: '20s', target: 70 },
                { duration: '1m', target: 70 },
                { duration: '20s', target: 100 },
                { duration: '1m', target: 100 },
                { duration: '20s', target: 70 },
                { duration: '1m', target: 70 },
                { duration: '10s', target: 35 },
                { duration: '1m', target: 35 },
            ],
            gracefulRampDown: '10s',
        },
    },
};

export default function () {
    console.log(`[VU: ${exec.vu.idInTest}, iteration: ${exec.scenario.iterationInTest}] Starting iteration...`);
    console.log('username: ', sharedData[exec.vu.idInTest - 1].username, ' / password: ', sharedData[exec.vu.idInTest - 1].password);
    http.get('https://httpbin.test.k6.io/basic-auth/' + sharedData[exec.vu.idInTest - 1].username + '/' + sharedData[exec.vu.idInTest - 1].password);
    sleep(randomIntBetween(1, 5));
}

You would get the virtual users you are after, I think. There are different stages, ramping stage when you go from 35 to 70, then steady at 70, etc.

However, the number of iterations/requests the VUs perform will vary. It will depend on the latency (request duration) of your endpoint, whether you add a sleep time to simulate a user behavior, and the duration of the test. With this executor you are having the number of VUs you specify iterate on your VU code as long as it’s defined in the stage duration.

If your scenario is closer to an API test, you could still use that and reduce or remove the sleep. I added a bigger sleep in the example to simulate a human-run workflow.

Usually though, for API testing, we know a fixed number of requests per second (or time unit) we want our API to fulfill. In this case, you might go for a constant arrival rate executor. There you can set the number of requests per time unit you want, and adjust the number of VUs that will be required to achieve the requests per time unit. Again this will depend on the latency of your endpoint, the sleep time (if you set any).

For example, let’s say I want to test 100 requests per second with my previous script. I can see I am able to achieve this with 100 users (preAllocatedVUs).

import exec from 'k6/execution';
import http from 'k6/http';
import { SharedArray } from 'k6/data';
import { sleep } from 'k6';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';

const sharedData = new SharedArray("Shared Logins", function () {
    let data = papaparse.parse(open('users.csv'), { header: true }).data;
    return data;
});

export const options = {
    discardResponseBodies: true,
    scenarios: {
        my_scenario1: {
            executor: 'constant-arrival-rate',
            duration: '2m',  // total duration
            preAllocatedVUs: 100,  // to allocate runtime resources pre all VUs
            rate: 100, // number of constant iterations given `timeUnit`
            timeUnit: '1s',
        },
    },
};

export default function () {
    console.log(`[VU: ${exec.vu.idInTest}, iteration: ${exec.scenario.iterationInTest}] Starting iteration...`);
    console.log('username: ', sharedData[exec.vu.idInTest - 1].username, ' / password: ', sharedData[exec.vu.idInTest - 1].password);
    http.get('https://httpbin.test.k6.io/basic-auth/' + sharedData[exec.vu.idInTest - 1].username + '/' + sharedData[exec.vu.idInTest - 1].password);
    sleep(0.1);
}

We can get the 100 request per second rate we are after.

This is because the response time is 100ms, and each VU can execute an iteration every second.

With 25 VUs we would get to 100 request per second, since the latency is low (and we have a sleep of 0.1).

            preAllocatedVUs: 25,
            rate: 100, 
            timeUnit: '1s',

If we had a response time of 5 seconds, we would need more VUs to reach the same rate.

A few more examples around executors can be found in k6-learn/Modules/III-k6-Intermediate/08-Setting-load-profiles-with-executors.

I hope this helps.

If you can further elaborate on your concrete case, and share your (sanitized) script, we can give advice more tailored to your scenario if needed.

Cheers!

Hi @eyeveebe,

Thank you very much for your answer and it has already helped me a lot to clarify my points.

I have data that informs the number of users who access the application per minute on normal days and on days that have campaigns. Of course, on days with campaigns, the number of users per second is higher. I have an API that has endpoints that log in, log out and check if the user has an account created by entering an ID.

I was creating my test using group for each endpoint. Would it be a good approach?

My biggest doubt is in the control of VU and iterations. If 100 users access the application at the same time, it would have to be 100 requests right? But if I put 100 VU’s, the number of requests is not 100 because of the iterations that each VU does.

Hi @marianafiori

Good to hear we were able to clarify some points.

I was creating my test using group for each endpoint. Would it be a good approach?

I would have a look at Tags and Groups. If I understand correctly, you want to use a group for each URL login, logout, check user account by ID. And there might be better options for your case.

I would have a look at URL grouping with a tag to see if that works for you.

My biggest doubt is in the control of VU and iterations. If 100 users access the application at the same time, it would have to be 100 requests right? But if I put 100 VU’s, the number of requests is not 100 because of the iterations that each VU does.

Here you will have to decide how to model this, choosing the best executor and scenarios for your case.

It might be best to switch to a constant arrival rate executor, and ask k6 to sustain the rate you want. It won’t be ramping up, though. You would preallocate the needed VUs (depending on the latency of your endpoints you might need more or less) and start the test asking k6 to do 100 requests every 1m. Similar to:

export const options = {
  discardResponseBodies: true,
  scenarios: {
    api_test: {
      executor: 'constant-arrival-rate',
      rate: 100,
      timeUnit: '1m', 
      duration: '10m',
      preAllocatedVUs: 200,
    }
  }
}

This is if you define in your VU code the 3 requests (login, check ID exists, logout).

If you want to test them independently, you could load test the 3 endpoints with 3 scenarios, each scenario hitting one of the endpoints.

The advanced examples might help you understand different options.

It all depends a bit on what you want to achieve, and if you want to “just” test the API endpoints, or you want to test the while cycle (login, check ID, logout) for the same VU. And if you need to ramp-up the VUs, or just test with 100 VUs.

I hope this helps.

Cheers!