The per-vu-iterations executor ensures that each VU runs an exact number of iterations. Unlike shared iterations where VUs compete for iterations, here every VU completes its own fixed quota of iterations.
How It Works
With per-VU iterations:
- Each VU is assigned a fixed number of iterations
- VUs run independently without competing
- Total iterations =
vus × iterations
- Each VU completes all its iterations or runs until
maxDuration
- All VUs complete the same number of iterations (if within maxDuration)
VU 1: [iter 1][iter 2][iter 3] (3 iterations)
VU 2: [iter 1][iter 2][iter 3] (3 iterations)
VU 3: [iter 1][iter 2][iter 3] (3 iterations)
Total: 9 iterations (3 VUs × 3 iterations each)
Configuration
Must be per-vu-iterations
Number of VUs to run concurrently. Must be greater than 0.
Number of iterations each VU executes. Must be greater than 0.
Maximum duration for the executor. If a VU’s iterations don’t complete within this time, remaining iterations are dropped.
Example
Basic Configuration
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
scenarios: {
per_vu_scenario: {
executor: 'per-vu-iterations',
vus: 10,
iterations: 20,
maxDuration: '1m',
},
},
};
export default function () {
http.get('https://test.k6.io');
sleep(1);
}
This runs 10 VUs, each executing 20 iterations, for a total of 200 iterations.
Per-User Session Simulation
import http from 'k6/http';
import { sleep } from 'k6';
import { check } from 'k6';
export const options = {
scenarios: {
user_sessions: {
executor: 'per-vu-iterations',
vus: 50,
iterations: 10, // Each user completes 10 sessions
maxDuration: '5m',
},
},
};
export default function () {
// Login
const loginRes = http.post('https://test.k6.io/login', {
username: `user_${__VU}`,
password: 'password',
});
check(loginRes, { 'login successful': (r) => r.status === 200 });
sleep(1);
// Browse
http.get('https://test.k6.io/products');
sleep(2);
// Logout
http.post('https://test.k6.io/logout');
sleep(1);
}
When to Use
Use the per-VU iterations executor when:
- You want consistent work per VU (e.g., each user completes 10 sessions)
- You need predictable per-VU behavior for testing
- You’re simulating realistic user behavior where each user does a set amount of work
- You want the total iterations to scale linearly with VUs
- You need to test with isolated per-VU data or state
Behavior Details
Total Iterations
The total number of iterations is the product of VUs and iterations per VU:
export const options = {
scenarios: {
example: {
executor: 'per-vu-iterations',
vus: 10,
iterations: 5,
// Total iterations: 10 × 5 = 50
},
},
};
VU Isolation
Each VU maintains its own iteration counter and executes independently:
export default function () {
console.log(`VU ${__VU}, iteration ${__ITER}`);
// VU 1: iterations 0, 1, 2, 3, 4...
// VU 2: iterations 0, 1, 2, 3, 4...
// VU 3: iterations 0, 1, 2, 3, 4...
}
Maximum Duration
If a VU cannot complete all iterations within maxDuration, remaining iterations are dropped:
export const options = {
scenarios: {
example: {
executor: 'per-vu-iterations',
vus: 5,
iterations: 100,
maxDuration: '10s', // May not complete all 100 per VU
},
},
};
Dropped iterations are tracked per VU in the dropped_iterations metric.
Scaling Behavior
When using execution segments for distributed testing, only VUs are scaled, NOT iterations per VU. This maintains linear scaling.
Example with 50% execution segment:
// Original config
{
vus: 10,
iterations: 20,
// Total: 200 iterations
}
// With 50% segment (0:0.5)
// Scaled VUs: 5
// Iterations per VU: 20 (unchanged)
// Total: 100 iterations (50% of original)
Metrics
The executor emits these metrics:
iterations - Total completed iterations across all VUs
iteration_duration - Time to complete each iteration
dropped_iterations - Iterations that didn’t complete within maxDuration (tracked per VU)
vus - Number of active VUs
vus_max - Maximum number of VUs
Common Patterns
User Journey Testing
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
scenarios: {
user_journey: {
executor: 'per-vu-iterations',
vus: 20,
iterations: 5, // Each user completes journey 5 times
},
},
};
export default function () {
// Complete user journey
http.get('https://test.k6.io');
sleep(1);
http.get('https://test.k6.io/products');
sleep(2);
http.post('https://test.k6.io/cart/add', { productId: 123 });
sleep(1);
http.post('https://test.k6.io/checkout');
sleep(1);
}
Per-VU Data Processing
import http from 'k6/http';
import { SharedArray } from 'k6/data';
const users = new SharedArray('users', function () {
return JSON.parse(open('./users.json'));
});
export const options = {
scenarios: {
user_requests: {
executor: 'per-vu-iterations',
vus: users.length,
iterations: 10, // Each user makes 10 requests
},
},
};
export default function () {
const user = users[__VU - 1]; // Each VU gets one user
http.post('https://test.k6.io/api', JSON.stringify({
userId: user.id,
iteration: __ITER,
}));
}
Ramping Users with Fixed Work
Combine with other executors for complex scenarios:
export const options = {
scenarios: {
warmup: {
executor: 'per-vu-iterations',
vus: 1,
iterations: 10,
startTime: '0s',
},
main_load: {
executor: 'per-vu-iterations',
vus: 50,
iterations: 20,
startTime: '30s', // Start after warmup
},
},
};
Comparison with Shared Iterations
| Feature | Per VU Iterations | Shared Iterations |
|---|
| Total iterations | vus × iterations | Fixed total |
| Distribution | Even (each VU does same) | Dynamic (faster VUs do more) |
| VU workload | Equal | Unequal |
| Predictability | High per-VU | High total |
| Scaling | Linear with VUs | Independent of VUs |
| Use case | Consistent per-user load | Fixed total work |
Best Practices
- Set realistic maxDuration: Ensure each VU has enough time to complete all iterations
- Use for user simulation: Great for simulating N users each doing M actions
- Monitor per-VU metrics: Use
__VU and __ITER to track per-VU behavior
- Consider VU reuse: VUs are reused across iterations, so handle state appropriately
- Plan for scaling: Remember only VUs scale in distributed testing, not iterations
See Also