Stress-Testing HL7 Interfaces: A Guide to xk6-MLLP

Stress-Testing HL7 Interfaces: A Guide to xk6-MLLP

In the world of healthcare IT, we spend a lot of time ensuring that data is accurate. We build complex mappings, validate FHIR resources, and obsess over HL7 v2 segment structures. But there is one critical factor that often gets pushed to the late stages of a project: Performance.

Most healthcare integration engines (like Mirth Connect, Rhapsody, or Cloverleaf) communicate via MLLP (Minimal Lower Layer Protocol). While it is the industry standard for transporting HL7 messages over TCP, it presents unique challenges when it comes to modern load testing.

To solve this, I’ve developed xk6-mllp, an extension for the k6 load testing tool that brings healthcare protocols into the modern performance engineering stack.

The Challenge: Why MLLP isn’t HTTP

If you’ve ever used tools like JMeter or k6 for web apps, you’re used to the request-response cycle of HTTP. MLLP is different:

  1. Stateful TCP Connections: MLLP relies on persistent TCP connections. Opening and closing a connection for every message (as some naive tools do) creates massive overhead that doesn’t reflect real-world production traffic.
  2. Framing: Messages must be wrapped in specific start (0x0b) and end (0x1c 0x0d) bytes.
  3. Synchronous ACKs: A sender must wait for an Application Acknowledgment (ACK) before sending the next message on that same socket. Managing this timing at high concurrency is non-trivial.

Traditional load testing tools often require complex custom scripts or expensive proprietary plugins to handle these nuances correctly.

Introducing xk6-mllp

The xk6-mllp extension allows you to write performance tests in JavaScript (the native k6 language) while offloading the heavy lifting of the MLLP protocol to a high-performant Go backend.

How to use it

First, you need to build a k6 binary that includes the extension using xk6:

xk6 build --with github.com/gjergjsheldija/xk6-mllp

A Simple Test Script

Once built, you can define your load test in a simple .js file:

import mllp from 'k6/x/mllp';
import { check } from 'k6';

const client = new mllp.Client({
    addr: 'localhost:4444',
    timeout: '5s',
});

export default function () {
    const message = "MSH|^~\\&|SENDER|FACILITY|RECEIVER|FACILITY|202301011200||ADT^A01|123|P|2.3\r" +
                    "EVN|A01|202301011200\r" +
                    "PID|||PAT123||DOE^JOHN||19800101|M";

    const response = client.send(message);

    check(response, {
        'is status 200': (r) => r.status === 200,
        'has ACK': (r) => r.data.includes('MSA|AA'),
    });
}

Why This Matters

When a hospital system goes live, it doesn’t just process one message at a time. It deals with “bursts”—a morning rush of laboratory results or a sudden influx of admissions.

By using xk6-mllp, you can:

  • Identify Bottlenecks: Find where your integration engine starts dropping messages or where latency exceeds clinical requirements.
  • Capacity Planning: Determine exactly how many messages per second your hardware can handle before you need to scale.
  • Reliability: Simulate network instability and see how your Mirth channels handle timeouts and retries.

Final Thoughts

Healthcare interoperability shouldn’t be stuck with legacy testing methodologies. By bringing MLLP support to k6, we can apply the same rigorous performance standards to clinical data pipelines that we apply to the world’s largest web applications.

If you are working on high-volume healthcare integrations, I invite you to try out the tool and help improve it.

Source Code

Explore the project and contribute on GitHub: https://github.com/gjergjsheldija/xk6-mllp