Understanding throughput

Fast enough for whatever your health system throws at it

MessageFoundry moves messages faster than almost any real deployment needs. But the giant throughput numbers everyone likes to quote — ours included — can mislead. This is a plain-language guide to what actually sets the rate, and how to size for your environment in real terms.

Read this first. Every throughput figure here is a theoretical maximum, measured in a controlled lab under ideal conditions with synthetic data. Your real-world throughput will almost always be lower, and it's usually set by how fast your partner systems accept and acknowledge each message — not by the engine. MessageFoundry adds only a few milliseconds of its own overhead per message; the rest of the time is spent waiting on round-trips to your database and your downstream systems. Size against your systems, not against a headline number.

The short version

Five things that actually matter

The engine is nearly transparent

In our reference lab, MessageFoundry adds on the order of ~16 milliseconds of its own processing per message — roughly 60 messages/second end-to-end on a strictly-ordered MLLP feed, and ~450/second at the point of receipt. Ceilings under ideal conditions, not promises for your environment.

Your partner systems dominate

On an ordered HL7 v2 / MLLP feed the engine sends a message, waits for the partner to acknowledge it, then sends the next — so a partner that takes 50 ms to ACK caps a single MLLP stream near 15 messages/second, no matter how fast the engine is. Request/response transports (REST, SOAP, FHIR) run concurrently, so this cap is chiefly an MLLP concern.

"Messages per day" is a weak unit

Healthcare traffic is bursty. In the production ADT feeds we've profiled, the busiest hour runs about 2.7× the all-day average. You size for the peak hour, not the daily average — so a raw "X million/day" figure, on its own, tells you very little.

Scale by fanning out

A strictly-ordered interface is a single serial lane with a bounded rate — that's the physics of guaranteed ordering, not a product limit. Real capacity comes from running many interfaces: in testing, one engine and one database sustained ~350,000 messages an hour (~8.4 million a day) across 1,500 interfaces, loss-free — and it climbs with a bigger database or more engine+database pairs (§9).

Four questions to ask of any throughput claim — ours or a competitor's: (1) How big are the messages? (2) Is delivery strictly ordered or parallel? (3) How fast does the receiving system acknowledge? (4) What's the peak-hour rate behind that daily average? Without those four, a msg/s or msg/day number is marketing, not engineering.

1 · Two speeds

How a message flows — and why there are two different rates

A message moves through MessageFoundry in stages, each written durably to the message store before the next begins — that's what guarantees nothing is lost.

Two acknowledgements, two speeds. A message flows sender → receive & persist → route → transform → deliver → partner. The engine acknowledges the sender the moment it has safely stored the message — that's the fast intake path. The partner's acknowledgement comes later, once the message is delivered, and it's paced by the partner — that's the slower delivery path.

That gives you two distinct throughput numbers, and they are often very far apart:

RateWhat it measuresTypical relationship
Intake rate
(ACK-on-receipt)
How fast the engine accepts a message, stores it durably, and acknowledges the sender. The sender never waits for downstream delivery.Higher — ~450/s in our lab. Protects upstream senders from downstream slowness.
Delivery rate
(end-to-end)
How fast the engine gets a message all the way to the partner and confirms it landed.Lower — ~60/s in our lab. Paced by everything downstream, especially the partner.

The gap between them isn't waste — it's the buffer. MessageFoundry accepts and safely stores messages faster than it delivers them, so a momentary slowdown at a partner doesn't push back on the sender; the backlog drains as the partner catches up. When someone quotes a throughput number, ask which rate they mean — intake numbers look impressive but say nothing about how fast messages actually reach their destination.

2 · The biggest factor

Your partner systems and their round-trip times

This is the point most capacity conversations miss — for HL7 v2 over MLLP, an acknowledged serial stream. MessageFoundry delivers each MLLP message in the exact order received — a hard requirement for clinical feeds — so it holds one message in flight per destination at a time:

  1. Send message N to the partner.
  2. Wait for the partner to receive it, process it (often persist it to its own database), and acknowledge.
  3. Only then send message N+1.

