---
title: "Geo-Distributed Load Testing: DNS, Latency, and Realistic User Journeys"
description: "Run generators close to users—or emulate latency—and avoidDNS shortcuts that fake performance."
publishDate: "2026-10-12"
draft: false
keyword: "geo distributed load testing"
intent: "MOFU"
cta: "Use Performate to coordinate multi-region scenario packs with consistent tagging."
tags: ["k6","load-testing","api-performance","geo","distributed"]
---

Your k6 runner sits in `us-east-1`, your users open the app in São Paulo and Madrid, and the dashboard shows green p95—until Black Friday exposes cross-region database hops and a CDN cache key that only warms in Virginia. **Geo distributed load testing** is how you prove latency and routing match customer reality instead of your CI network shortcut.

In this guide you will learn why single-region generators lie, how to tag k6 runs by region, when to deploy distributed agents vs emulate RTT, and which DNS choices belong in the scenario—not in assumptions.

## Why geography changes performance—not just ping charts

Aggregating all virtual users through one regional generator hides problems that only appear on the wire path customers use:

- **GeoDNS** steers names to different anycast edges; wrong resolver in the test means wrong POP.
- **TLS and HTTP/3** negotiation differs by distance; CPU on the client side is not the story—RTT amplifies tail latency on chatty APIs.
- **CDN cache hit ratio** varies by region; a warm cache in Virginia does not help Chile on first byte.
- **Database followers** may sit in one region while writes stay primary elsewhere—read-heavy journeys look fast only near the writer.
- **Compliance routing** forces EU traffic through EU clusters; a US-only generator never exercises that path.

Think of performance as a map, not a single number. One dot on the map is a lab result; a contour plot is what your SLO should defend.

Pair regional tags with [k6 observability metrics and tags](/en/blog/k6-observability-metrics-tags) so merged summaries still split `http_req_duration` by `region:eu-west` vs `region:sa-east`.

## Practical k6 implementation: regional tags and parallel scenarios

