ADR-009: PostgreSQL-Backed Session Storage

On this page

Context

BFF services (canopy-web, canopy-portal) require server-side session storage for Keycloak OIDC state, CSRF tokens, and user preferences. The choice of session backend affects reliability, scalability, compliance auditability, and operational complexity.

Options Considered

  1. In-memory (MemoryStore) — simplest, zero dependencies. Sessions lost on restart. No horizontal scaling (sticky sessions required). No audit trail.

  2. Redis-primary — fast, widely used for sessions. Requires Redis operational expertise. Data loss risk on eviction under memory pressure. No built-in durability guarantees without AOF/RDB persistence tuning. Not queryable for compliance audits.

  3. PostgreSQL-primary with Redis cache — PostgreSQL as authoritative store, Redis as optional LRU read-through cache. Sessions survive restarts and Redis eviction. Queryable for compliance audits (IRS Pub 1075 access logging). Horizontally scalable (any replica reads from PostgreSQL). Redis provides sub-millisecond reads for active sessions.

  4. Cookie-only (encrypted JWT) — no server state. Limited payload size. Cannot revoke sessions server-side. Logout requires token blocklist (re-introducing server state).

Decision

Option 3: PostgreSQL-primary with Redis LRU cache.

  • tower-sessions-sqlx-store::PostgresStore as the authoritative session backend

  • Redis 7 (Alpine) as an optional LRU cache layer (128 MB maxmemory, allkeys-lru eviction)

  • MemoryStore is banned project-wide

NOTE
Amended by ADR-026. The applicant portal (canopy-portal) session storage backend is narrowed to Redis-primary by the separate ADR-026 (its sessions are short-lived, server-side-revocable, opaque tokens for anonymous applicants — not worker sessions). The worker portal (canopy-web) and the PostgreSQL-primary mandate above are unchanged.

Consequences

Positive

  • Sessions survive service restarts, Redis eviction, and Redis outages (graceful degradation to PostgreSQL-only)

  • Session data is queryable via SQL for compliance audits (who accessed what, when)

  • Horizontal scaling without sticky sessions — any service replica reads from PostgreSQL

  • Redis provides fast reads for the hot working set of active sessions

  • PostgreSQL is already a required dependency (no new infrastructure for session storage)

Negative

  • Write path is slower than Redis-only (PostgreSQL round-trip on session create/update)

  • Two systems to operate (PostgreSQL + Redis) instead of one

  • Redis cache invalidation on session revocation requires explicit delete

Constraints

  • canopy-web: 8-hour TTL, SameSite=Lax (Keycloak OIDC redirect compatibility)

  • canopy-portal: 30-minute TTL, SameSite=Strict (no cross-site auth flow)

  • Both: HttpOnly, Secure=configurable via CANOPY_SESSION_SECURE

Edit this page · default