The previous lesson showed that open() reads a file once per VU. With 500 VUs loading a 10,000-row user dataset, K6 holds 500 copies of that dataset in memory simultaneously. SharedArray solves this: the data is loaded once and shared across all VUs, regardless of VU count.
The memory difference
Regular array vs SharedArray memory usage
Regular array (open + JSON.parse)
open() runs once per VU in init
100 VUs × 10,000 records = 1 million records in memory
Memory scales linearly with VU count
A 5MB file with 500 VUs = 2.5GB memory allocation
Serialised separately for each VU's JavaScript context
SharedArray
Loader function runs exactly once, regardless of VU count
10,000 records in memory — always, regardless of VU count
Memory stays constant as VU count increases
500 VUs, 5MB file → still ~5MB for the data
Shared read-only access across all VU JavaScript contexts
Creating a SharedArray
import { SharedArray } from 'k6/data';
// The constructor takes: a name (string) and a loader function
// The loader function runs ONCE — its return value is shared across all VUs
const users = new SharedArray('users', function () {
return JSON.parse(open('./users.json'));
});
export const options = { vus: 100, duration: '2m' };
export default function () {
// Access like a regular array — random element
const user = users[Math.floor(Math.random() * users.length)];
// user.email, user.password, etc. are available
http.post('https://api.example.com/login',
JSON.stringify({ email: user.email, password: user.password }),
{ headers: { 'Content-Type': 'application/json' } }
);
}The name 'users' is a label used in K6's debug output. It must be unique if you create multiple SharedArrays in the same script.
SharedArray with CSV
Combine SharedArray with papaparse for memory-efficient CSV loading:
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
const users = new SharedArray('users', function () {
return papaparse.parse(open('./users.csv'), {
header: true,
skipEmptyLines: true,
}).data;
});
export default function () {
const user = users[Math.floor(Math.random() * users.length)];
// ...
}Indexing patterns
SharedArrays support the same indexing patterns as regular arrays. Choose based on what your test scenario requires:
// Random — different user each iteration, no coordination needed
const user = users[Math.floor(Math.random() * users.length)];
// Per-VU — each VU always uses the same user
// Good for simulating persistent sessions: VU 1 is always Alice, VU 2 is always Bob
const user = users[(__VU - 1) % users.length];
// Per-iteration — sequential cycling within each VU
// VU 1 uses users[0] on iter 0, users[1] on iter 1, etc.
const user = users[__ITER % users.length];
// Global unique — each iteration across all VUs gets a different record
// Works best when total iterations ≤ users.length
const user = users[(__VU - 1 + __ITER * __ENV.VU_COUNT) % users.length];The per-VU pattern is the most predictable for scenarios requiring user-specific state (login → session → actions — all as the same user).
SharedArray is read-only
SharedArray data cannot be modified during the test. Attempting to assign to an element throws an error:
const users = new SharedArray('users', function () {
return JSON.parse(open('./users.json'));
});
export default function () {
users[0].email = 'modified@test.com'; // ❌ Throws TypeError: Cannot assign to read-only property
}This is intentional — the data is shared across VUs, and mutable shared state would require locking that would destroy K6's concurrency model. If you need to track per-VU state (like a session token obtained after login), use a module-level variable that each VU sets independently.
Multiple SharedArrays
A script can have multiple SharedArrays — use distinct names for each:
import { SharedArray } from 'k6/data';
const users = new SharedArray('users', () => JSON.parse(open('./users.json')));
const products = new SharedArray('products', () => JSON.parse(open('./products.json')));
const promos = new SharedArray('promos', () => JSON.parse(open('./promos.json')));
export default function () {
const user = users[Math.floor(Math.random() * users.length)];
const product = products[Math.floor(Math.random() * products.length)];
const promo = promos[Math.floor(Math.random() * promos.length)];
// ...
}Each SharedArray's loader runs once. Memory usage is the sum of all three datasets, not multiplied by VU count.
When not to use SharedArray
SharedArray is for large, read-only reference data — user pools, product catalogues, test payloads. It is not appropriate for:
- Data generated during setup — the setup() return value handles this (pass via
dataparameter) - Small inline arrays — a 5-element array is fine as a plain const at module level
- Per-VU mutable state — use a module-level variable that each VU's context manages independently
⚠️ Common mistakes
- Using a regular
constarray withopen()and wondering why memory is high.const users = JSON.parse(open('./users.json'))copies the data once per VU. Wrap it innew SharedArray(...)to load once. - Trying to modify SharedArray elements. SharedArray is immutable by design. Track mutable per-VU state in a separate module-level variable.
- Using the same name for two SharedArrays. K6 identifies SharedArrays by name. Two arrays with the same name will reference the same underlying data — the second constructor's loader function may not run. Use unique descriptive names.
- Putting large data generation logic inside the SharedArray loader instead of open(). The loader runs once, which is correct. But if it makes HTTP requests or does expensive computation, it blocks K6's init phase. Keep the loader to: open file → parse → return.
🎯 Practice task
Compare memory usage with and without SharedArray. 30 minutes.
- Create
products.jsonwith 1,000 product entries:[{ "id": 1, "name": "Product 1", "price": 9.99 }, ...]. You can generate this with a quick Node.js script outside K6 or create a smaller version manually. - Write a K6 script that loads this file with a plain
JSON.parse(open('./products.json'))— no SharedArray. Run withvus: 10, duration: '10s'. Note the output (K6 does not show memory directly, but observe startup time). - Refactor to use
new SharedArray('products', () => JSON.parse(open('./products.json'))). Run again with the same settings. The test should behave identically but use less memory at high VU counts. - In the default function, select a random product and make a GET to
https://httpbin.org/get?productId=${product.id}&name=${encodeURIComponent(product.name)}. Add a check that status is 200. - Change to per-VU selection:
products[(__VU - 1) % products.length]. Add a console.log showing the product name. Run withvus: 5, iterations: 5. Confirm VU 1 always logs the same product. - Try assigning to a SharedArray element —
products[0].price = 0— inside the default function. Observe the error K6 throws.