Early Access · beta. Help test and shape the 1.0 release. Where the project stands →

Understanding throughput

Size throughput for your workload

Start with your peak message rate and partner response time. Use the measurements below as references, then test with your transforms, storage, and hardware.

These lab measurements are references for testing, not deployment guarantees. Synthetic-data tests used controlled conditions; the separate ~50 messages/second reference used anonymized, representative traffic. Partner acknowledgement time, transforms, and storage affect actual delivery rates. Keep each figure with its units and test conditions.

The short version

Four sizing inputs

One ordered stream in the lab

One strictly ordered MLLP feed delivered roughly 60 messages/second in the reference lab, with an instant-acknowledging partner. That rate implies about 16 ms per message across the serial path. It is a reference ceiling under controlled conditions, not a deployment guarantee.

Your partner systems dominate

An ordered HL7 v2 / MLLP feed waits for each acknowledgement before sending the next message. In the model below, a 50 ms partner acknowledgement gives about 15 messages/second. REST, SOAP, and FHIR request/response transports can run concurrently.

Size for the busiest hour

Size for the peak hour. In the de-identified hospital ADT traffic profiled here, the busiest hour ran about 2.7× the daily average. Measure your own traffic before applying that factor.

Test interfaces together

A four-shard configuration measured about 603 events/second, supporting a published figure of 40 million events/day after reserving capacity. Events count messages in and out. This configuration is not yet supported in production. A separate test held 1,500 concurrent interfaces without message loss. See §9 for both tests and their limits.

Check message size, ordering, partner acknowledgement time, and peak-hour rate before comparing throughput figures.

1 · Two speeds

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

Each message moves through stages. The engine writes each stage to the durable store before starting the next, so processing can resume after a restart.

The path is sender → receive & persist → route → transform → deliver → partner. The engine acknowledges the sender after storing the message. This is the intake path. The partner acknowledges delivery later, at its own processing rate.

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 — ~193/s per engine 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.

Stored messages buffer short partner slowdowns. The backlog drains as delivery catches up. Intake measures how fast messages are accepted; delivery measures how fast they reach the destination.

2 · The biggest factor

Your partner systems and their round-trip times

For HL7 v2 over MLLP, MessageFoundry delivers messages in receive order. It keeps one message in flight per destination:

  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.

The wait includes the network round trip and the partner's processing. For an ordered MLLP stream, estimate throughput as follows:

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

The reference model uses ~16 ms for the serial path before adding partner acknowledgement time. This comes from the ~60 msg/s lab rate, not a separate engine-latency measurement:

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 flat traffic around the clock. “Realistic” divides by the measured 2.7× burst factor from the profiled ADT traffic. This is an illustrative model using the ~16 ms lab reference. Measure your own partner latency and burst factor.

A partner that takes a quarter-second to acknowledge limits a single ordered stream to a few messages per second in this model.

What you can do about a slow partner

  • Open multiple partner connections only where the feed can be split into independent ordered streams and 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 the partner about acknowledgement latency and whether it can be reduced.
3 · Ordering & transports

Where the serial cap applies: HL7 v2 over MLLP

The one-message-in-flight model applies to HL7 v2 over MLLP. MessageFoundry waits for acknowledgement before sending the next message to that destination. This preserves receive order and limits a single stream's rate.

Ordering matters when a later message corrects an earlier one, as with HL7 admission, discharge, and transfer (ADT) messages.

Outbound REST, SOAP, and FHIR can keep several requests in flight to a partner. Concurrency overlaps partner waits, so the single-stream MLLP calculation does not apply.

For higher volumes, split feeds at the source across ordered interfaces. Their rates share a host and store, so treat their sum as an upper bound. See §7.

4 · The message store

Durability has a cost, and location matters

The store durably commits each stage before the next runs. This supports at-least-once delivery and recovery after crashes. Commit latency therefore affects each message.

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 — often by an order of magnitude. We have not run a controlled local-versus-remote A/B, so treat that as direction rather than a number to plan on — measure it on your own hardware before you size against it.
Disk speedThe store forces data to disk on commit for durability; faster storage (NVMe vs. shared SAN) lowers that per-commit cost.

