Designing a Reference Resolver That Survives Partial Outages

Editorial illustration in bauhaus-grid style depicting a resolver with per-target circuit breakers, timeouts, and a partial-results surface

A reference resolver that assumes every target is reachable is a resolver that fails the first time a downstream server is slow, unreachable, or returning errors. Designing for partial outages up front means the resolver returns something useful even when a subset of references cannot be resolved right now. That is the difference between a system that degrades gracefully and one that goes down when its dependencies do. The site's Bundle reference walker shows unresolved references as a first-class output. For the wider FHIR framing, FHIR for analytics and Stars guides has more.

What Partial Outages Look Like

  • One of several target servers is down
  • A single target server is up but responding slowly
  • Specific resources are returning 404 when they should exist
  • Rate limits are throttling batched requests
  • Network partitions between the resolver and one target

Each failure mode is real. Each has a different response.

First Principle: Return Partial Results

A resolver that returns nothing on partial failure is a resolver that punishes the caller for every dependency's problems. The right shape is a partial result: the references that resolved plus a list of the ones that did not, with reasons.

Callers decide what to do with the unresolved set. Some render with placeholders. Some retry later. Some escalate. The resolver's job is to make each decision possible.

For the base pattern, resolving FHIR references without a full database walk is the entry.

Timeouts Per Target

Set a per-target-server timeout. When a target crosses the threshold, mark its outstanding references as timed-out and continue with the rest. That single mechanism prevents one slow target from stalling the whole walk.

A generous timeout is measured in seconds, not tens of seconds. If a target regularly needs more than a few seconds to respond, either its resource types belong closer, or the workload should reshape to not need synchronous resolution.

Circuit Breakers Per Target

When a target has failed enough times in the last window, stop hitting it for a cooldown period. That circuit-breaker pattern prevents the resolver from amplifying an incident on the target — a cascade where the resolver's retries make the target's incident worse.

Log every circuit-breaker trip. The signal is useful in incident review.

Fallback To Identifier When URL Fails

Reference.reference (URL-based) fails when the URL is unresolvable. Reference.identifier (system + value) may succeed via search when the URL fails. If both are present on the reference, try the URL first, fall through to identifier on failure. For the wider integrity picture, reference integrity in a distributed FHIR deployment covers the mechanics.

Retry Semantics That Match The Failure

  • 429 Too Many Requests — retry with the server's Retry-After
  • 503 Service Unavailable — retry with exponential backoff and a low ceiling
  • 500 Internal Server Error — do not retry automatically; escalate
  • 404 Not Found — do not retry; log the reference and continue

Blanket retry logic amplifies incidents. Specific retry logic per code respects the target server's signals.

Cache With Purpose

A resolver cache is not free. It costs memory, it introduces staleness, and it lives for a specific duration. The right cache is request-scoped: it lives for the current resolution and dies at the end. Persistent caches introduce more problems than they solve for reference resolution.

For the batching mechanic that pairs with this, batching resolution to reduce round trips is the entry.

Report The Failure Shape

Every unresolved reference should carry the reason: timeout, 5xx, 404, rate-limited, circuit-open, unknown target. That structured failure data is what makes retry policy and workflow response possible. Callers cannot make good decisions from an opaque "resolution failed".

The Short Version

Partial results are the default. Per-target timeouts and circuit breakers keep one bad neighbor from taking the walk down. Identifier fallbacks catch URL failures. Retry semantics match the failure shape. Report unresolved as first-class output with structured reasons. That is the resolver that survives.

Bauhaus-grid diagram of a resolver with per-target circuit breakers, timeouts, batched fallbacks, and a partial-results output surface, drawn as flat geometric grid tiles with purple accents on off-white

Sources