> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blnkfinance.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Load Testing

> Learn to perform load testing on your Blnk server using k6.

export const CtaCallout = props => {
  const {title, buttonLabel, href, trackingEvent, buttonTarget, rel = "noopener noreferrer", children} = props;
  const handleCtaClick = () => {
    if (typeof window === "undefined" || !trackingEvent) {
      return;
    }
    try {
      window.dispatchEvent(new CustomEvent("blnk:docs-cta", {
        detail: {
          name: trackingEvent,
          href
        }
      }));
    } catch {}
    try {
      window.posthog?.capture?.(trackingEvent, {
        href
      });
    } catch {}
    const gaPayload = {
      cta_href: href
    };
    try {
      window.gtag?.("event", trackingEvent, gaPayload);
    } catch {}
    try {
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: trackingEvent,
        ...gaPayload
      });
    } catch {}
  };
  const isExternal = typeof href === "string" && (/^https?:\/\//i).test(href);
  const target = buttonTarget ?? (isExternal ? "_blank" : undefined);
  const linkRel = isExternal ? rel : undefined;
  return <section className="cta-callout not-prose relative my-8 w-full min-w-0 overflow-hidden rounded-xl border border-zinc-200 p-5 dark:border-white/10">
      <div className="cta-callout-noise" aria-hidden="true" />
      <div className="cta-callout-layout">
        {title ? <div className="cta-callout-title-row">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" width="14" height="14" className="cta-callout-icon shrink-0 text-zinc-800 dark:text-zinc-200" aria-hidden="true">
              <g fill="none" fillRule="nonzero">
                <path d="M28 0v28H0V0h28ZM14.691833333333335 27.134333333333334l-0.012833333333333334 0.0023333333333333335 -0.08283333333333333 0.04083333333333334 -0.023333333333333334 0.004666666666666667 -0.016333333333333335 -0.004666666666666667 -0.08283333333333333 -0.04083333333333334c-0.011666666666666667 -0.004666666666666667 -0.022166666666666668 -0.0011666666666666668 -0.028000000000000004 0.005833333333333334l-0.004666666666666667 0.011666666666666667 -0.019833333333333335 0.49933333333333335 0.005833333333333334 0.023333333333333334 0.011666666666666667 0.015166666666666667 0.12133333333333333 0.08633333333333333 0.0175 0.004666666666666667 0.014000000000000002 -0.004666666666666667 0.12133333333333333 -0.08633333333333333 0.014000000000000002 -0.018666666666666668 0.004666666666666667 -0.019833333333333335 -0.019833333333333335 -0.4981666666666667c-0.0023333333333333335 -0.011666666666666667 -0.0105 -0.019833333333333335 -0.019833333333333335 -0.021Zm0.3091666666666667 -0.13183333333333336 -0.015166666666666667 0.0023333333333333335 -0.21583333333333335 0.1085 -0.011666666666666667 0.011666666666666667 -0.0035000000000000005 0.012833333333333334 0.021 0.5016666666666667 0.005833333333333334 0.014000000000000002 0.009333333333333334 0.008166666666666668 0.23450000000000004 0.1085c0.014000000000000002 0.004666666666666667 0.026833333333333334 0 0.03383333333333334 -0.009333333333333334l0.004666666666666667 -0.016333333333333335 -0.03966666666666667 -0.7163333333333334c-0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.023333333333333334 -0.023333333333333334 -0.025666666666666667Zm-0.8341666666666667 0.0023333333333333335a0.026833333333333334 0.026833333333334334 0 0 0 -0.0315 0.007000000000000001l-0.007000000000000001 0.016333333333333335 -0.03966666666666667 0.7163333333333334c0 0.014000000000000002 0.008166666666666668 0.023333333333333334 0.019833333333333335 0.028000000000000004l0.0175 -0.0023333333333333335 0.23450000000000004 -0.1085 0.011666666666666667 -0.009333333333333334 0.004666666666666667 -0.012833333333333334 0.019833333333333335 -0.5016666666666667 -0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.011666666666666667 -0.21466666666666667 -0.10733333333333334Z" strokeWidth="1.1667" />
                <path fill="currentColor" d="M14 2.916666666666667A1.75 1.75 0 0 1 15.750000000000002 4.666666666666667v6.302333333333334L21.207666666666668 7.816666666666667a1.75 1.75 0 0 1 1.75 3.031L17.5 14l5.457666666666667 3.151166666666667a1.75 1.75 0 0 1 -1.75 3.031l-5.457666666666667 -3.1500000000000004V23.333333333333336a1.75 1.75 0 0 1 -3.5 0v-6.302333333333334L6.792333333333334 20.183333333333337a1.75 1.75 0 1 1 -1.75 -3.031L10.5 14 5.042333333333334 10.848833333333333a1.75 1.75 0 0 1 1.75 -3.031l5.457666666666667 3.1500000000000004V4.666666666666667A1.75 1.75 0 0 1 14 2.916666666666667Z" strokeWidth="1.1667" />
              </g>
            </svg>
            <p className="cta-callout-title min-w-0 font-semibold text-zinc-800 dark:text-zinc-200">
              {title}
            </p>
          </div> : null}
        <div className={`cta-callout-body text-sm leading-normal text-zinc-800 dark:text-zinc-200${title ? " cta-callout-body--indented" : ""}`}>
          {children}
        </div>
        <a href={href} target={target} rel={linkRel} onClick={handleCtaClick} data-docs-cta={trackingEvent || undefined} className="cta-callout-button inline-flex items-center justify-center gap-1 rounded-full bg-white px-3 py-1.5 text-sm font-semibold transition hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 dark:bg-white dark:hover:bg-zinc-200">
          {buttonLabel}
          <span className="cta-callout-button-arrow" aria-hidden="true">
            →
          </span>
        </a>
      </div>
    </section>;
};