The local-store lab test sustained tens of thousands of commits per second. That measures store commits, not end-to-end delivery across concurrent interfaces. See the measurements in §8 and §9.

5 · Message & transform factors

What else moves the number

  • Larger messages take more work to parse, store, encrypt, and transmit. The de-identified hospital ADT feed profiled here averaged 11.5 KB per message. Treat this as a reference, not a constant. Base64-encoded documents or images can greatly increase storage and bandwidth needs.
  • Routing can use tolerant field inspection. Full strict validation adds work and is optional per feed; enable it where you need conformance checks.
  • 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.
  • A handler may make an approved read-only lookup, such as a provider or eligibility check. Each lookup adds another round trip.
  • 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

Daily totals need a peak-hour rate

Healthcare traffic changes throughout the day, week, and year. Peak rates matter when sizing capacity.

  • Traffic often rises with morning registration, admissions, rounds, lab draws, and shift changes, then falls in the evening. Emergency department surges add short 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.

Size for the busiest hour. In the anonymized, representative ADT traffic profiled here, the busiest hour ran about 2.7× the all-day average. Measure your own peak-to-average ratio before using that factor.

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

These columns use the same engine rate but different traffic assumptions. A daily capacity figure needs a peak-hour rate, message size, and ordering conditions. The separate 40-million-events/day measurement has its own configuration and limits in §9.

7 · Putting it together

How to size a deployment

Estimate capacity using your ordering mode, partner latency, and peak traffic:

  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 — then test their combined rate against the shared host and store.

Use your partner latency and peak factor to estimate the required interfaces, then measure their combined throughput. In one 16-interface run, aggregate delivery reached ~87 msg/s; adding individual rates would have predicted ~960 msg/s. Shared host and store resources limit the total.

8 · Reference lab measurements

The numbers — with their conditions

These lab figures use synthetic data under controlled conditions. They are reference limits, not deployment guarantees. They were not measured against a live clinical system.

The site's ~50 messages/second figure uses anonymized, representative traffic. This guide's ~60 messages/second reference uses an instant-acknowledging lab partner. Both describe one ordered interface. Start with the representative figure, then measure the effect of your partner, transforms, and store.

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)~193 msg/s per engineAccept-and-persist, not end-to-end delivery; engine-CPU-bound, SQLite, one destination. Two engines measured ~383/s.
Single-node store commit ceiling~23,600–27,200 /sLocal-store commit test; measures commits, not end-to-end delivery across concurrent interfaces.
Per-message budget, one ordered lane~16 msAbout 16 ms per message, calculated as the reciprocal of the ~60 msg/s reference rate above. This is not a separate measurement of engine or database latency.

How your deployment may differ

  • The lab figure covers one strictly ordered MLLP stream. Concurrent request/response transports and multiple interfaces have different limits.
  • The lab partner acknowledges immediately. Real partner response times reduce ordered delivery rates.
  • Throughput depends on hardware and workload. Use published figures as references for your own tests, not guarantees.
9 · At enterprise scale

1,500 interfaces held loss-free — and 40 million events a day

One engine and one SQL Server 2022 database held 1,500 concurrent inbound MLLP interfaces open in a lab test. The run recorded zero message loss, with durable, at-least-once persistence for every message.

This test used MessageFoundry’s pooled claim scheduler, with low idle overhead across the open interfaces.

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)

That two-machine configuration sustained ~97 messages/second across all 1,500 interfaces using one unsharded database. The separate four-shard test below measured a different configuration.

Measured capacity — 40 million events a day

A separate campaign ended on 17 July 2026. It measured a four-shard engine configuration with a SQL Server store and produced the capacity figures below.

MeasurementEvents/secondPer hourPer day
Measured sustainable ceiling~603 (600–605)~2.17 million~52 million
Published capacity — ceiling less a >20% reserve~463~1.67 million40 million

