---
title: "Parameterization Done Right: CSV, JSON, and Shared Data in k6"
description: "k6 parameterization with CSV/JSON: SharedArray, memory-safe datasets, and skewed traffic—official k6 data docs and GDPR-aware test data."
publishDate: "2026-08-20"
draft: false
keyword: "k6 test data parameterization"
intent: "MOFU"
cta: "Use Performate to turn this playbook into runnable k6 scenarios, thresholds, and shareable reports without losing days to glue code."
tags: ["k6","load-testing","api-performance","data","parameterization"]
---

Your staging API accepts ten thousand SKUs—but your k6 script hard-codes three product IDs from last quarter's demo. That mismatch is why load tests pass while production checkout queues spike: every virtual user hits the same hot cache line, the same inventory row lock, and the same search index shard.

Parameterization is how you turn a smoke script into a **representative workload**. In this guide you will learn why dataset shape changes performance outcomes, how to load CSV and JSON safely with [`SharedArray`](https://k6.io/docs/javascript-api/k6-data/sharedarray/), and which selection strategies keep traffic honest without blowing memory on every VU.

## Why test data shape changes what you measure

Hard-coded payloads feel fast to write. Under concurrency they distort results in predictable ways:

- **Cache warming** collapses when every request uses the same URL, SKU, or user ID—tail latency looks artificially low.
- **Database hotspots** appear when all VUs update one row; you measure lock contention, not normal spread.
- **Search and recommendation paths** skew when queries repeat; you miss cold-index and long-tail behavior.
- **Auth and tenancy** break realism when a single token represents "every customer"—rate limits and shard routing never activate.

Think of it like stress-testing a parking garage with one license plate. The gate software works; the real-world queue at rush hour does not.

### When small datasets lie about tail latency

Functional tests need one valid row per case. Load tests need **distribution**—popular SKUs, long-tail queries, and inactive accounts in proportions that match analytics. Pair parameterized datasets with [k6 observability and tags](/en/blog/k6-observability-metrics-tags) so you can slice failures by `sku_tier` or `region` when a subset misbehaves.

For regulated domains, read [GDPR-aware test data](/en/blog/performance-test-data-gdpr) before copying production exports into CSV files—even in staging.

## Practical k6 implementation: SharedArray, CSV, and weighted picks

Load data once per test run, share it across VUs, and pick rows with a strategy that mirrors production skew—not uniform random unless your analytics say so.

**Example script (illustrative—not a production-ready test).** The snippet below uses fictional product data and SLO numbers. Adapt paths, columns, and weights to your catalog.

**What this example demonstrates:**

- **Memory-safe sharing:** `SharedArray` parses CSV once; VUs read by index instead of duplicating the full file in memory.
- **Weighted SKU tiers:** ~70% traffic to `hero` SKUs, 30% to `longtail`—aligned with typical ecommerce mix.
- **Per-row tags:** `sku_tier` and `region` tags let you threshold or filter metrics by segment in summaries.
- **Env-driven data path:** `DATA_CSV` lets CI and local runs point at different fixtures without editing the script.

```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

const BASE = __ENV.API_BASE || 'https://staging.example.com';

// Parsed once, shared read-only across all VUs
const products = new SharedArray('catalog', function () {
  return open(__ENV.DATA_CSV || './data/products.csv')
    .split('\n')
    .slice(1) // skip header: sku,tier,region
    .filter(Boolean)
    .map((line) => {
      const [sku, tier, region] = line.split(',');
      return { sku, tier, region };
    });
});

function pickProduct() {
  // Weighted pick: ~70% hero, 30% longtail
  const wantHero = Math.random() < 0.7;
  const pool = products.filter((p) =>
    wantHero ? p.tier === 'hero' : p.tier === 'longtail'
  );
  return pool[Math.floor(Math.random() * pool.length)];
}

export const options = {
  scenarios: {
    checkout_mix: {
      executor: 'constant-arrival-rate',
      rate: 20,
      timeUnit: '1s',
      duration: '5m',
      preAllocatedVUs: 15,
      maxVUs: 60,
      exec: 'checkout',
    },
  },
  thresholds: {
    'http_req_duration{sku_tier:hero}': ['p(95)<600'],
    'http_req_duration{sku_tier:longtail}': ['p(95)<900'],
    http_req_failed: ['rate<0.01'],
  },
};

export function checkout() {
  const product = pickProduct();
  const body = JSON.stringify({ sku: product.sku, qty: 1 });

  const res = http.post(`${BASE}/checkout`, body, {
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${__ENV.TOKEN}`,
    },
    tags: { sku_tier: product.tier, region: product.region },
  });

  check(res, { 'checkout 2xx': (r) => r.status >= 200 && r.status < 300 });
  sleep(0.4);
}
```

**JSON alternative:** for nested fixtures (multi-step flows, cart bundles), use `JSON.parse(open('./data/flows.json'))` inside `SharedArray` the same way—one parse, many readers. Keep files small enough to diff in code review; split large corpora by scenario.

**Patterns that work**

- **`SharedArray` for read-only catalogs**—never mutate rows inside VU code; generate derived values locally instead.
- **Separate data files per scenario** when mixes differ (browse vs checkout vs admin)—see [k6 scenario types](/en/blog/k6-scenario-types-explained).
- **Stable seeds for CI:** when you need reproducible picks, derive index from `__VU` and `__ITER` for smoke gates, random for soak tests.
- **Column contracts in README:** document CSV headers next to the file so Postman imports and k6 scripts stay aligned ([Postman to k6](/en/blog/postman-to-k6-step-by-step)).

**Anti-patterns to avoid**

- Loading CSV with plain `open()` outside `SharedArray` on large files—memory multiplies with VU count.
- Copying production PII into repos—use synthetic or masked datasets.
- Uniform random selection when analytics show heavy skew toward a few keys.
- One global user record for all VUs when auth middleware keys rate limits per subject.

**Pro tip (example command):** point CI at a fixture path without editing the script.

```bash
k6 run checkout-parameterized.js -e DATA_CSV=./fixtures/catalog-smoke.csv -e API_BASE=https://staging.example.com
```

**What this command demonstrates:** the same script runs against smoke-sized and full-catalog fixtures by swapping env vars—ideal for [CI/CD load testing](/en/blog/load-testing-in-ci-cd) gates vs weekly soak jobs.

## Decision framework: CSV vs JSON vs inline data

| Situation | Recommended action |
|:---|:---|
| Flat catalog (SKU, region, tier columns) | CSV + `SharedArray`; easy diff and spreadsheet export from analytics |
| Nested multi-step payloads (cart + shipping + payment) | JSON fixture per flow; one file per scenario family |
| <20 static IDs for smoke only | Inline constants acceptable; document that full tests need a dataset |
| Regulated or customer data | Synthetic/masked generators; never commit secrets ([k6 secrets](/en/blog/k6-secrets-test-environments)) |
| Hot-key skew in production | Weighted selection or stratified scenarios with separate rates |

**Use CSV if** non-engineers maintain catalog slices and you want grep-friendly diffs in pull requests.

**Use JSON if** payloads are nested objects or arrays that would be painful to stringify from CSV columns.

**Use weighted picks if** analytics show a small fraction of keys drive most traffic—uniform random hides real hotspots.

## Observability, documentation, and next steps

Parameterized tests only help when the dataset itself is versioned and explained:

- [ ] Document CSV/JSON schema (columns, allowed values) next to the fixture in the repo or data catalog.
- [ ] Record dataset version or git SHA in scenario tags so regressions compare the same catalog slice.
- [ ] Validate row counts and tier percentages before long runs—empty `longtail` pools cause silent bias.
- [ ] Mask or synthesize PII; align with your org's data retention policy for test environments.
- [ ] Archive which fixture path CI used per release so "pass" means the same traffic shape, not a smaller file.

## How Performate simplifies parameterized k6 workflows

Maintaining CSV paths, column maps, and weighted picks by hand does not scale across teams. Below is a **concrete workflow example** for the same checkout catalog this article discusses.

**Example: drive checkout from a shared dataset without glue scripts**

1. **Import a Postman collection** (or OpenAPI) whose requests use `{{sku}}` and `{{region}}` variables. *Problem solved:* request shapes stay the single source of truth instead of duplicating JSON in k6 and Postman.
2. **Attach a CSV data file in the scenario panel**—map columns to collection variables (`sku`, `tier`, `region`). *Problem solved:* non-engineers refresh catalog slices without editing JavaScript.
3. **Create two scenarios or weights** for hero vs longtail tiers if your analytics show skew—set arrival rates to match last week's mix. *Problem solved:* honest traffic shape without hand-writing filter logic in every script.
4. **Apply tags from CSV columns** (`sku_tier`, `region`) in the visual tag editor. *Problem solved:* reports match the tag model in the k6 example above for stakeholder reviews.
5. **Run against staging and open the comparison view**—filter by tier when `p95` on longtail diverges. *Problem solved:* backend and QA debate one export, not forked spreadsheets per segment.
6. **Export the generated k6 script** with `SharedArray` wiring for CI smoke gates ([load testing in CI/CD](/en/blog/load-testing-in-ci-cd)). *Problem solved:* local tuning and pipeline runs stay aligned.

That workflow maps directly to the `cta` in this post: turn parameterization playbooks into runnable scenarios without losing days to glue code.

## Closing takeaway

Realistic load tests are **distribution problems**, not single-payload problems. Share datasets with `SharedArray`, pick rows the way analytics do, and tag segments so tail latency on long-tail keys cannot hide behind hero-SKU averages.

Refresh your catalog fixture from last week's traffic mix before the next release gate—and note which tier still owns the latency tail your SLO cares about.

[Try Performate free](https://performate.app) | [Book a demo](/demo) | [k6 SharedArray](https://k6.io/docs/javascript-api/k6-data/sharedarray/)