Use load testing to ensure that your Blnk deployment can handle expected traffic and maintain performance under various conditions. This is a critical step in setting up your Blnk server.

Blnk includes a comprehensive load testing suite built with [k6](https://k6.io/), a modern load testing tool. This guide walks you through finding, configuring, and running load tests to validate your Blnk server's performance across different scenarios.

***

## Before you start

Make sure you have:

1. Cloned the Blnk repository to your local machine
2. [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed, for running the Blnk server
3. k6 installed on your machine. Visit [k6.io](https://k6.io/) for installation instructions
4. Your Blnk server running and accessible

<Info>
  If you haven't deployed Blnk yet, follow the [installation guide](/home/install) first.
</Info>

***

## Getting started

You can find the load test files in the `tests/loadtest` directory of the Blnk repository. Navigate to this directory to access all available test scripts:

```bash theme={"system"}
cd tests/loadtest
```

***

## Main test script

The primary load test script (`script.js`) supports five configurable load-testing scenarios, each controlled via environment variables.
Each scenario targets a specific aspect of your Blnk server’s transaction performance, allowing you to test different load patterns and system behaviors.

* **Baseline**: Steady, constant load to measure normal performance.
* **Ramp**: Gradually increasing load to find when your server struggles.
* **Contention**: Tests when one balance gets most of the traffic.
* **Soak**: Long-duration test (2 hours) to find memory leaks and stability issues.
* **Spike**: Sudden traffic surge to test recovery behavior.

<Warning>
  By default, tests target `http://localhost:5001`. Ensure your Blnk server is running and accessible at this address, or configure the URL using the `URL` environment variable.
</Warning>

Use the `SCENARIO` environment variable to select which scenario to run. Each scenario has specific configuration options:

<Tabs>
  <Tab title="Baseline">
    Runs a steady, constant load to measure your server's normal performance. Use this to see how fast your server responds under normal conditions.

    ```bash wrap tests/loadtest theme={"system"}
    k6 run --env URL=http://localhost:5001/transactions --env SCENARIO=baseline --env VUS=100 --env DURATION=5m script.js
    ```

    <ParamField query="VUS" type="integer" default="100">
      Number of virtual users to run concurrently
    </ParamField>

    <ParamField query="DURATION" type="string" default="5m">
      Test duration (e.g., "5m", "30s", "1h")
    </ParamField>
  </Tab>

  <Tab title="Ramp">
    Slowly increases traffic over time to find when your server starts to struggle. Starts with 100 users and gradually increases to 1000 users across three stages.

    ```bash wrap theme={"system"}
    k6 run --env URL=http://localhost:5001/transactions --env SCENARIO=ramp \
      --env START_VUS=100 \
      --env VUS1=300 --env STAGE1=5m \
      --env VUS2=600 --env STAGE2=5m \
      --env VUS3=1000 --env STAGE3=5m \
      script.js
    ```

    <ParamField query="START_VUS" type="integer" default="100">
      Initial number of virtual users at the start of the test
    </ParamField>

    <ParamField query="VUS1" type="integer" default="300">
      Target number of virtual users for the first ramp stage
    </ParamField>

    <ParamField query="STAGE1" type="string" default="5m">
      Duration of the first ramp stage
    </ParamField>

    <ParamField query="VUS2" type="integer" default="600">
      Target number of virtual users for the second ramp stage
    </ParamField>

    <ParamField query="STAGE2" type="string" default="5m">
      Duration of the second ramp stage
    </ParamField>

    <ParamField query="VUS3" type="integer" default="1000">
      Target number of virtual users for the third ramp stage
    </ParamField>

    <ParamField query="STAGE3" type="string" default="5m">
      Duration of the third ramp stage
    </ParamField>
  </Tab>

  <Tab title="Contention">
    Tests what happens when one balance gets most of the traffic while others get less. This simulates real-world scenarios where some accounts are much busier than others.

    ```bash wrap tests/loadtest theme={"system"}
    k6 run --env URL=http://localhost:5001/transactions --env SCENARIO=contention \
      --env HOT_DEST=@hot-balance-0001 \
      --env HOT_RPS=308 \
      --env RANDOM_RPS=132 \
      --env DURATION=5m \
      --env VUS=200 \
      --env MAX_VUS=800 \
      script.js
    ```

    <ParamField query="HOT_DEST" type="string" default="@hot-balance-0001">
      The balance identifier that receives concentrated traffic (70% of requests)
    </ParamField>

    <ParamField query="HOT_RPS" type="integer" default="308">
      Requests per second targeting the hot balance
    </ParamField>

    <ParamField query="RANDOM_RPS" type="integer" default="132">
      Requests per second targeting random balances
    </ParamField>

    <ParamField query="DURATION" type="string" default="5m">
      Test duration (e.g., "5m", "30s", "1h")
    </ParamField>

    <ParamField query="VUS" type="integer" default="200">
      Number of pre-allocated virtual users
    </ParamField>

    <ParamField query="MAX_VUS" type="integer" default="800">
      Maximum number of virtual users that can be allocated
    </ParamField>
  </Tab>

  <Tab title="Soak">
    Runs a steady load for a long time (default: 2 hours) to check if your server has memory leaks or other problems that only show up after running for a while.

    ```bash wrap tests/loadtest theme={"system"}
    k6 run --env URL=http://localhost:5001/transactions --env SCENARIO=soak --env VUS=200 --env DURATION=2h script.js
    ```

    <ParamField query="VUS" type="integer" default="200">
      Number of virtual users to run concurrently
    </ParamField>

    <ParamField query="DURATION" type="string" default="2h">
      Test duration (e.g., "5m", "30s", "1h", "2h")
    </ParamField>

    <Warning>
      Soak tests run for extended periods. Monitor your server's resource usage during these tests.
    </Warning>
  </Tab>

  <Tab title="Spike">
    Simulates a sudden traffic spike to see if your server can handle the surge and recover afterward.

    ```bash wrap tests/loadtest theme={"system"}
    k6 run --env URL=http://localhost:5001/transactions --env SCENARIO=spike \
      --env BASE_RPS=200 \
      --env SPIKE_RPS=600 \
      --env WARM=2m \
      --env PEAK=1m \
      --env COOL=3m \
      --env PRE_VUS=300 \
      --env MAX_VUS=1200 \
      script.js
    ```

    <ParamField query="BASE_RPS" type="integer" default="200">
      Baseline requests per second during warm-up and recovery phases
    </ParamField>

    <ParamField query="SPIKE_RPS" type="integer" default="600">
      Peak requests per second during spike phase
    </ParamField>

    <ParamField query="WARM" type="string" default="2m">
      Duration of warm-up phase before spike
    </ParamField>

    <ParamField query="PEAK" type="string" default="1m">
      Duration of spike phase
    </ParamField>

    <ParamField query="COOL" type="string" default="3m">
      Duration of recovery phase after spike
    </ParamField>

    <ParamField query="PRE_VUS" type="integer" default="300">
      Number of pre-allocated virtual users
    </ParamField>

    <ParamField query="MAX_VUS" type="integer" default="1200">
      Maximum number of virtual users that can be allocated during the spike
    </ParamField>
  </Tab>
</Tabs>

***

## Understanding test results

**k6** provides comprehensive metrics for each test run. Key metrics to monitor include:

* **http\_req\_duration**: Request latency (p50, p95, p99 percentiles)
* **http\_req\_failed**: Error rate (should be \< 0.1%)
* **http\_reqs**: Total requests processed
* **vus**: Virtual users active during the test

<Steps titleSize>
  <Step title="Performance thresholds">
    The main test script (`script.js`) includes built-in performance thresholds that automatically fail tests if exceeded:

    * **Error rate**: Less than 0.1% (`http_req_failed < 0.001`)
    * **P95 latency**: Less than 300ms
    * **P99 latency**: Less than 600ms
  </Step>

  <Step title="Exporting results">
    The main test script automatically exports results to `summary.json`. Run any scenario:

    ```bash wrap theme={"system"}
    k6 run --env SCENARIO=baseline script.js
    ```

    Results are automatically saved to `summary.json` in the current directory.
  </Step>

  <Step title="Analyze results with TPM tool">
    The test suite includes a utility script to convert k6 JSON output into transactions per minute (TPM) CSV format.

    First, export k6 results as JSON:

    ```bash wrap theme={"system"}
    k6 run --out json=results.json --env SCENARIO=baseline script.js
    ```

    Then convert to CSV:

    ```bash wrap theme={"system"}
    python3 tools/k6_json_to_tpm.py results.json tpm_results.csv
    ```

    <ParamField query="input.json" type="string" required>
      Path to k6 JSON output file (use `--out json=output.json` when running k6)
    </ParamField>

    <ParamField query="output.csv" type="string" required>
      Path to output CSV file
    </ParamField>

    The generated CSV includes: minute timestamp, requests, transactions per minute (tpm), success ratio, p50/p95/p99 latency, and apdex score.
  </Step>
</Steps>

***

## Troubleshooting

### Connection refused errors

If you see connection errors, verify:

1. Your Blnk server is running and accessible.
2. The URL matches your server's address and port.
3. Firewall rules allow connections to the Blnk server.

### High error rates

If error rates exceed thresholds:

1. Check Blnk server logs for errors.
2. Verify database connection pool settings.
3. Review server resource usage (CPU, memory).
4. Consider reducing virtual users or request rate.

***

## Need help?

We are very happy to help you make the most of Blnk, regardless of whether it is your first time or you are switching from another tool.

To ask questions or discuss issues, please [contact us](mailto:support@blnkfinance.com) or [join our Discord community](https://discord.gg/7WNv94zPpx).

<CtaCallout title="Connect your ledger to Blnk Cloud" href="https://cloud.blnkfinance.com/auth/sign-up?utm_source=blnk_docs&utm_medium=documentation&utm_campaign=need-help" buttonLabel="Open Blnk Cloud" trackingEvent="clicked_cloud_signup">
  Sign up and manage your ledger with our back-office dashboard. You can invite teammates to collaborate and manage your ledger operations directly from the dashboard.
</CtaCallout>
