Skip to content

Back the circuit breaker with Redis for cross-instance state sharing #377

@greatest0fallt1me

Description

@greatest0fallt1me

Description

CircuitBreaker (src/lib/circuitBreaker.ts:48) stores all state — state, consecutiveFailures, lastFailureTime, lastStateChange — as private in-process fields, with config supplied only via the constructor (createCircuitBreaker(config) at src/lib/circuitBreaker.ts:186). When the API runs as multiple replicas (the repo ships a Dockerfile and docker-compose.yml), each instance trips independently, so a failing Soroban/Horizon upstream can be hammered N times over before all breakers open. This issue introduces an optional distributed (Redis-backed) state store so the breaker opens cluster-wide.

Requirements and context

  • Introduce a CircuitBreakerStore abstraction so the existing in-memory behavior becomes the default implementation and a Redis-backed implementation can be plugged in, mirroring the store-backed pattern already used by the rate limiter (src/services/rateLimiter.ts exposes InMemoryRateLimiter and a Postgres-backed store).
  • Add env-driven configuration (a CIRCUIT_BREAKER_* redis URL/enable flag) validated through src/config/env.ts / src/config/index.ts; default to in-memory when unset so local/dev/test behavior is unchanged.
  • Preserve the public execute<T>() contract and the CircuitBreakerState enum so all current callers (e.g. settlement/Horizon paths) keep working without changes.
  • Non-functional: a Redis outage must fail safe (degrade to local in-memory decisions, never block requests indefinitely); reads/writes to Redis must be bounded by a timeout consistent with the health-check timeouts in src/services/healthCheck.ts.

Acceptance criteria

  • A CircuitBreakerStore interface exists with in-memory (default) and Redis implementations.
  • With Redis enabled, two breaker instances sharing a key observe the same open/closed transitions.
  • When Redis is unreachable, the breaker degrades to local state and logs the degradation; requests are never blocked indefinitely.
  • Default (no env config) behavior is byte-for-byte the current in-memory behavior; existing circuitBreaker tests still pass.
  • New env vars are validated in src/config/env.ts and documented in README.md/.env.example.
  • Tests cover shared-state open/close and the Redis-down fallback path.

Suggested execution

1. Fork the repo and create a branch

git checkout -b feature/distributed-circuit-breaker

2. Implement changes — refactor src/lib/circuitBreaker.ts to delegate to a store; add the Redis store and config plumbing.
3. Write/extend tests — extend the existing circuit-breaker unit tests and add a store-level *.test.ts; mock Redis in-process.
4. Test and commit

npm run lint
npm run typecheck
npm test -- circuitBreaker --runInBand
npm run build

Example commit message

feat(resilience): add Redis-backed distributed circuit breaker store

Guidelines

Keep the repo's 90%+ coverage target (README.md) and the existing fail-safe resilience guarantees described in RESILIENCE.md. Add JSDoc to the new store interface and document configuration in README.md. Timeframe: 96 hours.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions