What pooling costs, and where it stops.
Three questions decide the shape of a routed DICOM tier: what does putting a load balancer in front of your routers cost you, does a bigger load balancer help, and how long can a router run before it stops accepting images. We measured all three, on two independent clouds, at shipped defaults with no tuning — and we publish the conditions alongside the results. Two routers that each accept ~875 instances/sec do not accept 1,750 through a pool; they accept 626 to 785. And every router has a volume at which it refuses images. Ours is 438–583 MB of operational database, with no warning before it arrives.
Corrected 2026-08-24. Every rate in this paper is an ingest acceptance rate — how fast a router accepts and spools an image — not how fast it forwards one. The pooling ratio and the volume limit are unaffected; two claims are withdrawn outright. The correction, and the first direct measurement of delivery, are immediately below.
Every rate in this paper is an ingest figure, not a delivery figure
Both of the load harnesses behind this paper compute a rate the same way: sender-side C-STORE successes divided by send-loop elapsed. A XyDromatics Router returns success as soon as it has handed the object to its processing queue; the forward to the destination runs asynchronously behind that queue. The stopwatch therefore stops long before delivery. Every rate we have published — X′, Y′, the pooled figure Z, the load-balancer scaling curve, the cross-cloud comparison and the corrected baseline in §6 — is an ingest acceptance rate: how fast a router accepts and spools, not how fast it routes. There is no second timing mode in either harness, so none of the runs behind this paper can be re-read as delivery.
What is unaffected
- The pooling ratio in §2 and the scaling curve in §3. Both sides of Z / (X′ + Y′) are the same kind of number, measured the same way on the same rig, so the ratio is valid. 0.36, 0.43 and 0.45 stand. What changes is the noun: they are ratios of ingest, and this paper now says so.
- The volume limit in §4. It is a database size at the moment of refusal, not a rate, so the timing defect does not touch it. 438–583 MB stands, as does its portability.
- What a router does with work it has accepted. Across every run, what a router received and what it sent matched exactly. When overloaded it refuses at the door rather than losing what it took.
What is withdrawn
- The delivered-in-full claim. §1 previously said delivery was counted per SOP instance and a run reported only if every instance arrived. The count is sender-side. Our own reconciliation tooling records that a sender-side view conflates lost with unacknowledged, and marks a whole 50-instance batch failed on a single association fault. Receiver-side reconciliation exists, but it is a separate manual step and was not part of these legs.
- The recorded-database-volume condition. §8 previously presented operational-database size as a per-run recorded condition, and inferred from it that nothing entered the refusal regime. The campaign scripts that produced every figure here never record it. See §8 for what the record actually supports.
Delivery, measured directly for the first time
On 2026-08-24 we re-ran the published workload verbatim — 15-second ramp, 500-instance warmup, 32 concurrent senders — and counted delivery instead: one instance per object the router forwarded and the receiving sink acknowledged, clocked from first arrival to last delivery. Zero failures, queues verified empty at settle, and unlike the published campaign, a clean database on every host.
| Host | Ingest (measured) | Delivery (measured) | Overstatement | Delivered when the published clock stops |
|---|---|---|---|---|
| Router A — 8 vCPU, EPYC Rome, 1996 MHz | 511 (488–547) | 159 (154–164) | 3.21× | 8.0% |
| Router B — 8 vCPU, EPYC Milan, 3250 MHz | 751 (724–767) | 196 (195–197) | 3.83× | 2.0% |
Instances/sec, n=3 per host, range in parentheses. These two hosts are the single-router boxes behind the 527 and 817 instances/sec figures in our published benchmark set; the measured ingest column reproduces both. That is what establishes the delivery column was measured on the same hosts under the same workload rather than on a different rig.
A third configuration was run on Azure as a deliberate control, with the router's spool on a RAM-backed filesystem. This is not a customer configuration and should not be sized against. It delivered 336 instances/sec at association caps of 40/40/40 (n=4, 329–351) and 321 at caps of 32/10/3 (n=3, 316–329), against an ingest rate of roughly 724–755. Its forwarding leg considered alone ran at 524 and 480 instances/sec respectively. 19.4% of the workload had been delivered at the moment the ingest clock stopped.
The sizing consequence is not simply "divide by three." Compare the two rows: the faster silicon accepts 751 against 511, but delivers 196 against 159 — a delivery advantage of only 1.23×, well short of its advantage at the front door. The forwarding leg does not scale with processor speed the way ingest does, so a faster host widens the gap between what a router will accept and what it will move, and the overstatement grows with it: 3.21× on the slower box, 3.83× on the faster one.
We now have a measured delivery figure for a pool, and it is better than the ingest ratio suggested. Measured on 2026-08-24 on two 8 vCPU routers of identical silicon, all four router-class hosts reset to a clean database first (the sinks included), the published workload run verbatim, three runs per condition, every run settling at exactly 20,000 delivered with zero failures: pooled delivery is 432 instances/sec against 667 for the sum of the same two routers measured directly — a ratio of 0.65, not the 0.45 the ingest figures give. It is also 1.24× a single node (432 against 350), so on delivery a pool adds throughput rather than only buying availability. The ingest row of those same runs reproduces the published 0.45, which is what makes the 0.65 credible: same rig, same workload, same instant, different question. Delivery was measured on single routers; the pooled legs in §2 and §3 were not re-run under delivery timing. This paper therefore publishes no pooled delivery number, and we will not derive one from the ratios. Until it is measured, treat §2 and §3 as answering "how much ingest capacity does pooling cost" and nothing more.
Executive summary
Three questions decide the shape of a routed DICOM tier: what does putting a load balancer in front of your routers cost you, does a bigger load balancer help, and how long can a router run before it stops accepting images. We measured all three, on two clouds, and publish the conditions alongside the results.
Read the correction band above these bullets first. Every rate below is an ingest acceptance rate — how fast a router accepts and spools an image, not how fast it forwards one. The ratios survive that correction because both sides of each ratio are the same kind of number; the absolute rates should not be read as delivery.
- Pooling costs 55–64% of the raw sum of its parts. Two routers that individually accept 875 instances/sec each do not accept 1,750 through a pool — they accept 626 to 785 depending on the load balancer. This is the number vendors usually omit, and it is the single most important input to sizing a pool's front door.
- A bigger load balancer helps, then abruptly stops helping. Quadrupling it from 2 to 8 vCPU buys 25%, and most of that arrives by 4 vCPU. Past there the constraint moves off the balancer and onto the routers — measured directly, not inferred: at 2 vCPU the balancer runs at 97% CPU while the routers sit at 73–76%; at 8 vCPU the balancer drops to 28% and the routers become the busiest thing in the path.
- Every router has a volume at which it refuses images, and ours is at 438–583 MB of operational database. We measured it rather than leaving you to discover it.
- That limit travels; the rates do not. The two clouds' volume limits differ by 3% while runs on the same cloud vary by up to 33% — so the limit is a property of the software. Ingest between the same two clouds differs by 16–35%, because that is a property of your hardware. Both are worth knowing, for opposite reasons.
- There is no gradual warning before the limit. The acceptance rate held between 66% and 103% of baseline right up to the burst in which refusals began. One run was measurably faster than its own baseline as it hit the wall. No threshold you could watch would have warned you.
- Ingest capacity is not delivery throughput, and we had been publishing only the first. Measured directly for the first time on 2026-08-24, delivery ran 3.21× and 3.83× below the ingest figure on the two single-router hosts tested — 159 and 196 instances/sec delivered against 511 and 751 accepted. The pooling ratio and the volume limit survive; the sender-side delivery ledger and the recorded-database-volume condition do not. The correction band above sets out all of it.
- We corrected our own published figures upward during this work, and then withdrew the percentage we had put on that correction. A defect in our own software had been suppressing the figures. But the same curation rule we apply everywhere else voids every pre-fix run, so the old baseline cannot support a percentage. §6 and §7 describe both, because the correction and its retraction are equally part of the record.
The practical headline: size the pool's front door for the pooled number, not the sum of its routers; size the forwarding leg separately, because it is several times smaller and does not scale the same way; stop growing the load balancer at 4 vCPU; and bound your retention before volume bounds it for you.
Measurement note. Every number here is a measured result from dedicated performance rigs on two independent cloud providers, at shipped defaults with no tuning. 251 measured runs were produced; 86 are used and 165 were discarded, each with the reason recorded. §7 lists what was thrown away and why — including five runs we had wrongly kept — because a table of only the surviving runs says nothing about how they were chosen. The runs were curated rigorously and timed wrongly; §8 states exactly what the rate the harness computed does and does not represent.
§1 — The three legs, and why the ratio is the answer
A pooled measurement is only meaningful against the alternative. Every campaign therefore takes three legs under identical conditions, back to back:
| Leg | Path | What it establishes |
|---|---|---|
| X′ | Generator → Router A → Archive A | What one router accepts alone |
| Y′ | Generator → Router B → Archive B | What the other accepts alone |
| Z | Generator → load balancer → both routers → both archives | What the pool accepts |
Every leg is 20,000 synthetic CT instances at 32 concurrent senders, with a live receiving archive that drains — never a discard rule and never a dead destination. The draining archive matters, and it is worth being exact about why. It holds the router's processing queue under real back-pressure, so the acceptance rate is coupled to what is happening downstream. That coupling is not the same as measuring the forward, and it should not be read as one: the rate each leg reports is still sender-side C-STORE successes over send-loop elapsed.
Counting was sender-side, and the previous claim about it is withdrawn. This section used to say delivery was counted per SOP instance and a run reported only if every instance arrived. What was actually counted is what the sender recorded as accepted, which is not the same thing: our own reconciliation tooling documents that a sender-side view conflates lost with unacknowledged, and marks a whole 50-instance batch failed on a single association fault. Receiver-side reconciliation exists but is a separate manual step, and was not run as part of these legs. The curation rule in §7 still applies as written — a leg that did not complete cleanly is void — but it is a rule about sender-side completion, not a delivery ledger.
The ratio Z / (X′ + Y′) is the finding. If pooling were free it would be 1.00. It is not. Both sides of that ratio are ingest acceptance rates measured the same way on the same rig, which is what keeps the ratio valid even though neither side is a delivery rate.
§2 — What pooling costs
Every figure in this section is an ingest acceptance rate — see the correction band at the top of this paper. The ratio in the last column is the finding, and it is unaffected: both sides of it were measured the same way on the same rig.
| Load balancer | X′ | Y′ | Pooled ingest (Z) | Z / (X′+Y′) |
|---|---|---|---|---|
| 2 vCPU | 870.7 | 874.0 | 626.1 | 0.36 |
| 4 vCPU | 861.1 | 872.6 | 741.2 | 0.43 |
| 8 vCPU | 875.4 | 876.7 | 785.1 | 0.45 |
Instances/sec accepted, mean of 4 runs each. Azure, AMD EPYC 9V74 on every participating node — identical silicon throughout, which is what makes this table a scaling measurement rather than a hardware comparison.
Two routers that accept ~875 each do not accept 1,750 pooled. They accept 626 at a small load balancer and 785 at a large one — 36% to 45% of the arithmetic sum.
This is not a defect and it is not hidden overhead. A load balancer that accepts an image, chooses a destination, forwards it, and waits for the receiving router's acknowledgement before answering the sender is doing real work on the critical path, and that work is serialised per image. The honest way to state it is that a pool buys you resilience and a single ingress address, and you pay for them in the rate at which the tier will take work in. If you need raw ingest and can live with modality configuration pointing at individual routers, sending direct is faster.
What a pool does buy: one address for every modality to target, a node that can fail without the ingress disappearing, and capacity you can grow without touching modality configuration. Those are usually worth 40%.
What this table does not tell you. It sizes the front door. It does not tell you how fast a pool forwards, because the pooled legs were never run under delivery timing — and on the single routers where delivery was measured, it ran several times below the acceptance rate. Do not multiply a pooled figure here by anything to get a forwarding number; there is no measurement behind that arithmetic.
§3 — Does a bigger load balancer help?
Up to a point, sharply, and then it stops.
As in §2, the rates below are ingest acceptance rates. This is a scaling curve for the front door of a pool, and should be read as one.
| Load balancer | Pooled ingest | vs 2 vCPU |
|---|---|---|
| 2 vCPU | 626.1 | baseline |
| 4 vCPU | 741.2 | +18% |
| 8 vCPU | 785.1 | +25% |
Quadrupling the balancer buys 25%, and roughly three-quarters of that gain has already arrived at 4 vCPU. The reason is directly observable in the CPU record rather than inferred:
| Load balancer | Balancer CPU | Router CPU | What is actually limiting |
|---|---|---|---|
| 2 vCPU | 97% | 73–76% | the balancer |
| 8 vCPU | 28% | 64–66% | the routers |
At 2 vCPU the balancer is saturated and is the constraint. At 8 vCPU it is loafing at 28% and the routers behind it have become the busiest component. You have not removed the wall; you have moved it. Growing the balancer past 4 vCPU buys progressively less, and past 8 vCPU it buys nothing at all until you add routers.
Receiving archives never exceeded 42% CPU in any run, which is how we know they were not the constraint in any of these figures.
Do not read this curve as a forwarding-capacity curve. It describes how the acceptance rate responds to balancer cores. The forwarding leg responds to hardware quite differently: on the two single-router hosts where delivery was measured directly, the faster silicon accepted 751 instances/sec against 511 but delivered only 196 against 159 — a 1.23× delivery advantage, well short of its advantage at the front door. Cores bought for the front door are not cores bought for the forward.
A note on which rig this table comes from
These figures are from one rig only, and deliberately so. Our Vultr rig mixes AMD EPYC-Rome and EPYC-Milan across its load-balancer tiers, so its scaling curve varies core count and silicon at the same time. Its numbers are real and reproduce, but they cannot answer a scaling question, so they are not used for one. The Azure rig runs an identical processor on every node, so its curve isolates core count and nothing else.
§4 — Where a router stops accepting images
Every store-and-forward router keeps an operational record of what it has handled. That record grows. At some volume it becomes the limiting factor and the router begins refusing new images — correctly, because refusing is safer than accepting what it cannot account for.
We pushed eight sustained soaks, four per cloud, until that happened. The Azure soaks refused at 453, 458, 462 and 570 MB of operational database, a mean of 486 MB. The Vultr soaks refused at 438, 463, 514 and 583 MB, a mean of 499 MB. Every figure in those two lists is a database size in megabytes at the moment of the first refusal — not a rate.
The two clouds differ by 3%. Runs on the same cloud vary by up to 33%.
Three figures carry the argument, and all three read better drawn than tabulated. Figure 1 is the spread of that volume limit. Figure 2 is the gap from §2 — a pool against the sum of its routers. Figure 3 is the same pooled workload run on both clouds, which is the counterpoint to Figure 1.
Each dot is one sustained soak pushed until the router began refusing. The spread within a cloud is far wider than the gap between them.
Means 486 and 499 MB — 3% apart, on hosts whose raw ingest rate differs by 60%. The limit follows the software, not the hardware.
Same two routers, same workload. Measured individually, then driven through the load balancer. Both bars are ingest — instances accepted at the door and spooled, not instances delivered. Figure 4 measures the difference.
Pooling costs 55–64% of the arithmetic sum of the two routers' ingest rates. A bigger balancer recovers some of it — and stops helping once the routers behind it become the constraint. These percentages survive the 2026-08-24 correction: both sides of the comparison are the same kind of number, measured the same way, so the ratio is unaffected. Only what the bars are called has changed.
Identical build, identical workload, identical topology. The only difference is the hardware underneath. As in Figure 2, both series are ingest acceptance rates, not delivery rates.
The gap runs 1.16× to 1.35× — compare Figure 1, where the volume limit differs by 1.03× between the same two clouds. The limit follows the software; the ingest rate follows your hardware.
One uncontrolled variable in Figures 2 and 3, disclosed here because it is not in the run plan: the soak behind Figure 1 resets the operational database before each measurement, and says why. The campaign runs behind these two figures never touch that database and never record its size — and every host on both rigs was found well past the refusal range Figure 1 plots (1,467 MB on one router, 1,901 MB on one sink). So these rates were taken at an unknown, drifting database volume.
Two single routers, not the pools in Figures 2 and 3. Published workload verbatim, re-run 2026-08-24: three runs a box, zero failures, queues verified empty at settle, clean database. The left bar reproduces the published measurement. The right bar counts one instance per file the destination acknowledged, clocked from first arrival to last delivery.
Ingest overstates delivery by 3.21× and 3.83× — the gap between what the published figures measure and what they were taken to mean. At the moment the published stopwatch stopped, 8.0% and 2.0% of the work had actually been delivered; the rest was still in the queue. Nothing was lost — received and sent matched exactly on every run — but it had not left yet.
Size on the delivery number, not the ingest one. Between these two boxes ingest rises 511 to 751 while delivery rises only 159 to 196 — 1.23×. The forwarding leg does not scale with the processor the way the front door does.
A deliberate control on Azure with the spool on tmpfs — not a customer configuration — delivered 336/sec at 40/40/40 association caps (four runs, 329–351) and 321/sec at 32/10/3 (three runs, 316–329), against 524 and 480 for the forwarding leg alone. Even on tmpfs, only 19.4% of the work had been delivered when the clock stopped.
Every value plots a measured run, not a smoothed curve. The awkward numbers are the point: in Figure 1 the finding is the spread, not the means, and in Figure 3 the Vultr series changes processor generation between tiers, so the trend across tiers there is not attributable to tier size alone. Every axis in these figures labelled as a rate in instances/sec is an ingest acceptance rate — the correction at the top of this paper applies to the figures exactly as it applies to the tables. Figure 1 is a database size and is unaffected.
That is the whole finding, and the correction at the top of this paper does not reach it: a database size at the moment of refusal is not a rate, so how the harness clocked its rates is irrelevant to it. The variation between providers is an order of magnitude smaller than the variation within one, on hosts whose ingest rate differs by 60%. So this limit is a property of the software and it travels with it — you can plan against it on hardware we have never tested. Compare that with the rate figures in §2 and §3, which differ by 16–35% between the same two clouds precisely because they are a property of the hardware.
The cross-cloud rate numbers make the contrast concrete. Against Azure's pooled 626.1, 741.2 and 785.1 instances/sec at 2, 4 and 8 vCPU, Vultr accepted 537.9, 566.0 and 580.1 — a gap of 1.16× to 1.35× on identical build, workload and topology. The volume limit in Figure 1 moves 1.03× between the same two providers. One follows the software; the other follows your hardware.
A supporting constant: across all eight soaks and both clouds, the operational record consumed 896–902 bytes per handled instance. A budget in records converts to a budget in bytes the same way on any host.
§5 — There is no warning
The result that should change how you operate a router is not the limit itself. It is that nothing tells you that you are approaching it.
The acceptance rate across the eight soaks held between 66% and 103% of baseline right up to the burst in which refusals began. One run was measurably faster than its own baseline in the same burst that it started refusing images. There is no slow degradation to alarm on, no rising latency to trend, no threshold that would have given warning.
These soak rates are ingest acceptance rates like every other rate in this paper, and here that is worth stating twice rather than once: the signal that stayed flat is precisely the one a sender can see. A sender watching its own C-STORE rate has visibility of the front door and none of the forward, and the front door held steady until it shut.
Two things follow.
Monitoring cannot substitute for retention. An alert on the acceptance rate would not have fired. An alert on latency would not have fired. The only signal that precedes the cliff is the size of the operational record itself, which is why that figure is now reported in our own product telemetry.
A bigger disk does not help, and neither does a different database. The limit is reached by accumulating records, not by filling a volume. A retention policy that deletes nothing under sustained load fails identically on any database engine — just later, and at a larger number. Bound the record count.
§6 — We corrected our own numbers upward, and here is why that matters
The pooled figures previously published on our own site averaged 347.4 instances/sec. They are now 537.9 — the same rig, the same hardware tier, the same 20,000-instance workload. Both are ingest acceptance rates, and both were computed the same way, so this section describes a real movement in a real quantity; it is a correction within the ingest figure, not a correction of what the figure measures. That second correction is the one at the top of this paper, and it is separate.
We first published that gain as +48%. We are withdrawing the percentage rather than restating it. The rule this document applies to every other run — that a leg routed through a node on the older build is void — disqualifies every pre-fix run, including all of the ones that produced the 347.4 baseline. A percentage measured against a void baseline is not a measurement; it only looks like one. The absolute figures stand on their own, and they are the only thing we claim.
We did not find this because a customer challenged it. We found it re-checking our own rig, and two separate faults were responsible:
Our own software had a defect. The router's operational database used a write-ahead log with no bound on its growth. Under sustained load that log grew without limit, and the acceptance rate decayed as it grew. Every earlier figure was measured on a router already carrying that penalty. Fixed, the log now peaks at 8.2 MB against a pre-fix peak of 242.3 MB on the same rig and workload.
And our test rig had a fault we had not detected. One of the two receiving archives had been left on the previous software build for an entire measurement series — on both rigs. Because a pooled run routes through both archives, every pooled number we had published passed through a node that degraded as it received.
We are stating this plainly rather than quietly replacing a table, for a reason that matters to anyone reading a vendor benchmark: the failure mode of a benchmark is not usually a wrong number, it is a plausible one. Both faults produced results that looked entirely reasonable. Neither was visible in the output. They were found by verifying the rig itself rather than trusting that the tooling had done what it reported doing — which is now a written requirement of our test method rather than a habit.
A third fault was found afterwards, and it is the largest of the three: the harness was clocking the wrong event. Everything above was a legitimate correction to a number that was still not measuring what we said it measured. That is the case for auditing the definition of a measurement as well as its conditions, and it is why the correction band at the top of this paper sits above the executive summary rather than in a footnote.
§7 — What we discarded, and why
The campaign produced 251 measured runs. 86 are used. 165 were discarded. Each discarded run is retained with the reason it was dropped:
- Runs measured before the write-ahead-log defect was found. The rate on those decayed with cumulative volume, so any figure is a function of how much had already passed through that node rather than of the node itself.
- Runs where a receiving archive was on the previous build. A pooled leg routes through both archives, so these passed through a degrading node — including every pooled figure we had previously published.
- Runs on the rig whose processors differ between tiers, where a scaling question was being asked. The numbers are real; they simply cannot answer that particular question.
Five runs were kept that should not have been, and finding them is the most useful thing in this section.
Of five pooled runs at the smallest tier, four cluster between 529 and 550 instances/sec accepted and one sat at 418.7. We checked whether that one could be attributed to a fault: every host showed 0.00% hypervisor steal and the balancer was at 99% CPU — the same profile as a normal run. With no fault to blame, we kept it.
We had checked the wrong thing. The fault was not in the host telemetry, it was in the send record: on that run the sender recorded 19,657 successful C-STOREs out of 20,007 attempted, 350 short. Auditing the rest of the campaign on that basis found four more legs in the same state — all on the same rig — falling 50, 215, 3,317 and, in the worst case, 10,485 instances short of 20,000. That last one failed to place more than half its workload and still carried no void reason.
Those are sender-side shortfalls, and we are no longer describing them as instances lost. A sender-side count conflates lost with unacknowledged, and one association fault marks a whole 50-instance batch failed. What those five legs establish is that they did not complete cleanly — which is enough to void them, and is all we claim from them.
The rule stated in §1 — a leg counts only if the sender placed every instance — already disqualified all five. We had applied it to 160 other runs without noticing it applied here too. The tally above is the corrected one: 86 used, 165 discarded, not the 91 / 160 we first reported.
The correction runs against our own interest in the direction that matters: removing the 418.7 leg moves the smallest-tier figure up, from 514.0 to 537.9 instances/sec. We had been under-reporting ourselves by keeping a run we should have dropped. A curation rule that is only applied when it flatters you is not a curation rule, which is why all five are named here rather than quietly removed.
§8 — The conditions every figure was measured under
A rate without its conditions is not much use. Published alongside these results:
- What the rate actually is. Sender-side C-STORE successes divided by send-loop elapsed. A router answers a C-STORE with success once it has taken the object onto its processing queue, and forwards behind that queue, so the clock stops at acceptance. Neither of our harnesses offers a second timing mode. Every rate in §2, §3, §5, §6 and §7 is therefore an ingest acceptance rate, and none of the archived runs can be re-read as delivery. Delivery was measured separately on 2026-08-24; those figures are in the correction band at the top.
- The processor model, core count and memory of all fourteen participating nodes. Equal vCPU counts do not imply equal silicon.
- The operational-database size on the participating nodes was neither controlled nor recorded, and the claim that it was is withdrawn. This bullet previously reported a per-run condition of 477 MB to 1.9 GB and concluded from it that nothing entered the refusal regime and that every reported run delivered in full. The campaign record does not support that. Our soak script resets the operational database before each measurement and states the reason — the sink is itself a router and accumulates as it receives, so leaving it loaded would degrade X′ for a reason that is not the variable under test. The two campaign scripts that produced every figure in this paper do neither: they never touch the database and never record its size. Every host on both rigs was later found past the 438–583 MB knee from §4, two of them at 1,467 MB (a router) and 1,901 MB (a sink). These figures were taken at unknown and drifting database volume, and since the rate declines as that record grows, that is an uncontrolled variable sitting underneath the whole campaign. It does not invalidate the ratios — the legs of a ratio were run back to back on the same hosts — but it does mean no absolute rate here should be treated as a fresh-install number or as a repeatable one.
- CPU utilisation captured on every host for every run, which is how §3 can state where the constraint actually sat rather than infer it.
The delivery runs in the correction band were taken under the published workload verbatim and on a clean database, with queues verified empty at settle. They are the only figures in this paper measured with the database volume controlled.
Conclusion
Four numbers should shape how you size a routed DICOM tier.
Ingest capacity and delivery throughput are different numbers, and this paper measures the first. On the two single routers where both were measured, delivery ran 3.21× and 3.83× below acceptance. Size the front door from the figures here; size the forward from a delivery measurement, and ask for the one that matches your hardware rather than scaling ours.
Pooling costs roughly 55–64% of the arithmetic sum of its routers' ingest rates. Size for the pooled figure. The resilience and single ingress address are worth paying for; the payment is real and should be planned for rather than discovered.
Stop growing the load balancer at 4 vCPU. Most of the available gain has arrived by then, and past 8 vCPU the constraint has moved to the routers, where more balancer cores buy nothing.
Bound your retention before volume bounds it for you. The limit is real, it is measurable, it travels across hardware — and nothing warns you before you reach it.
Every figure here is reproducible on request, including by vendors who wish to dispute one. The raw results, the per-instance send records, the CPU samples and the scripts that produced them are retained and individually hashed — including the runs that produced the claims this paper has withdrawn. If a number here matters to your decision, ask us for the run behind it, and ask us whether it is an ingest number or a delivery number.
Synthology Healthcare Solutions Group, LLC. All software referenced is general-purpose, non-device software under FD&C Act §520(o)(1)(D) — not a medical device, and no clinical diagnostic function. Nothing in this whitepaper describes a medical device.
Ask us for the run behind the number.
We'll size a routed DICOM tier against your real instance rates and retention window — and show you the measured pooling cost, not an arithmetic sum, with ingest and delivery stated separately.