That waiting time isn't the engine's — it belongs to the network round-trip and the partner's own processing — and because the MLLP stream is serial, it lands squarely in the critical path. For an ordered MLLP stream, throughput is approximately:

messages/second ≈ 1000 ÷ ( engine overhead + partner round-trip ), in milliseconds

The engine overhead is small and roughly fixed (~16 ms in our lab). The partner round-trip is yours, and it dominates as soon as it's more than a few milliseconds:

Partner acknowledges in…Approx. ordered throughput"Uniform" msgs/day*Realistic msgs/day†
~0 ms (lab ideal)~60 /s~5.2 M~1.9 M
10 ms~38 /s~3.3 M~1.2 M
25 ms~24 /s~2.1 M~0.8 M
50 ms~15 /s~1.3 M~0.5 M
100 ms~9 /s~0.8 M~0.3 M
250 ms~4 /s~0.35 M~0.13 M

* "Uniform" assumes traffic is perfectly flat around the clock — it never is. "Realistic" divides by a 2.7× burst factor, the realistic planning number. Illustrative first-order model anchored to the measured ~16 ms engine floor; your numbers depend on your systems.

The lesson is stark: a partner that takes a quarter-second to acknowledge each message holds a single ordered stream to a few messages per second, and no amount of engine or database speed changes that — the bottleneck has moved outside MessageFoundry entirely.

What you can do about a slow partner

  • Open multiple connections to the partner — where a feed can be split into independent streams, each destination connection is its own ordered lane, so N connections give you N parallel lanes (if the partner accepts them).
  • Fan out at the source — split one hot feed into several interfaces (see §7) so the aggregate isn't gated by a single serial lane.
  • Ask your partner about their acknowledgement latency — it's often the cheapest thing to improve, and the term that matters most.
3 · Ordering & transports

Where the serial cap applies: HL7 v2 over MLLP

The one-in-flight, wait-for-the-ACK model in §2 is about HL7 v2 over MLLP — an acknowledged serial stream, and the workhorse of clinical interfacing. MessageFoundry delivers every MLLP message in the exact order received, so only one is in flight to a destination at a time: the next can't go until the previous is confirmed. That in-order guarantee is what bounds a single MLLP interface's rate.

The bound isn't a MessageFoundry quirk — it's a property of any system that guarantees order over a single stream. And for HL7 ADT, where a later message can correct an earlier one, never delivering out of order is exactly what you want.

Request/response transports behave differently. Outbound REST, SOAP, and FHIR aren't a single serial stream — the engine can keep several requests in flight to a partner at once, so the partner round-trip is hidden by concurrency and throughput is higher. The serial cap above is chiefly an MLLP concern.

And for higher MLLP volumes you don't give up ordering — you add lanes: split the feed at the source (by hospital, facility, or service line) across more interfaces, each its own ordered lane. Aggregate throughput is the sum (see §7).

4 · The message store

Durability has a cost, and location matters

Every stage handoff is committed durably to the store before the next stage runs — that's what makes delivery at-least-once and crash-safe. So the store's commit latency is part of every message's journey.

ChoiceEffect on throughput
SQLite
(embedded, default)
Runs in-process with no network hop — the fastest per-commit path. Its ceiling is single-writer serialization, not a problem for most single-node deployments.
PostgreSQL / SQL Server
(server database)
Chosen for concurrency, high availability, and enterprise operations — not for raw single-stream speed. Every commit is now a network round-trip to the database server, which adds latency to each handoff.
Store locationMatters more than the database brand. A commit to a co-located local disk is dramatically quicker than one that crosses the network. In our testing a local NVMe store committed on the order of ~65× faster than a round-trip to a database on another box.
Disk speedThe store forces data to disk on commit for durability; faster storage (NVMe vs. shared SAN) lowers that per-commit cost.

