ADR 0003: Require Valkey for distributed runtime capabilities
- Status: Accepted
- Date: 2026-08-06
Context
Ovahol needs capabilities such as rate limiting, idempotency, short-lived caching, distributed scheduler locks, and temporary job progress. Some of these capabilities, including rate limiting and timer coordination, are currently implemented with process-local memory and goroutines. Those implementations do not coordinate across API or scheduler replicas and lose their state when a process restarts.
PostgreSQL remains the authoritative store for business state.
Decision
Adopt Valkey as a required runtime dependency for Ovahol deployments. Its connection lifecycle is owned by the platform layer under backend/internal/platform/cache, with purpose-specific adapters for rate limiting, idempotency, caching, distributed locks, transient job state, and similar coordination capabilities.
Business modules depend on narrow interfaces rather than a general-purpose Valkey client. Every key has an explicit prefix, tenant scope where applicable, and TTL where appropriate.
Production API, worker, and scheduler processes that require these capabilities fail startup readiness when Valkey is unavailable. Unit tests may use in-memory fakes, and narrowly scoped tooling commands that do not exercise a Valkey-backed capability do not need to connect to Valkey.
Development, staging, and production do not share a Valkey deployment.
Required responsibility map
| Capability | Valkey responsibility | Durable authority |
|---|---|---|
| API and login rate limiting | Shared counters and windows across API replicas | None; limits are transient policy state |
| Idempotency | Atomic reservations and bounded replay metadata | PostgreSQL where a durable business result must be recoverable |
| Caching | Expiring reference, response, and computed-value caches | PostgreSQL or the owning external system |
| Scheduler coordination | Distributed leases/locks preventing duplicate execution | PostgreSQL for business effects and execution records that require history |
| Job coordination | Deduplication, leases, transient progress, and wake-up signals | PostgreSQL outbox/job records for work that must not be lost |
| Refresh sessions | Active-session lookup, expiry, refresh-token rotation state and replay detection | PostgreSQL for durable session and security history |
| Revocation | Session and token-family revocation checks across API replicas | PostgreSQL for durable revocation and audit records |
| Security counters | Login attempts, temporary account/IP throttles and other expiring security state | PostgreSQL for durable identity and security audit data |
Existing process-local implementations of these distributed capabilities are transitional and should be replaced by the corresponding Valkey-backed adapter. In particular, the current in-memory rate limiter must not remain the production implementation once Valkey is introduced.
Valkey does not own business scheduling rules. The scheduler application and the relevant business module decide what should run and when; Valkey provides the distributed lease that ensures only one scheduler replica performs a due run. Likewise, Valkey is not a substitute for the transactional outbox when a job or event must survive eviction or data loss.
For authentication, short-lived access JWTs may be validated locally. Valkey provides the shared real-time state for refresh sessions, rotation, replay detection, revocation, and login throttling. PostgreSQL retains the durable session record and security audit history. Refresh and security-sensitive session mutations normally fail closed when Valkey is unavailable.
Valkey is not the authoritative store for work orders, audit events, payments, notifications requiring guaranteed delivery, or other durable business records.
Failure policy
Each adapter defines its runtime failure behaviour. This policy cannot be chosen globally because rate limiting, caching, locking, and idempotency have different correctness requirements. A cache operation may degrade to its authoritative source, while a scheduler lease or correctness-critical idempotency reservation normally fails closed.
Consequences
- Valkey lifecycle, health, metrics, timeouts, and configuration are managed by application composition.
- Valkey is included in local, test, staging, and production deployment definitions, although unit tests may use fakes.
- Application readiness distinguishes Valkey failures from PostgreSQL failures.
- Tenant identifiers are mandatory in keys for tenant-owned data.
- Cache eviction must never delete authoritative business state.
- Deployments should be split when workload, eviction, durability, security, scaling, or availability requirements diverge.