A per-user Server-Sent-Events notification stream with proxy-safe heartbeats, Last-Event-ID reconnection replay, and multi-instance Redis fan-out.
Nobody has started this yet — be first.
Default heartbeat interval violates its own documented safety margin
The entire reason GET /notifications/stream sends a heartbeat comment on a schedule is to stay well under the idle-connection timeout of whatever sits between the browser and this app -- a load balancer, an nginx reverse proxy, a corporate firewall. The comment right above the setting spells out the math: the shortest common default out there is GCP's 30s backend timeout, and 15s was chosen specifically to leave a 2x margin against that. Someone changed the number without redoing the arithmetic, and the new value blows straight past the exact timeout it was supposed to leave headroom against. The first customer sitting behind a GCP HTTP(S) load balancer will see their notification stream silently die and reconnect over and over -- and because heartbeats are invisible SSE comment lines, nothing in the browser console or server logs points at "heartbeat interval too long" as the cause.
About this project
A per-user notification stream over Server-Sent Events instead of WebSockets, built around three things that make it production-shaped rather than a toy while True: yield: proxy-safe heartbeats (a : heartbeat\n\n comment line on a fixed interval, so idle-connection timeouts on load balancers and reverse proxies between browser and app never fire), Last-Event-ID reconnection replay (every event carries a monotonic id; on reconnect the server replays everything the client missed from a short-retention Redis Stream before resuming live delivery), and multi-instance fan-out (notifications publish to a per-user Redis pub/sub channel, so whichever app instance currently holds that user's live connection forwards it, working behind a load balancer fronting N instances/workers).
Two Redis primitives back one user's feed, both keyed by : a Stream () as the durable backlog, ed by and read by reconnection replay via ; and a pub/sub channel () for live fan-out, ed alongside the same write. subscribes to the channel reading the backlog specifically to close the race window between the two -- anything published in that gap lands on both paths, and one id comparison drops the resulting duplicate instead of re-sending it.
Notification payloads have no size cap
POST /notifications currently accepts a data payload of any size, and every notification gets written into Redis twice -- once into the backlog entry, once into the message fanned out to every live subscriber -- and stored again for every entry retained under the configured backlog length. A single oversized call becomes a standing resource cost multiplied by however many live subscribers and however many backlog entries are retained, with no configured limit anywhere actually stopping it.
user_idsse:stream:{user_id}XADDPOST /notificationsXRANGEsse:channel:{user_id}PUBLISHevent_stream()Fifteen practice tickets sit against this codebase, ordered easy to hard. Nine are real injected bugs: an inverted health check that reports "ok" during a Redis outage, a missing SSE blank-line terminator, an off-by-one that silently drops the first character of every bearer-token user id, a >= that lets an exact Last-Event-ID match through as a duplicate, a pub/sub message key that drifted out of sync with the field name the live-delivery code reads it back under, a leaked Redis connection on a subscribe failure, and others. Six are small missing features: per-event-type metrics, a multi-user broadcast endpoint, a per-user connection cap, and a capstone that wires graceful shutdown through every open stream. Each ticket has its own dedicated, currently-failing pytest test -- and one bug (a spurious heartbeat emitted on every fresh connection) is severe enough to also break four tests in the project's own tests/ suite until it's fixed, a documented, deliberate lesson in how one bug that happens to run first in a request path can mask others sitting right behind it.
curl localhost:8000/healthz
curl -N -H "Authorization: Bearer alice" localhost:8000/notifications/stream
# watch `: heartbeat` lines arrive roughly every 15s with no other traffic
Work the tickets in practice_tickets/tickets/ (ordered easy → hard); each names one dedicated pytest test in practice_tickets/tests/:
pytest practice_tickets/tests/test_ticket_01_heartbeat_default_margin.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
Tickets 14 and 15 need genuinely concurrent, long-lived streams, which the ASGI test transport used everywhere else in this project can't represent -- their tests spin up a real (short-lived) uvicorn server on a loopback socket instead (LiveServer in practice_tickets/tests/conftest.py).
How to run it locally, step by step.