One store serves many interfaces. A single node's store has enormous headroom — in our lab a local store sustained on the order of tens of thousands of commits per second, far more than any one ordered interface can generate. That's why the scaling story is "add interfaces," not "make one interface faster": the shared store is nowhere near the wall.

5 · Message & transform factors

What else moves the number

  • Message size — a small ADT message and one carrying an embedded document aren't the same unit of work. Bigger messages cost more to parse, store, encrypt, and transmit.
  • Parsing & validation — fast, tolerant field inspection on the routing path is cheap; full strict validation is deliberately opt-in per feed because it's the slow path. Turn it on where you need conformance, not everywhere.
  • Transformation complexity — how much work your handler does per message directly affects the rate. Heavy transformation is one of the largest hardware-independent reducers of throughput.
  • Live enrichment lookups — a handler may do a sanctioned read-only lookup (e.g. a provider or eligibility check). Each lookup is another round-trip that adds to a message's time in the pipeline — powerful, but use it deliberately.
  • Fan-out — if one inbound message is delivered to N destinations, that's N deliveries to commit and N partners to wait on. Each destination is its own independent lane.
6 · The misleading unit

Why "messages per day" tells you almost nothing

Healthcare message traffic is bursty — nothing like a flat, around-the-clock stream. It follows the operational rhythm of the facilities behind it, and that shape matters far more than the daily total.

  • Within a day — light overnight, ramping sharply in the morning with registration, admissions, rounds, lab draws, and shift changes, peaking during the busy clinical hours, tapering in the evening. ED and registration surges add short, sharp spikes.
  • Across a week — weekdays run several times heavier than weekends (elective admissions, scheduled procedures, clinics).
  • Across a year — seasonal load (e.g. respiratory-illness season) shifts the baseline further.

A system doesn't experience the daily average — it experiences the busiest hour. Size for the average and you'll be underwater exactly when volume is highest: queues back up and latency climbs right when timeliness matters most. In the production ADT feeds we've profiled, the busiest hour runs about 2.7× the all-day average rate — so a feed with a tidy daily figure is really asking for nearly three times that average rate at its peak, and that is the rate your interface has to sustain.

One ordered interface at 60 msg/sNaive (assumes flat traffic)Realistic (peak = 2.7× average)
Daily capacity~5.2 M messages/day~1.9 M messages/day

Nothing about the engine changed between those columns — only the assumption about traffic shape, and the naive number is off by nearly 3×. That's why a bare "handles X million messages/day" claim is close to meaningless. Always ask for the peak-hour rate, the message size, and the ordering guarantee behind any daily figure — those, not the daily total, tell you whether it fits your environment. When we do quote a daily figure — the ~8.4 million messages a day (~350,000/hour) a single engine and database sustained loss-free in testing (§9) — it comes bolted to its database, its conditions, and a "sustained, not peak" label, for exactly this reason.

7 · Putting it together

How to size a deployment

There's no single throughput number — there's a short calculation using your inputs:

  1. Start from the per-interface ceiling for your ordering mode (our lab reference: ~60 msg/s ordered, with an instant partner).
  2. Apply your partner's real acknowledgement time (§2). Usually the biggest reduction.
  3. Apply your traffic's burst factor (§6) — divide by ~2.7 for ADT-shaped traffic, or measure your own feed's peak-hour ÷ daily-average ratio.
  4. Fan out to enough interfaces to cover your aggregate peak, splitting feeds at the source.

A worked (illustrative) example. A partner acknowledges in ~25 ms and you have ADT-shaped traffic:

• Ordered throughput per interface ≈ 1000 ÷ (16 + 25) ≈ ~24 msg/s.
• Peak-aware daily capacity ≈ 24 × 86,400 ÷ 2.7 ≈ ~0.8 M messages/day per interface.
• To carry a 5-million-message/day aggregate, fan out to roughly 6–7 interfaces — each comfortably within one node's shared-store headroom.

