design decision

Where sessions live

The monolith split leaves session state homeless: today it rides the app server's memory and dies with it. The choice has narrowed to three operating models, with Redis and a short signed-cookie fallback recommended.

Constraints

Settled: the session id travels in a host-only Lax cookie

How the session id travels was decided before this page and the client change is merged, so it reads as one line — open it if you want the alternatives.

effort: lowrisk: low Host-only cookie, SameSite=Lax Set by the auth origin, sent on top-level navigations; nothing to store client-side and nothing for a script to read. My take: this is the least surprising transport. effort: lowrisk: med SameSite=Strict Tighter, but a session started by following a link from email arrives logged out — support saw this on the last trial. effort: highrisk: med Bearer header from local storage Works for the mobile client without a cookie jar; puts the id somewhere every script on the page can read.

The payments precedent is useful — and not equivalent

Payments settled a similar question in March. Its answer does not travel cleanly: payments had a tenth of our write volume and nothing that had to survive the store going down.

Payments, March
Writes
a tenth of ours
Store down
queue and retry

A payment is seconds long, so a Redis blip is a spinner and a retry, and nothing about their answer had to outlive the store.

Sessions, today
Writes
every request
Store down
readers stay signed in

Rolling expiry writes on every request, and an outage must not sign the fleet out — the constraint their page never had to argue.

Where should a session live?

effort: lowrisk: high Stateless JWT
Revoke
build a denylist
Outage
unaffected
New ops
none, at first

Sessions become signed tokens; no store at all. Revocation requires a denylist, which quietly reintroduces the store — with the hard parts (replication, expiry) still attached, and the account-takeover response waiting on them.

With nothing stored there is nothing to list, so the support console loses its per-device Revoke and "log out all devices" waits on the token's own expiry.

effort: medrisk: low Redis, cookie fallback
Revoke
delete the key
Outage
reads ride the cookie
New ops
what we already do

Sessions in the Redis we already run, keyed by an opaque id; a short-lived signed cookie covers Redis outages for reads, so a blip doesn't log everyone out. Revocation is a delete — which is the shape support's "log out all devices" needs. My take: this preserves revocation without making Redis an outage.

effort: lowrisk: med Postgres table
Revoke
delete the row
Outage
shares the database's fate
New ops
vacuum pressure

One table, no new moving parts. Every request writes a row (rolling expiry), so at our volume that's the primary's headroom spent on expiry bookkeeping — and a session outage becomes a database outage.

The two cookies

revocablenothing readable in itSession cookie An opaque id; the store says everything else. outlives a revocationsurvives a Redis blipFallback cookie A signed snapshot; outlives a blip, never a revocation.