The heaviest studies on the smallest box.
Can a vendor-neutral DICOM load balancer survive a flood of 3D tomosynthesis and 10,000-slice CT studies arriving at once? We put SynthIQ on a dedicated performance rig — on the smallest instance a cloud will rent, 2 vCPU / 8 GB — and measured it. The result: 70 tomosynthesis studies and 30,000 CT objects passed through it using under 700 MB of memory, with no failed C-STORE on any run. SynthIQ does not decode or hold pixel data; it spools to disk and streams. Memory is never the wall. The object counts are sender-side — an acceptance ledger, not a receiver-verified delivery ledger (§5).
Executive summary
The fear with any DICOM proxy is the big study: when a mammography unit pushes a 1 GB tomosynthesis volume, or an "Alpha CT" dumps a 10,000-slice study, does the load balancer fall over? We answered it the only credible way — on a dedicated rig, on the smallest instance a cloud will rent (2 vCPU / 8 GB), with the system-under-test isolated and instrumented. The findings:
- No failed object on any run. 70 tomosynthesis studies (35 GB) and a mixed 30,000-object CT workload (30 GB), every C-STORE answered successfully. That is a sender-side count of objects accepted; we previously published it as "100% delivery" and that framing is withdrawn (§5).
- Memory peaked under 700 MB — about 8% of an 8 GB box — and plateaued (no leak) across 23-minute sustained runs.
- SynthIQ's memory is bounded by concurrency, not data volume. It never decodes or buffers pixels; each in-flight object spools to disk and streams to the backend, so the heap stays flat whether you send a handful of gigabyte objects or thirty thousand small ones.
- It stayed responsive throughout — its management API answered every health probe during every run; no stalls under load.
- The only thing that scales with the box is throughput, not memory. Extreme-instance studies are accepted faster with more cores; memory is never the limit. The rates we have measured are ingest acceptance, not delivery, and the two differ by roughly threefold (§7).
The practical headline: SynthIQ handles every modality on a 2 vCPU / 8 GB instance with memory to spare. You size the box for the throughput you need, not to survive the biggest study — and "the throughput you need" is the rate the path delivers at, which is well below the rate it accepts at (§7).
§1 — The real question, and the two fears behind it
"Can it handle big studies?" actually hides two distinct fears, and they stress completely different resources:
Fear 1: the giant object (memory)
A single 3D tomosynthesis volume is ~561 MB to over 1 GB — one such study outweighs thousands of routine images. The worry is that a few of these arriving at once will exhaust the box's RAM. This is the failure mode that constrains processing nodes, which must hold the decoded volume in memory.
Fear 2: the giant count (throughput & contention)
A wide-detector CT angio can be 3,000–10,000 thin slices — each small, but the sheer number stresses the per-object pipeline: connection churn, and the routing-state lookups that a study-aware balancer performs for every instance. This is the contention class that can choke a naïve database-backed router.
A trustworthy answer has to address both axes — few-huge and many-small — not one. This paper measures each.
§2 — Methodology
Credibility comes from rigor, so the method matters.
- Dedicated rig, isolated roles. Three machines on a private network: one generated DICOM load ("the modality"), one ran SynthIQ under test, one was the storage backend. Nothing else competed for resources.
- The smallest instance on purpose. The system-under-test was a 2 vCPU / 8 GB box — deliberately modest, so any ceiling would show first. (You cannot prove "no memory wall" on a 64 GB host.)
- Two backends. Runs were repeated against a fast no-op sink (to isolate SynthIQ's own behaviour) and a real production router (to confirm under realistic backend drain). The results matched.
- Both stress axes. A tomosynthesis profile (few huge multi-frame objects) and a high-count CT profile (a fleet of 1,000-object studies plus two 10,000-object "Alpha CT" studies), fired concurrently.
- SUT-side instrumentation. Every run sampled SynthIQ's own process memory, system free memory, CPU, temp-disk spool, and management-API responsiveness on a fixed interval — so the numbers are SynthIQ's, not inferred from the client.
- What the object counts and clocks are. Object counts and elapsed times in this paper are sender-side: an object counts when the C-STORE that carried it was answered successfully, and a run's clock stops on the last such response. That makes the rates here ingest acceptance rates and the counts an acceptance ledger. Receiver-side reconciliation is a separate procedure and was not run against these results (§5). The memory, CPU, spool and API-responsiveness figures are unaffected — those were sampled on the system under test itself.
§3 — The central finding: memory doesn't track data
The single most important result: SynthIQ's memory footprint is governed by how many objects are in flight at once, not by how big they are or how many you send. Because SynthIQ is a routing proxy — it inspects an object's identity to make a placement decision, but never decodes the pixels — each in-flight object is spooled to a temporary file and streamed to the chosen backend. The pixel bytes live on disk for the moments they're in transit; they never inflate the managed heap.
Plotted against the payload pushed through it, SynthIQ's heap is essentially a flat line hugging the floor of the box:
Measured peak heap on a 2 vCPU / 8 GB instance. The heap stays at the floor of the box across a 7× range of data volume — and never approaches the host ceiling. This is the spool-and-stream design: the in-flight pixel data is on disk, not in memory.
§4 — Fear 1, tested: 3D tomosynthesis (the giant object)
We pushed batches of 4 multi-frame tomosynthesis objects per study (~512 MB/study) at 10 concurrent studies, then sustained it, then repeated to check for leaks — through a real production router backend, on the 2 vCPU / 8 GB SynthIQ:
| Run | Studies / payload | Failed C-STOREs | SynthIQ peak heap |
|---|---|---|---|
| 10 concurrent | 10 / 5 GB | none | 274 MB |
| Sustained churn | 50 / 25 GB | none | 478 MB (plateaued) |
| Leak check (repeat) | 10 / 5 GB | none | 490 MB (held) |
| Cumulative (one process) | 70 / 35 GB | none | 490 MB |
Memory rose to a steady-state during the sustained run and then held flat — it did not ratchet up across the repeat, which is the signature of no leak. Throughout, ~7 GB of the box's RAM stayed free and CPU was never pinned. The disk spool absorbed the in-flight volume (sawtoothing up to ~19 GB and draining in waves) while the heap stayed under 500 MB.
§5 — Fear 2, tested: high-count CT (the giant count)
To stress the count axis we simulated a busy CT environment: a fleet of ten 1,000-object studies plus two 10,000-object "Alpha CT" studies, all arriving concurrently — 30,000 objects, ~30 GB, with a new routing decision and routing-state lookup for every object.
| Stream | Studies × objects | Payload | Failed C-STOREs |
|---|---|---|---|
| CT fleet | 10 × 1,000 | 9.8 GB | none |
| Alpha CT | 2 × 10,000 | 19.5 GB | none |
| Combined | 12 / 30,000 | ~30 GB | none |
A correction to this table. Its last column previously read "Delivery", with totals of 10,000 / 10,000 and 20,000 / 20,000 presented as a delivery ledger. Those totals were sender-side counts of successful C-STOREs, not a receiver-side reconciliation, and we have removed them rather than restate them. The sender's view cannot separate an object that was lost from one that was merely unacknowledged, and a single association fault marks a whole 50-instance batch failed regardless of what actually arrived. Receiver-side reconciliation exists as a separate procedure; it was not run against these results, so there is no delivery percentage here to quote. What the table does say is that nothing was refused: every object we sent was accepted, and SynthIQ reported no failure on any of them.
Over a 23-minute sustained run, SynthIQ's heap peaked at 620 MB and plateaued; ~6.9 GB of RAM stayed free; CPU was never pinned; and the management API answered every one of 282 health probes. Critically, SynthIQ absorbed 30,000 routing-state lookups — the exact contention pattern that overwhelms database-backed routers — without a stumble, because its study-affinity store records a placement once per study and serves the rest from a fast cache.
Few-huge or many-small, the answer is the same: heap under 700 MB, not one refused or failed object, on a 2 vCPU / 8 GB box.
§6 — "Why does SynthIQ use so little memory?"
Because it is a router, not a renderer. A SynthIQ placement decision reads an object's identity attributes — study, series, modality, SOP class — to choose which backend should receive it. It never decompresses the pixel data, never assembles a volume in RAM, and never holds a study to "process" it. Each object is received to a temporary spool file and streamed straight out to the chosen backend.
The consequence is the flat line in §3: the only memory in play is the per-object working set of however many objects are crossing the wire at one instant — bounded by concurrency, a few hundred megabytes — not the size of the studies or the total volume. This is the architectural difference between SynthIQ and a processing node (a PACS, a VNA, an AI pipeline), which must hold the decoded study and is sized for it.
§7 — What does scale with the box: throughput
Memory is settled. The lever that remains is throughput. SynthIQ opens a fresh, fully-released association per forwarded object — a deliberate reliability choice that eliminates a class of stale-socket failures — so an extreme-instance study (a 10,000-slice Alpha CT) clears at a rate governed by per-object handling and the available cores. On the 2 vCPU floor, that single 10,000-object study was accepted over about 23 minutes; on more cores it parallelises and clears faster, though well short of proportionally: quadrupling the load balancer from 2 to 8 vCPU bought 25% on our Azure rig, and most of that arrived by 4 vCPU. Throughput rises with cores, with diminishing returns; it is never gated by memory.
Both of those figures — the 23 minutes and the 25% — are ingest acceptance, timed from the sender's C-STORE responses. Behind SynthIQ sits a router that answers as soon as it has spooled an object and queued it, so a successful C-STORE means the object was accepted, not that the destination has it. The delivered rate is materially lower. On the two single-router hosts where we have measured delivery directly, counting on the receiving side, delivery ran at roughly a third of ingest — 159 instances/sec against 511 on one host, 196 against 751 on the other.
Delivery through a pool — measured, and it is the better number
An earlier version of this section said we had not measured delivery through a SynthIQ pool. We have now. On a separate rig where both routers are the same 8 vCPU silicon, each forwarding through its own outbound leg to its own destination: three runs per condition, the published workload unchanged, every router-class host — including both destinations — reset to a clean database first, and every run settling at exactly 20,000 delivered with zero failures.
| Measured | Router A alone | Router B alone | Sum of the two | Through the pool | Pool ÷ sum |
|---|---|---|---|---|---|
| Ingest acceptance | 858/sec | 864/sec | 1,722/sec | 770/sec | 0.45 |
| Delivery | 350/sec | 317/sec | 667/sec | 432/sec | 0.65 |
The ingest row reproduces the 0.45 pooling ratio we had already published at this balancer tier — on the same runs that produced the delivery row, which is what makes the delivery row trustworthy rather than merely new. The two rows are not the same fact. Pooling costs about a third of delivery throughput, not more than half, so the 0.45 we had been quoting is an ingest ratio that understates what a pool does for the work actually leaving the path. Pooled delivery is also 1.24× a single node — 432 against 350 — so on the delivery framing a second node buys throughput as well as availability, where the ingest framing made pooling look like a tax paid purely for redundancy. The split between the two nodes was even to within 1%, so balancing is not the constraint.
The reason the two ratios differ is structural. Ingest is the front door, and the balancer hop in front of it costs real acceptance capacity — that is what 0.45 measures. Delivery is the pipeline behind that door, and the pipeline is where a second node genuinely helps: each router forwards through its own outbound leg to its own destination. Measuring only the front door hid the benefit the pool exists to provide. Two nodes is what was measured; nothing about a third node is, and the ratios are not extended to one.
One further disclosure about the campaign that produced the 25% figure: it neither reset nor recorded the size of each router's operational database between measurements, and every host on both rigs was afterwards found past the volume knee at which a router begins refusing new work — a knee measured at 438–583 MB, against hosts found at 1,467 MB and 1,901 MB. Those figures were therefore taken at an unknown and drifting database volume. The delivery measurements quoted above were taken on a clean database with the queues verified empty at settle.
And capacity grows the same way it does for the rest of the platform — by adding nodes, not bigger boxes. A SynthIQ pool load-balances DICOM across multiple instances, and its weight-aware placement steers the heavy studies toward the backends with the most headroom. Scale-out is predictable and needs no forklift upgrade, and the architecture is highly available by design rather than dependent on a single oversized node. It is not, on the evidence above, linear: a second node delivered 1.24× one node, not 2×. Plan for the measured ratio, not for the arithmetic one.
§8 — Recommended configurations
Because memory is never the constraint, SynthIQ sizing is about throughput and concurrency, not survival of the biggest study. RAM is comfortable at 8 GB for any modality; cores set the pace.
| Site profile | Workload | Per instance |
|---|---|---|
| Entry / branch | any modality mix, modest concurrency | 2 vCPU / 8 GB / SSD |
| Standard | busy enterprise, mixed CT/MR/DBT | 4 vCPU / 8–16 GB |
| High-throughput | high instance-rate, many concurrent high-count studies | 8 vCPU / 16 GB |
- 2 vCPU / 8 GB accepts every modality without a failed object — the entry tier is not a compromise on correctness, only on throughput.
- Add cores for throughput, especially where extreme-instance studies must clear quickly.
- 8 GB RAM is ample across the board; high-IOPS storage is unnecessary, though a fast temp volume helps the spool.
- Beyond a single instance's throughput band, add a SynthIQ pool node.
One caution on reading this table. The tiers are stated in terms of concurrency and instance rate, and the instance rates we have measured for SynthIQ are ingest acceptance rates (§7). What a site experiences is the rate the pool behind SynthIQ delivers at, and on the single-router hosts where we have measured it, that ran roughly a third of ingest; measured through a two-node pool it was 432 instances/sec against 770 accepted (§7). Size the SynthIQ instance from this table; size the routers behind it from their delivered rate, not from what SynthIQ accepts.
Conclusion
The worry that a DICOM load balancer will buckle under big studies does not survive measurement. On the smallest instance a cloud will rent, SynthIQ took 70 tomosynthesis studies and 30,000 CT objects without a single failed C-STORE, using under 700 MB of memory, staying responsive throughout. Because it routes rather than renders, its memory is bounded by concurrency, not by the size or count of the studies — so 2 vCPU / 8 GB is enough for every modality, with room to spare. Size SynthIQ for the throughput you want — measured as what the path delivers, not as what it accepts — and you will never size it to survive the biggest study.
See SynthIQ on your workload.
We'll size a SynthIQ deployment against your real modality mix and instance rates — and show you the measured headroom.