The first migration meeting where someone asks "how long will this take?" is where a lot of import projects go quietly off the rails. Answer with a number and you commit to a schedule you cannot deliver. Answer with "it depends" and you lose credibility. The right answer is a small model — four dominant drivers, a per-driver estimate, and a range instead of a single number. The site's Bulk import time estimator is that model in a paste-in form. For the wider setting, the FHIR explainers for outpatient teams has more.
The Base Question
For a target of N patients with expected M observations, K conditions, and so on, how long will the import take on our target server?
That is what the estimator answers. The output is a range: fastest realistic and slowest realistic. Actual runs fall between.
The Four Drivers
- Resource volume — the raw count of resources to write
- Reference density — how many references each resource carries
- Terminology validation — whether coded values are checked
- Target server throughput — the sustained writes-per-second capacity
Every one of these is measurable. Together they predict duration within a factor of 2. For the deeper picture, the four dominant drivers of import duration is the entry.
The Straight-Line Estimate
Time = (resource count / throughput) * average-write-cost
Simple. Wrong at the edges but useful as a starting point. For an import of 5 million resources into a server that sustains 1000 writes per second, that is 5000 seconds — about 1.4 hours.
Real imports are longer than that because the throughput drops as the store fills, and because references and terminology add per-write cost.
The Multiplier For References
Resources with dense references (Observations, MedicationRequests, Encounters) cost more per write than resources with few references (Patient, Practitioner).
A rule of thumb: an Observation with subject + encounter + performer references costs about 1.4× a bare Observation. Multiply the write-count by an average reference multiplier per resource type.
For the parallelization implications, parallelizing an import without corrupting references is the entry.
The Multiplier For Terminology
Terminology validation adds per-code lookup cost. If your import validates every coded value, expect 30-60% longer duration than the same import without validation.
Turning terminology off for the initial import and running a validation pass afterward is often faster and easier to reason about.
The Non-Linear Ramp
Fresh servers write fast. As indexes grow and caches warm and cold-start effects fade, throughput settles into a steady-state that is usually 60-80% of the initial rate.
Import estimates that use the initial rate produce optimistic numbers. Use the steady-state throughput for the majority of the estimate.
The Throughput Ceiling
Every server has a max sustained write rate. Beyond that rate, you get:
- Rejected writes with retry-after
- Growing queue depth on the server
- Latency spikes
- Eventually, timeouts
The estimator uses your server's known ceiling (or a conservative default) as the upper bound.
For the monitoring side, monitoring an in-flight import for the numbers that matter is the entry.
Range Not Point Estimate
The right answer is a range. "Between 6 and 10 hours" is honest. "8 hours exactly" is a promise you cannot keep.
Communicate ranges. Update as the import progresses.
The Warm-Up Cost
Container spin-up, initial connection pool creation, and cold-cache queries dominate the first few minutes. Do not extrapolate from the first minutes; do not include them in throughput calculations.
Discard the first 5-10 minutes of throughput data when computing sustained rate.
The Post-Import Overhead
Import completion is not the end. Index rebuild, materialized view refresh, reference resolution, terminology binding checks. Each adds time.
Reasonable rule: post-import overhead is 20-40% of the import time itself. Include it in the total ETA.
The Short Version
Model the four drivers. Emit ranges not point estimates. Discard warm-up. Account for post-import overhead. Update as data comes in. The estimator does the arithmetic; the discipline is in what you feed it.

Sources
- HTML - HTML, HL7 - FHIR Bulk Data Access IG - Async Request Pattern