Your inputs will differ; the point is the method. Plug in your partner latency and your peak factor, then count interfaces. A mega health system can't push all of its ADT through one interface, and it doesn't need to — it splits the feed at the source by hospital, facility, region, or service line across multiple interfaces. Aggregate capacity is the sum of the interfaces; the shared store on a single node has ample headroom for the combined load.

8 · Reference lab measurements

The numbers — with their conditions

These are the figures referenced above, stated with full conditions so you can judge how they map to your environment. They are theoretical maxima under controlled conditions using synthetic data — not guarantees, and not measured against any live clinical system.

A note on the numbers: elsewhere on the site you'll see a measured ~50 messages/second per interface — that figure comes from anonymized production telemetry. The ~60 messages/second used in this guide is a lab reference ceiling with an instant-acknowledging partner. Both describe the same well-behaved ordered interface; the ~50 is the number to plan around, and everything in §2 about partner round-trips applies on top of it.

MeasurementValueConditions
End-to-end delivery, one ordered interface~60 msg/sSingle strictly-ordered MLLP interface, synthetic ADT, pass-through (no heavy transform), server database over a LAN, default settings, instant-acknowledging partner.
Intake (ACK-on-receipt)~450 msg/sSame setup; measures accept-and-persist, not end-to-end delivery.
Single-node store commit ceiling~tens of thousands /sLocal NVMe store; the shared store is far from the bottleneck for realistic interface counts.
Engine's own per-message overhead~16 msDerived from the ordered end-to-end rate with an instant partner; the portion MessageFoundry contributes.

Why these are conservative, and why your mileage will vary

  • Ordered MLLP, single interface. The lab figure is a strictly-ordered MLLP stream — serial by design. Request/response transports (REST/SOAP/FHIR) run concurrently, and splitting a feed across more interfaces adds lanes; both go faster.
  • Instant partner. The lab partner acknowledges immediately. Real partners don't — and their round-trip time is usually the dominant factor. This is the single largest reason your end-to-end number will differ from the lab number.
  • No independent industry benchmark exists. There's no trustworthy public per-node throughput number for the major interface engines either — every published msg/s figure is hardware- and workload-dependent. Treat all such numbers (including these) as starting points for your own measurement, not guarantees.
9 · At enterprise scale

1,500 interfaces on one engine — held loss-free

A single MLLP interface is a serial lane (§3). At a health system's scale the more useful question is the inverse: how many interfaces can one engine hold at once without dropping a message? In a deliberately extreme test — 1,500 concurrent inbound MLLP interfaces on one MessageFoundry engine and one SQL Server 2022 database — we held all 1,500 open with zero message loss and durable, at-least-once persistence on every message.

That fan-out is where naive designs fall over. A one-worker-per-interface engine degrades sharply at this scale; MessageFoundry's pooled claim scheduler — the configuration used in this test — holds steady with low idle overhead. It's an architectural property, not a tuning trick.

The two boxes, on the same local network:

AWS EC2Engine boxDatabase box
Instancem7i.2xlargei4i.2xlarge
CPU / RAM8 vCPU / 32 GiB8 vCPU / 64 GiB
Storage100 GiB EBSLocal NVMe SSD (+ 100 GiB EBS)
RunsMessageFoundry engine (Windows)SQL Server 2022 (Windows)

On that config, aggregate end-to-end throughput at 1,500 interfaces was ~97 messages/second sustained (measured peak ~107) — the rate a single database durably commits across all 1,500 interfaces, not a per-interface figure. Every message is parsed, routed, transformed, durably committed to the database, and delivered:

MeasurementRatePer hourPer day, flat-out*
Sustained, zero-loss~97 msg/s~350,000~8.4 million
Peak (burst)~107 msg/s~385,000~9.3 million

* A flat-out ceiling for the i4i.2xlarge SQL Server 2022 database above, not a realistic daily load — real traffic is bursty (§6), so a live deployment on this config would carry less. Change the database and the number changes with it.

