design decision

Where sessions live

The monolith split leaves session state homeless: today it rides the app server's memory and dies with it. Three candidates, one recommendation — Redis with a signed-cookie fallback. Click an option — or Tab to its mark and press Enter — to decide; your pick reaches the agent directly.

Constraints

Settled last week

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. chosen 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. choose one 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. choose one
Send

How payments were asked

Payments settled the same question in March. Their page put it in two cards with the operating cost on the front of each, which is a framing worth copying. Their answer travels less well, since they had a tenth of our write volume and nothing that had to survive the store going down.

payments, March already on call for it Redis Revocation is a delete, and the idempotency keys live there already. ledger primary Postgres table One table, but every request writes to the instance the ledger is on. 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.

Options

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.

choose one
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.

hit

miss

Redis

handle

cookie

choose one
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.

choose one
Send

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.

One revision is waiting on you below: accept or reject it with the controls in the margin, and the next version carries whichever you chose.

✓ Accept✗ Reject

What I pick up next

Whichever store wins, three jobs stand behind it, and each is argued somewhere above rather than in a card of its own. Pick the ones worth starting — click a row, or Tab to its mark and press Enter — and the box takes anything the rows don't cover, including "none of these".

A revocation drill at support's volume§ constraintschoose any Key rotation for the fallback cookie§ flowchoose any A write load test at request rate§ optionschoose any
Send