Runtime Resilience

Production services need more than happy-path effects. Timeouts, overload, and flaky dependencies are normal. This chapter covers coordination primitives and the id_effect_resilience crate for keeping programs responsive under stress.

What This Chapter Covers

  • RequestResolver — batch parallel lookups through a single fetch
  • SubscriptionRef — shared state plus change notifications
  • Redacted — schema-layer secrets with masked Debug
  • match_effect! — enum match helper with path-prefixed arms
  • id_effect_resilience — circuit breaker, rate limiter, bulkhead, hedged requests

RequestResolver

Effect.ts batches data-source lookups so N parallel getUser(id) calls become one SQL WHERE id IN (…). In id_effect, each pending lookup is a [RequestEntry] with a [Deferred] result slot. A [RequestResolver::run_all] receives sequential batches; entries inside a batch may run in parallel.

batching deduplicates keys per batch and calls your fetch function once:

#![allow(unused)]
fn main() {
use id_effect::{Deferred, RequestEntry, batching, run_async};

let resolver = batching(|keys| Effect::new(move |_r| {
    // build HashMap from keys …
    Ok(map)
}));
}

SubscriptionRef

[SubscriptionRef] combines [Ref] with [PubSub]. Every set / update publishes the new value; subscribe returns a [Queue] that receives the current value first, then every subsequent change.

#![allow(unused)]
fn main() {
use id_effect::{Scope, SubscriptionRef, run_async};

let cell = run_async(SubscriptionRef::make(0u32), ()).await?;
let scope = Scope::make();
let changes = run_async(cell.subscribe(), scope.clone()).await?;
}

Redacted values

Use [Redacted<T>] anywhere a schema or domain type might reach logs. Debug and Display print <redacted>; call expose only at trust boundaries.

match_effect!

The match_effect! proc macro prefixes variant names so the compiler checks exhaustiveness without repeating the enum path:

#![allow(unused)]
fn main() {
use id_effect::match_effect;

match_effect!(Color, paint, {
    Red(n) => n,
    Green => 0,
    Blue => 1,
})
}

id_effect_resilience

Add id_effect_resilience to your workspace dependency when you need operational guardrails:

TypeRole
[CircuitBreaker]Fail fast after repeated errors; half-open probe after cooldown
[RateLimiter]Token-bucket admission control
[Bulkhead]Cap concurrent in-flight effects via semaphore
[hedged]Race a delayed backup effect against a primary
#![allow(unused)]
fn main() {
use id_effect_resilience::{CircuitBreaker, hedged};
use id_effect::run_async;

let breaker = run_async(CircuitBreaker::make(5, Duration::from_secs(30)), ()).await?;
let out = run_async(breaker.call(fetch_user(id)), ()).await?;
}

Pair resilience primitives with Schedule retry policies from Part III — breakers shed load, schedules handle transient faults.