7.7 Retries, effect classes, and idempotency

Composed flows touch multiple services, so failures are a fact of life. RS2 makes retry declarative and safe: you describe a policy, and the runtime only auto-retries what's safe to retry, deriving idempotency keys so effects don't duplicate.

Effect classes: what may auto-retry

Every call has an effect class that governs whether it can be retried automatically. Defaults are inferred from the method; you can override per call.

Class Meaning Method default
pure No side effects; always safe to retry GET/HEAD/OPTIONS
idempotent Repeating is harmless PUT/DELETE
keyed Retry only with an idempotency key
unsafe Never auto-retried POST/PATCH

Override on a call: { "call": { …, "effect": "keyed" } }. Marking a POST as keyed opts it into retry because the runtime will attach a stable idempotency key (below).

The retry policy

A declarative policy (camelCase), resolved most-specific-first: per-call retry → mount retry → tenant retry → runtime default (no retry).

{ "enabled": true, "maxAttempts": 4, "baseDelayMs": 250, "maxDelayMs": 5000,
  "backoffMultiplier": 2, "jitter": "full",
  "retryStatuses": [408, 429, 500, 502, 503, 504],
  "retryOnNetworkError": true, "respectRetryAfter": true }

Unlike v1 there is no $_retry pipeline variable — retry is configured on the step, mount, or tenant, never imperatively inside the flow.

The policy is target-agnostic: an external call (an absolute URL through the mount's httpOut grants, 7.3) retries exactly like an internal one — retryStatuses against the upstream's response, retryOnNetworkError for transport failures, backoff with the upstream's Retry-After honored. A keyed external POST sends the auto-derived Idempotency-Key header to the provider (the Stripe convention), stable across attempts.

Auto-derived idempotency keys

This is the safety mechanism that makes retry usable for effects. For keyed/unsafe calls inside a segment, the runtime derives a stable idempotency key from the invocation and the step's position. The key is the same across retry attempts, so if a segment re-runs, a keyed effect dedupes instead of firing twice. You don't plumb keys through your pipeline — composed effects dedupe across segment retries automatically.

(For external callers, you send your own Idempotency-Key header; the runtime's idempotency store handles replay. That's the same machinery, covered in 9.3.)

Putting it together

  1. A call's effect class decides eligibility (pure/idempotent retry freely; keyed retries with a key; unsafe doesn't).
  2. The retry policy decides how (attempts, backoff, which statuses).
  3. The runtime's derived keys make keyed retries safe within a segment.

The "segment" keeps coming up because it's the unit all of this operates on. The next section explains segments and the ?$plan tool that shows them.


Previous: 7.6 Transforms with JSONata · Manual home · Next: 7.8 Segments and ?$plan →