Skip to content

k6 HTTP Batch Request Timing Issues Fix

DodaTech Updated 2026-06-24 3 min read

In this tutorial, you'll learn about k6 http batch request timing issues fix. We cover key concepts, practical examples, and best practices.

Your k6 http.batch() returns mixed results — some requests succeed while others fail, and you cannot distinguish their individual timing. Batch requests run in parallel but report aggregated metrics, hiding per-endpoint performance.

The Problem

import http from 'k6/http';
import { check } from 'k6';

export default function () {
  const responses = http.batch([
    ['GET', 'https://api.example.com/users'],
    ['GET', 'https://api.example.com/orders'],
    ['GET', 'https://api.example.com/products'],
  ]);

  // Cannot tell which endpoint failed
  responses.forEach((res) => {
    check(res, { 'status is 200': (r) => r.status === 200 });
  });
}
     ✓ status is 200

     http_req_duration........: avg=234ms  p(95)=890ms

One endpoint might be slow (890ms p95) but you cannot tell which one.

Step-by-Step Fix

1. Name batch requests individually

import http from 'k6/http';
import { check } from 'k6';

export default function () {
  const responses = http.batch([
    { method: 'GET', url: 'https://api.example.com/users', tags: { name: 'UsersAPI' } },
    { method: 'GET', url: 'https://api.example.com/orders', tags: { name: 'OrdersAPI' } },
    { method: 'GET', url: 'https://api.example.com/products', tags: { name: 'ProductsAPI' } },
  ]);

  responses.forEach((res, i) => {
    const endpoints = ['Users', 'Orders', 'Products'];
    console.log(`${endpoints[i]} API: ${res.status} (${res.timings.duration}ms)`);
  });
}

2. Add per-endpoint thresholds

export const options = {
  thresholds: {
    'http_req_duration{name:UsersAPI}': ['p(95)<500'],
    'http_req_duration{name:OrdersAPI}': ['p(95)<2000'],
    'http_req_duration{name:ProductsAPI}': ['p(95)<1000'],
  },
};

3. Handle partial batch failures

import http from 'k6/http';
import { check, fail } from 'k6';

export default function () {
  const requests = [
    { method: 'GET', url: 'https://api.example.com/users', tags: { name: 'UsersAPI' } },
    { method: 'GET', url: 'https://api.example.com/orders', tags: { name: 'OrdersAPI' } },
  ];

  const responses = http.batch(requests);
  const failed = responses.filter((r) => r.status !== 200);

  if (failed.length > 0) {
    console.log(`${failed.length} requests failed in batch`);
    failed.forEach((r) => console.log(`  ${r.url}: ${r.status}`));
  }
}

4. Measure batch response time manually

import http from 'k6/http';
import { Trend } from 'k6/metrics';

const batchDuration = new Trend('batch_duration');

export default function () {
  const start = Date.now();
  const responses = http.batch([
    { method: 'GET', url: 'https://api.example.com/users', tags: { name: 'UsersAPI' } },
    { method: 'GET', url: 'https://api.example.com/orders', tags: { name: 'OrdersAPI' } },
  ]);
  batchDuration.add(Date.now() - start);

  const allOk = responses.every((r) => r.status === 200);
  console.log(`Batch completed in ${Date.now() - start}ms — ${allOk ? 'all OK' : 'some failed'}`);
}

5. Use separate http.get calls for critical endpoints

import http from 'k6/http';
import { check } from 'k6';

export default function () {
  // Critical endpoint measured individually
  const users = http.get('https://api.example.com/users', { tags: { name: 'UsersCritical' } });
  check(users, { 'users status is 200': (r) => r.status === 200 });

  // Non-critical endpoints in batch
  http.batch([
    ['GET', 'https://api.example.com/analytics'],
    ['GET', 'https://api.example.com/health'],
  ]);
}

Expected output:

     http_req_duration{name:UsersAPI}.......: avg=45ms   p(95)=120ms
     http_req_duration{name:OrdersAPI}......: avg=320ms  p(95)=890ms
     http_req_duration{name:ProductsAPI}....: avg=67ms   p(95)=150ms

Prevention Tips

  • Tag each batch request with a unique name for per-endpoint metrics
  • Set per-endpoint thresholds for granular pass/fail criteria
  • Handle partial failures explicitly in your script
  • Measure total batch duration with a custom Trend metric
  • Use individual http.get() for critical endpoints that need separate timing

Common Mistakes with http batch

  1. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  2. Non-exhaustive pattern matches that compile with warnings then crash at runtime
  3. Misunderstanding that String is [Char] with poor performance for large text operations

These mistakes appear frequently in real-world K6 code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### What's the limit on batch request count?

There is no hard limit, but each batch request consumes a goroutine. For more than ~50 requests per batch, consider splitting into multiple batch calls. k6 has a default connection limit per host (unlimited in k6 OSS).

Can I batch requests to different hosts?

Yes, http.batch() can target different hosts. Each request goes to its own URL. The batch completes when all requests finish or the slowest request times out.

How does batch timing differ from individual requests?

http.batch() runs all requests in parallel and returns when the slowest completes. Individual http.get() calls run sequentially. Batch is faster for independent requests but offers less granular timing data unless you tag each request.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro