ADR-0020: SSE Feed — D1 Polling over In-Memory Pool
Context
The notification feed for the TV display (issue #146) needs to deliver notifications in real time. Two competing approaches:
- In-memory pool: Worker maintains
Map<churchId, WritableStream[]>;POSTcreates notification and pushes to active streams. - D1 polling: SSE endpoint does
SELECTon D1 every 3s and emits new notifications as SSE events.
Decision
Use D1 polling (approach 2).
Consequences
Positive
- Works with multiple Worker instances — Cloudflare scales horizontally without losing notifications
- Survives deploys —
EventSourcereconnects and polling resumes fromlastCheck - Simpler to implement and test (no pool management)
- No risk of memory leak from orphaned connections
Negative
- ~3s latency between creation and display on the TV screen (vs real-time with pool)
- D1 query cost: 1 query every 3s per active SSE connection (~20/min)
- CPU time: polling is I/O-bound (D1 wait), not CPU-bound — does not impact 30s limit
Mitigations
- 3s latency is acceptable for church TV display (the parent is already being called in-person)
- D1 query is indexed by
(church_id, created_at)and returns at most ~1 row per polling cycle - If latency becomes a problem, can migrate to pool without breaking clients (EventSource is compatible)
Alternatives Considered
| Alternative | Reason for rejection |
|---|---|
| In-memory pool | Loses connections on deploy; doesn't scale with multiple Worker instances |
| WebSocket with Durable Objects | Heavier infrastructure dependency; over-engineered for MVP |
| D1 + Durable Object as broker | Unnecessary complexity for expected notification volume |
References
- Cloudflare Workers Streams API: https://developers.cloudflare.com/workers/runtime-apis/streams/
- Workers duration limits: https://developers.cloudflare.com/workers/platform/limits/#duration
- Issue #146: https://github.com/barateza/neemias/issues/146
- Spec:
.specs/features/notifications/spec.md