You can run **distributed k6** (k6 Cloud or self-hosted runners per [distributed execution](https://k6.io/docs/testing-guides/running-distributed-tests/)) or run one script with explicit `region` tags per scenario when each process executes in a different locale. Emulating RTT with Linux `tc` is a fallback—it approximates delay, not loss, jitter, or path MTU issues.

**Example script (illustrative—not production-ready).** Uses fictional hosts and thresholds. Adapt URLs, auth, and rates to your topology.

**What this example demonstrates:**

- **Parallel regional scenarios:** `journey_eu` and `journey_sa` run at the same RPS split as analytics—not 100% from one POP.
- **Region tags:** Every request carries `region:eu` or `region:sa` for percentile comparison in one summary export.
- **DNS realism:** `BASE_EU` and `BASE_SA` env vars point at geo-steered hostnames your customers resolve—not a single global staging CNAME shortcut.
- **Journey shape:** Multi-step flow (catalog + cart) exposes chatty APIs that RTT amplifies.
- **Thresholds per region:** Separate `p(99)` gates when product SLOs differ by market.

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

const BASE_EU = __ENV.API_EU || 'https://api-eu.staging.example.com';
const BASE_SA = __ENV.API_SA || 'https://api-sa.staging.example.com';
const TOKEN = __ENV.API_TOKEN || 'replace-me';

export const options = {
  scenarios: {
    journey_eu: {
      executor: 'constant-arrival-rate',
      rate: Number(__ENV.EU_RPS || 20),
      timeUnit: '1s',
      duration: '8m',
      preAllocatedVUs: 50,
      maxVUs: 150,
      exec: 'journeyEu',
    },
    journey_sa: {
      executor: 'constant-arrival-rate',
      rate: Number(__ENV.SA_RPS || 12),
      timeUnit: '1s',
      duration: '8m',
      preAllocatedVUs: 40,
      maxVUs: 120,
      exec: 'journeySa',
    },
  },
  thresholds: {
    'http_req_duration{region:eu}': ['p(95)<600', 'p(99)<1200'],
    'http_req_duration{region:sa}': ['p(95)<900', 'p(99)<1800'],
    http_req_failed: ['rate<0.02'],
  },
};

function catalog(base, regionTag) {
  const res = http.get(`${base}/v1/catalog?page=1`, {
    headers: { Authorization: `Bearer ${TOKEN}` },
    tags: { name: 'catalog', region: regionTag },
  });
  check(res, { 'catalog 200': (r) => r.status === 200 });
  return res;
}

function addToCart(base, regionTag) {
  const res = http.post(
    `${base}/v1/cart/items`,
    JSON.stringify({ sku: 'SKU-100', qty: 1 }),
    {
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        'Content-Type': 'application/json',
      },
      tags: { name: 'add_to_cart', region: regionTag },
    }
  );
  check(res, { 'cart 201': (r) => r.status === 201 });
}

export function journeyEu() {
  catalog(BASE_EU, 'eu');
  addToCart(BASE_EU, 'eu');
  sleep(0.5);
}

export function journeySa() {
  catalog(BASE_SA, 'sa');
  addToCart(BASE_SA, 'sa');
  sleep(0.8);
}
```

When you cannot deploy runners abroad yet, document that gap explicitly in the report—and pair with [CDN cache headers load testing](/en/blog/cdn-cache-headers-load-testing) so at least edge behavior is not invisible.

## Decision framework: distributed runners vs latency emulation

| Situation | Recommended action |
|:---|:---|
| Global product, SLOs per market | Runners in target regions; parallel scenarios with analytics-weighted RPS |
| Pre-prod budget, one staging stack | Emulate RTT cautiously + tag `region:*`; label results as approximate |
| CDN or geoDNS change this sprint | Resolver and hostname per region in env; compare before/after with same tags |
| Mobile backends with regional shards | Separate scenarios per shard URL; do not fold shards into one BASE |
| CI smoke only | Single region low RPS; full geo matrix nightly or weekly |

**Use distributed runners** when release gates depend on market-specific p99—not lab ping.

**Use RTT emulation** when you need directional signal before infra budget lands—never treat it as certification.

**Use per-region hostnames** when geoDNS is part of the product contract—internal `staging.global` shortcuts fake the journey.

## Observability and pre-release checklist

Geo tests fail silently when tags and DNS setup drift. Before you scale traffic:

- [ ] Document RPS split per region and the analytics source (gateway logs, mesh, or CDN).
- [ ] Record which resolver and hostname each runner used—screenshot or env export in run notes.
- [ ] Set alerts when `p(99){region:sa}` exceeds epsilon vs baseline while `region:eu` stays flat.
- [ ] Correlate k6 `region` tags with APM `geo` or `availability_zone` dimensions during incidents.
- [ ] Archive scenario JSON, regional env vars, and git SHA per release ([load testing in CI/CD](/en/blog/load-testing-in-ci-cd) for smoke; full matrix scheduled).

## How Performate coordinates multi-region scenario packs

Duplicating entire repos per locale does not scale. **Concrete workflow** for the EU + South America journey above:

1. **Import one collection** with catalog and cart requests duplicated into folders `EU` and `SA` (different base URLs or host headers). *Problem solved:* one source of truth for journey steps.
2. **Create two scenarios**—`journey_eu` at **20 req/s**, `journey_sa` at **12 req/s**—matching last week’s regional mix. *Problem solved:* honest weights without hand-editing executor blocks per tuning pass.
3. **Apply tags:** `region:eu` and `region:sa` on each scenario plus shared `route:checkout_path`. *Problem solved:* reports match the k6 tag model in this article.
4. **Run both and open comparison view**—filter by region and check whether SA p99 still meets the looser SLO while EU carries higher volume. *Problem solved:* one export for product and SRE.
5. **When marketing shifts traffic**, change RPS in the editor and re-run—same requests, new mix. *Problem solved:* geo shifts are parameter changes, not repo forks.
6. **Export k6** for scheduled multi-region jobs on runners your infra team provides.

That maps to the `cta`: coordinate multi-region scenario packs with consistent tagging.

## Closing takeaway

Geo distributed load testing is a **routing and residency** problem disguised as a latency chart. Run generators where users are—or be explicit when you emulate—and tag every request family by region so tail divergence blocks releases that look fine from one POP.

Run this week’s analytics-weighted regional mix before your next CDN or geoDNS change—and note which region still owns the p99 your SLO names.

[Try Performate free](https://performate.app) | [Book a demo](/demo) | [k6 scenarios](https://k6.io/docs/using-k6/scenarios/)