These figures count total events: messages in and out. For one inbound and one outbound message, 40 million events/day represents about 20 million inbound messages/day. Check units before comparing engines.

The published 40 million events/day reserves more than 20% below the measured ~52 million ceiling for bursts and retries. These figures count completed work. An earlier, higher figure was withdrawn because it counted created events rather than drained events.

With 25% deliberate overload, database CPU averaged ~69% at the measured peak. The record concluded "not CPU-bound." Evidence points to write serialization, but the precise mechanism has not been isolated. Treat that explanation as an inference. A separate test reduced committed transactions by 28% but changed sustained throughput by less than 1%. Adding database cores did not increase the measured ceiling. The throughput report identifies no proven change that raises this limit. An earlier “near-saturated” description was retracted after a maximum was mistaken for a mean.

Keep these configurations separate: the 1,500-interface test used one store and two machines (~97 msg/s). The four-shard capacity test measured ~603 events/s.

The measurements used an already-loaded store with 2.6–4.0 million rows. The ceiling is a flow-balanced point estimate (~600 ± 4), not a long soak. A long soak could reveal drift and lower sustainable throughput. The fan-out comparison changed more than one variable. Six- and eight-shard configurations were not measured.

The measured multi-shard arrangement uses several engine processes sharing one server database. It is built and invariant-tested, but is not yet a supported production topology. Support awaits a clean multi-engine no-loss benchmark. Size production multi-engine deployments as active-passive until then. Ask about sizing →

Sizing checklist

Seven takeaways

  1. Measure partner acknowledgement time, store latency, message size, and transform work together.
  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. Size for peak traffic. The profiled ADT feed peaked at about 2.7× its daily average; measure your own ratio.
  5. Measure interfaces together. Their combined rate can be far below the sum of individual rates.
  6. Interrogate every throughput claim with four questions: message size, ordering, partner acknowledgement time, and peak-hour rate.
  7. The four-shard reference is 40 million events/day, counting messages in and out with more than 20% reserve below ~603 events/s. It is not a supported production topology. A separate run held 1,500 MLLP interfaces without message loss. See §9.
FAQ

Frequently asked questions

How many messages per second can MessageFoundry handle?

One strictly ordered MLLP interface delivered about 60 messages/second in the reference lab with an instant-acknowledging partner. This implies about 16 ms per message across the serial path. A separate reference using anonymized, representative traffic measured about 50 messages/second. Measure your own partner latency, transforms, and storage. REST and FHIR request/response transports can run concurrently.

Why is a messages-per-day number misleading?

Daily totals hide peak loads. The profiled de-identified hospital ADT feed peaked at about 2.7 times its daily average. Measure your own ratio and size for the busiest hour. Compare message size, ordering, and peak rate alongside daily totals.

What actually limits MessageFoundry's throughput?

An ordered MLLP stream waits for each partner acknowledgement before sending the next message. The reference model gives about 15 messages/second with a 50 ms partner wait. Store latency, message size, validation, and transforms also affect throughput. REST, SOAP, and FHIR request/response transports can run several requests concurrently.

How do I size a MessageFoundry deployment?

Start with the ordered lab reference of about 60 messages/second and account for your partner's acknowledgement time. Apply your measured peak-to-average traffic ratio; the profiled ADT feed used about 2.7. Split feeds into independent ordered interfaces where possible, then measure their combined rate. Shared resources limit the total.

How does MessageFoundry scale to very high volumes?

Split feeds at the source across independent ordered interfaces, then test them together. They share a host and store. One 16-interface run delivered about 87 messages/second; adding individual rates would have predicted about 960. Treat that sum as an upper bound, not a deployment estimate.

How many interfaces can one MessageFoundry engine run?

One lab run held 1,500 inbound MLLP interfaces open without message loss using one engine, its pooled claim scheduler, and SQL Server 2022. A separate four-shard test measured about 603 events/second, counting messages in and out. Its published reference is 40 million events/day with more than 20% reserve. This was a point estimate, not a long soak. The multi-shard arrangement is not yet a supported production topology; use active-passive for production multi-engine sizing.