From Legacy Rails 5 to 7: A Migration Story (What Broke, What We’d Repeat)

Real-Time Updates in Rails: Action Cable vs Polling vs SSE for Admin Dashboards
Real-time is expensive—connection state, memory, and cognitive load. Pick the smallest tool that fits the job.
Internal admin dashboards rarely need the same “live” guarantees as a trading terminal or a multiplayer game. What they do need is fresh enough data without melting Postgres, clogging Redis, or debugging WebSockets at 2 a.m.
Here’s how I think about Action Cable, polling, and Server-Sent Events (SSE) for Rails-backed admin UIs—and when each one is the boring, correct choice.
What you’re really optimizing for
Before choosing a transport, answer three questions:
- How stale can the UI be? (5 seconds vs 500 ms changes the design completely.)
- Who is connected? (Ten internal admins vs ten thousand public users.)
- What triggers an update? (User action, background job completion, external webhook, CRUD from another tab?)
If you can’t justify sub-second latency, you probably don’t need WebSockets yet.
Option 1: Polling (the default I reach for first)
How it works: The browser calls GET /admin/metrics (or similar) on a timer—every 10s, 30s, or 60s.
Pros
- Dead simple: standard Rails controllers, no extra connection infrastructure.
- Works through any proxy, load balancer, or corporate firewall that hates long-lived connections.
- Easy to cache at HTTP layer (short
Cache-Control, ETags) when data allows.
Cons
- Wasted requests when nothing changed (mitigate with conditional requests or a cheap “version” endpoint).
- “Feels” less live at long intervals—fine for ops dashboards that don’t need tick-by-tick updates.
Rails tips
- Keep payloads small; return only deltas or cursors when tables are huge.
- Back every poll with an indexed query—
WHERE updated_at > ?beats full table scans. - Rate-limit per user if the dashboard is ever exposed broadly.
Rule of thumb: If your stakeholders are fine refreshing or waiting a few seconds, polling is correct.
Option 2: Server-Sent Events (SSE)—the middle path
How it works: The client opens one long-lived HTTP connection; the server streams events (text/event-stream) as things happen—job finished, record updated, import completed.
Pros
- Simpler than WebSockets for server → client push (one direction).
- Fits “notify the dashboard when this Sidekiq batch completes” beautifully.
- Still HTTP-ish—many teams find it easier to reason about than a full socket stack.
Cons
- Not supported for IE-era stacks (usually irrelevant today); check mobile proxies if you have odd clients.
- Some older proxies buffer streaming responses—test on your real hosting path.
- If you need client → server streaming or binary frames, you’ll add normal HTTP POSTs anyway.
When I pick SSE: Admin views that need occasional pushes (notifications, progress bars, “import done”) without full duplex chat.
Option 3: Action Cable (WebSockets)—when you truly need live
How it works: Persistent bidirectional connections; great for collaboration, presence, or high-frequency updates.
Pros
- Low latency once connected; can push tiny messages without polling overhead.
- First-class in Rails (
Action Cable+ Redis adapter in multi-process setups).
Cons
- Operational surface: connection pools, Redis memory, reconnect storms, sticky sessions vs shared pub/sub.
- Harder debugging than “open Network tab and see a GET every 30s.”
- Easy to overbuild internal tools that only needed SSE or polling.
When I pick Action Cable: Multiple admins editing the same resource with near-real-time conflict visibility, live counters that update many times per second, or features that are inherently push-heavy.
A practical decision ladder
- Start with polling + good indexes + small JSON.
- If users need push notifications or progress without hammering the DB, add SSE for those streams.
- If you outgrow that—collaboration, very high churn, or bidirectional needs—Action Cable.
Pick the boring option until revenue or operations proves otherwise.
What you’ll learn from shipping each
- Polling teaches you your real query cost and acceptable staleness.
- SSE teaches you streaming semantics and timeouts.
- Action Cable teaches you connection lifecycle under load.
All three are legitimate Rails patterns—the mistake is jumping to Cable first because it sounds modern.
What’s your default for internal dashboards today—timer-based refresh, streaming, or sockets?