At 1,500 interfaces the ceiling was the database write tier, not the engine: engine CPU sat near two cores, and tripling the database connection pool didn't move it — the commit round-trip to the server database is the limit (§4). So the lever for more is a faster or sharded database (more IOPS, a larger instance, or multiple stores), or more engine+database pairs — not tuning the engine host.

Scope. One two-box configuration — a single engine, a single SQL Server 2022 store on local NVMe, a lightweight transform, and synthetic HL7 (no patient data). A capacity-planning reference point, not a hard limit: headroom scales with the database tier and by adding engine+database pairs. Talk to us for sizing →

In a nutshell

Seven takeaways

  1. MessageFoundry adds only a few milliseconds per message; your partner systems and round-trip times set the real throughput.
  2. There are two rates — fast intake and slower end-to-end delivery. Know which one a number refers to.
  3. Strict ordering is serial and therefore bounded; add parallel connections or fan out across interfaces when you need more.
  4. Traffic is bursty — size to the peak hour (≈ 2.7× the average), not the daily total.
  5. Scale by fanning out to multiple interfaces; one node's store has ample headroom.
  6. Interrogate every throughput claim with four questions: message size, ordering, partner acknowledgement time, and peak-hour rate.
  7. In testing, one engine + one database held 1,500 concurrent MLLP interfaces loss-free at ~350,000 messages/hour (~8.4M/day) — at that fan-out the database's write tier is the ceiling, not the engine (§9).
FAQ

Frequently asked questions

How many messages per second can MessageFoundry handle?

It depends on your environment far more than on the engine. In our reference lab a single strictly-ordered HL7 v2 / MLLP interface delivers about 60 messages/second end-to-end with an instant-acknowledging partner, and accepts about 450 messages/second at the point of receipt. Real numbers are usually lower and are set by how fast your partner systems acknowledge each message — the engine itself adds only about 16 milliseconds per message. That serial cap is specific to MLLP; request/response transports like REST and FHIR run concurrently, and fanning out across interfaces scales further.

Why is a messages-per-day number misleading?

Healthcare traffic is bursty, not a flat around-the-clock stream. In the production ADT feeds we've profiled, the busiest hour runs about 2.7 times the all-day average, so you have to size for the peak hour, not the daily average. Two feeds with the same daily total but different burst profiles need very different capacity. Always ask for the peak-hour rate, the message size, and the ordering guarantee behind any 'X million per day' figure.

What actually limits MessageFoundry's throughput?

Because delivery is strictly ordered, the dominant factor is usually your partner system's round-trip time: the engine sends a message, waits for the partner to accept and acknowledge it, and only then sends the next one. A partner that takes 50 ms to acknowledge caps a single ordered stream near 15 messages/second, no matter how fast the engine or database is. The message store, message size, validation, and transformation complexity also contribute, but the partner round-trip is usually the biggest term.

How do I size a MessageFoundry deployment?

Start from the per-interface ordered ceiling (about 60 messages/second with an instant partner), reduce it by your partner's real acknowledgement time, divide by your traffic's burst factor (about 2.7 for ADT-shaped traffic), then fan out to enough interfaces to cover your aggregate peak — splitting feeds at the source by hospital, facility, or service line. One node's message store easily absorbs the combined load of dozens of interfaces.

How does MessageFoundry scale to very high volumes?

By fanning out, not by forcing everything through one pipe. A strictly-ordered interface is a single serial lane with a bounded rate — that's the physics of guaranteed ordering, not a product limitation. You split feeds at the source across many interfaces and the aggregate is the sum of them; a single node's shared message store sustains tens of thousands of commits per second, far more than any one ordered interface can generate.

How many interfaces can one MessageFoundry engine run?

In a lab test, one MessageFoundry engine and one SQL Server 2022 database held 1,500 concurrent inbound MLLP interfaces open with zero message loss and durable, at-least-once persistence. At that fan-out the throughput ceiling was the database's write tier, not the engine — so you scale further with a faster or sharded database, or by adding engine-and-database pairs. That test used MessageFoundry's pooled claim scheduler.