Skip to main content
Aug 20, 2026k6 test data parameterization

By Lucas Yoris · Performate

Parameterization Done Right: CSV, JSON, and Shared Data in k6

k6 parameterization with CSV/JSON: SharedArray, memory-safe datasets, and skewed traffic—official k6 data docs and GDPR-aware test data.

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, 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 so you can slice failures by sku_tier or region when a subset misbehaves.

For regulated domains, read GDPR-aware test data 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.
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.
  • 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).

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.

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 gates vs weekly soak jobs.

Decision framework: CSV vs JSON vs inline data

SituationRecommended 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 onlyInline constants acceptable; document that full tests need a dataset
Regulated or customer dataSynthetic/masked generators; never commit secrets (k6 secrets)
Hot-key skew in productionWeighted 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). 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 | Book a demo | k6 SharedArray

Ready to optimize your API performance?

Use Performate to turn this playbook into runnable k6 scenarios, thresholds, and shareable reports without losing days to glue code.

← Back to all posts