#6336·k6

Browser performance degredation while ramping up to 25 VUs despite low host CPU and memory utlization

Author: DevinCPCreated Aug 25, 2026Updated Sep 10, 2026
Labelsbugtriage

Brief summary

We're observing significant performance degradation when running browser tests with grafana/k6:latest-with-browser.

During a gradual ramp from 1 to 25 VUs (adding 1 VU every 2 minutes), end-user transaction response times steadily increase while throughput begins to decline.

Based on our investigation, this does not appear to be a traditional infrastructure bottleneck:

  • Host CPU is not saturated
  • Host memory is not exhausted
  • Network bandwidth is not saturated
  • Backend API response times remain relatively low
  • Increasing the container PID limit to 65535 did not improve the behavior

We also operate a separate browser testing platform based on Selenium + Chrome that executes the same application workflow and does not exhibit similar degradation, which makes us suspect the issue may be related to:

  • Browser-module scalability
  • Chromium integration
  • CDP communication overhead
  • Browser process management
  • Browser-side event processing

The behavior appears directionally similar to the discussion in #4628, where browser-heavy applications generated substantial CDP traffic and browser-side overhead.


Environment

Image:
grafana/k6:latest-with-browser

k6 version: 
v1.7.1+dirty (commit/9f82e6f1fc-dirty, go1.26.1, linux/amd64)

Browser:
Chromium 146.0.7680.153 (bundled Alpine version)

Execution model:
- k6/browser
- One browser context per iteration
- One page per iteration

Load profile

We gradually ramp from 1 to 25 VUs, holding each load level for approximately 2 minutes before adding another VU.


Observed behavior

As VUs increase:

  • Transaction response times trend upward
  • Throughput begins to decrease
  • Browser steps take progressively longer to complete
  • Degradation becomes noticeable around ~25 VUs
  • Higher loads can eventually result in failures and timeouts

This occurs while:

  • CPU remains below saturation
  • Memory remains available
  • Network bandwidth remains available
  • Backend APIs continue responding quickly

Because degradation begins before infrastructure resources are exhausted, we currently do not believe CPU, memory, or network capacity are the primary bottleneck.


Comparison with a Selenium-based solution

We also operate another browser testing platform using:

  • Selenium
  • Chrome
  • Chrome DevTools Protocol (CDP)

k6 architecture

k6
 └── Chromium

Both k6 and Chromium run inside the same container using grafana/k6:latest-with-browser.

Selenium architecture

Selenium Container
       │
       └── CDP
              │
Chrome Container

The browser runs in its own dedicated container and Selenium communicates with it remotely.

Observed differences

Using Selenium

  • No comparable response-time degradation up to approximately 100 users
  • Same application and user journey
  • Similar browser flags
  • Similar container sizing
  • Lower CPU utilization but higher memory utilization

Using k6/browser

  • Response times steadily increase during ramp-up
  • Throughput decreases
  • Degradation steadily increases from 1 to 25 browser VUs
  • Behavior persists after raising PID limits

We recognize this is not a perfect apples-to-apples comparison because the architectures differ. However, the difference makes us wonder whether browser-module architecture or CDP-related overhead could be contributing to the observed scaling behavior.


How transaction response times are measured

We are attempting to measure end-user perceived response time, not just network request duration.

Our transaction helper:

  1. Starts timing immediately before a user action
  2. Executes the browser action
  3. Waits for the expected UI state
  4. Records elapsed time

Example timing logic:

javascript
const start = Date.now();
await action();
const duration = Date.now() - start;

Example transaction:

javascript
const t2 = await transaction('02_Click_PRA', async () => {
    const header = page.locator(
        '//h2[contains(text(), "Example Header")]'
    );

    await header.click();

    const startButton = page.locator(
        '//button[contains(text(), "Example Label")]'
    );

    await startButton.waitFor({
        state: 'visible',
        timeout: 30000,
    });
});

The timer starts immediately before the click and stops when the expected element becomes visible.

As a result, the transaction duration includes:

  • User interaction
  • Browser rendering
  • DOM updates
  • AngularJS processing
  • SurveyJS rendering
  • JavaScript execution
  • XHR/fetch activity
  • Any browser-module overhead that occurs before the UI becomes ready

This same methodology is used in our Selenium-based implementation.


Application under test

The application is a client-heavy AngularJS SPA that includes:

  • AngularJS
  • SurveyJS
  • Knockout
  • Angular UI Bootstrap
  • ngResource
  • ngAnimate
  • ngFileUpload
  • Multiple custom components and plugins

One of the primary application bundles is:

/apps/pra/bundle.js
~2.45 MB (uncompressed)

This is a relatively large client-side application with significant rendering and state-management activity.


HAR observations

Backend responses appear healthy

Representative API calls during the affected transaction:

GET /api/forecasts/lobs     ~27.6 ms
GET /api/project/list       ~36.6 ms

Both requests returned HTTP 200 and showed low server-side wait times.

Significant browser-side queueing/blocking

Several HAR entries show browser-side queueing/blocking times larger than actual server processing times.

Examples:

/api/platforms
~130 ms total
~110 ms queueing

trumbowyg-ng.min.js
~98 ms total
~68 ms blocked

trumbowyg.table.min.js
~94 ms total
~51 ms blocked

In many cases, most observed latency appears to occur on the browser side rather than in backend processing.

Landing page observations

Initial application load appears relatively healthy:

DOMContentLoaded ~270 ms
Load Event       ~305 ms

We did not observe obvious backend bottlenecks or unusually slow initial page-load activity.


Why we suspect browser-side or browser-integration overhead

Several observations point away from infrastructure limitations:

  • CPU is available when degradation begins
  • Memory is available when degradation begins
  • PID limit increases had no effect
  • Backend APIs remain fast
  • HAR captures show significant browser-side queueing/blocking
  • A Selenium + Chrome implementation running the same workflow scales considerably further before exhibiting similar symptoms

Because the application, workflow, and browser technology are largely the same, we are interested in whether the behavior could be related to:

  • Browser-module scalability characteristics
  • Chromium process management
  • CDP communication overhead
  • Browser event processing overhead
  • Architectural differences between bundled Chromium and remotely controlled Chromium

Thank you for taking a look. We'd appreciate any guidance on whether this behavior aligns with known browser-module limitations or if there are additional diagnostics we should collect.

k6 version

v1.7.1+dirty (commit/9f82e6f1fc-dirty, go1.26.1, linux/amd64)

OS

RHEL7

Docker version and image (if applicable)

grafana/k6:latest-with-browser

Steps to reproduce the problem

At the moment we do not have a minimal public reproduction.

The behavior is consistently observed in our environment using a browser-heavy AngularJS SPA.

High-level reproduction steps:

  1. Run a k6/browser test using grafana/k6:latest-with-browser.
  2. Launch one Chromium instance per browser VU using the standard k6/browser APIs.
  3. Execute an end-user workflow that:
    • Navigates through a client-heavy AngularJS application
    • Performs UI interactions
    • Waits for UI elements to become visible before proceeding
  4. Gradually ramp load from 1 to 25 VUs, adding approximately 1 VU every 2 minutes.
  5. Measure end-user transaction duration from user action until the expected UI state becomes available.
  6. Observe transaction response times and throughput as VU count increases.

Expected behaviour

  • Transaction response times remain relatively stable as VUs increase, provided CPU, memory, network, and backend resources remain available.

Actual behaviour

Actual result:

  • Transaction response times steadily increase as VU count rises.
  • Throughput begins to decrease.
  • Timeouts/failures may occur at higher load levels.
  • The degradation occurs despite CPU, memory, and backend systems remaining healthy.