The single question every FHIR bulk-data client eventually asks is how long the export will take. The spec answer is "poll until it is done." That is technically correct and operationally useless. Every team that runs bulk exports at scale ends up building a prediction of its own, and the accuracy of that prediction determines whether the export is a scheduled event or a support ticket.
Naming the prediction inputs is the discipline. For related walkthroughs, our FHIR reference desk collects the surrounding material.
Why the Server Cannot Tell You
Bulk export duration depends on the server's own load, its concurrency limits, its persistence tier, and its delivery method. None of those are visible to the client through the bulk-data spec. The server does return a Retry-After header on the polling endpoint, but that header is a polling hint, not an ETA. For the spec-side detail, why the FHIR bulk data spec cannot tell you the ETA covers the gap.
A pass through the site's export duration estimator projects duration and payload size per engine from published benchmarks, which is the closest thing to a real ETA before the export starts.
The Formula That Actually Works
The ETA for a bulk-data export is roughly:
- ETA ≈ (resource_count × per_resource_serialize_ms + chunk_overhead) / parallel_workers
Every term is bounded by real numbers. Resource count comes from the export scope. Per-resource serialization time is empirically measurable against the engine. Chunk overhead depends on how many files the export produces. Parallel workers depends on the server's concurrency budget for exports.
The formula is not exact. It is precise enough to plan a schedule.
Resource Count Is the Biggest Driver
The single input that moves the ETA the most is the resource count. A small chart-review export of one thousand Patients with Observations is minutes; a full population export of one million Patients is many hours.
Estimate the resource count from the group definition or the type filter before starting the export. Estimates that leave the number implicit produce ETAs that are wrong by an order of magnitude. For the full list of drivers, the four things that dominate $export duration covers the shape.
Persistence Tier Sets the Ceiling
The server's persistence tier bounds how fast it can serialize resources. A well-tuned Postgres with the archive on SSD serializes at one rate; the same archive on cold storage serializes an order of magnitude slower.
Sizing plans should know which tier the export runs against. A ceiling that assumes hot storage but runs against cold produces ETAs that miss by hours.
Delivery Method Sits at the End
The last mile of a bulk export is delivery: signed URLs, direct download, Kafka topic. Each has its own throughput signature and its own retention behavior. The delivery step can add minutes or hours to the end-to-end ETA depending on the method.
For the specific delivery breakdown, delivery method and its impact on end-to-end ETA covers the trade-offs.
Report the ETA as a Range
A single ETA number is a hopeful guess. A range with low, mid, and high bounds is honest. Predict the range at export kickoff and tighten it as polling proceeds. The tightening is exactly what the client-side monitoring flow is for; for the shape of that, monitoring $export progress from the client side covers the pattern.
Every bulk-data operation that lands well starts with a predicted range, tightens the range through polling, and documents the actual duration for the next planning round. The compound benefit shows up when the next planner already has the data they need.

Sources
- HL7 FHIR Bulk Data Access IG - HL7 FHIR Bulk Data Access IG, canonical evergreen reference for $export duration modeling
