Q6 of 38 · Performance

Walk through designing a load test for a checkout flow.

PerformanceMidperformanceload-test-designcheckoutk6scenarios

Short answer

Short answer: Map the user journey end-to-end, parametrise with realistic data (carts, addresses, cards), mix transaction types by production ratio, model think times, ramp gradually, hold at target, and assert SLOs at the checkout-completed step rather than per individual call.

Detail

Step 1 — Map the journey. Login → browse → add to cart → view cart → enter shipping → enter payment → confirm. Capture each HTTP call (or business transaction) and the data dependencies between them — order ID from "create cart" feeds "add line item," etc.

Step 2 — Parametrise data. A real checkout test cannot use the same user, cart, or card for every iteration — the system de-duplicates, sessions collide, payment provider rate-limits, idempotency keys clash. Generate per-iteration data: unique user, unique cart, varied product mix, different shipping zones, randomised payment methods.

Step 3 — Mix transaction types by production ratio. Production has more browsers than buyers — maybe 100 product-page views per checkout. A pure checkout-loop test under-loads the read paths. Use weighted scenarios: 70% browse-only, 25% browse-then-cart-abandon, 5% complete checkout.

Step 4 — Model think times. Real users pause to read product details, type addresses, decide on payment. Random pauses (1-5s between steps) keep server-side concurrency realistic. Without them, you measure system limits, not user experience.

Step 5 — Ramp gradually. Spike-from-zero to peak load skips the warm-up phase real systems get during morning ramp. 5-10 minute ramp to peak, hold for 30+ minutes, then ramp down.

Step 6 — Assert at the right level. Per-call latency thresholds are useful but the user-facing SLO is "checkout completes in under 3 seconds." Track the end-to-end transaction duration explicitly; per-call assertions catch infrastructure issues, the end-to-end one catches user-impacting regressions.

// EXAMPLE

k6-checkout-flow.js

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

const users = new SharedArray('users', () =>
  JSON.parse(open('./users.json'))
);

export const options = {
  scenarios: {
    browse: { executor: 'ramping-vus', exec: 'browseOnly',
              startVUs: 0, stages: [{ duration: '5m', target: 70 }] },
    checkout: { executor: 'ramping-vus', exec: 'checkout',
                startVUs: 0, stages: [{ duration: '5m', target: 5 }] },
  },
  thresholds: {
    'group_duration{group:::checkout}': ['p(95)<3000'],
    http_req_failed: ['rate<0.005'],
  },
};

export function checkout() {
  const u = users[__VU % users.length];
  group('checkout', () => {
    const cart = http.post('/cart', { sku: 'A1' }).json();
    sleep(2);
    http.post('/cart/' + cart.id + '/address', u.address);
    sleep(3);
    http.post('/cart/' + cart.id + '/pay', u.card);
  });
}

// WHAT INTERVIEWERS LOOK FOR

A structured plan: journey mapping, data parametrisation, mix realism, think times, ramp shape, and end-to-end SLO assertion. Bonus for mentioning idempotency keys to avoid duplicate orders.

// COMMON PITFALL

Loading the same user-id 1000 times in parallel — you measure deduplication and lock contention, not realistic checkout load.