[ EXECUTIVE TEARDOWN // TL;DR ]
- A real-time product is defined by the connection that stays open, and the economics of serverless assume nothing stays open.
- In-process state turns per-message Redis round trips into memory reads, and fan-out into a loop rather than thousands of billable invocations.
- The honest cost: draining deploys, scaling decisions and memory discipline become mine — roughly a week of work paid once.
- The method transfers: find the constraint that governs the product, then check the default answer against it before accepting it.
Everyone said “just put it on serverless.” For the product I was building — a live dashboard holding thousands of open connections, pushing telemetry as it happened — I ran a long-lived Node process instead. This post is about that decision, because the decision is the product.
Know what your product actually is
Serverless is close to unbeatable for request-response work: a function wakes, does one job, returns, and you pay for milliseconds. If that is your workload, the argument is over and you should stop reading.
A real-time product is not that workload. Its defining feature is the connection that stays open — and the whole economic model of serverless is that nothing stays open. Every constraint that follows comes from that one sentence, and none of it is visible in a feature list.
What a long-lived process buys you
- State that survives between messages: a subscription registry, an in-process cache, a rate-limit window. On serverless each of those becomes a Redis round trip, on every message, forever.
- Fan-out that is a loop, not a bill: pushing one update to 5,000 subscribers is an iteration over a Map. The same fan-out through a serverless gateway is 5,000 billable invocations.
- Backpressure you can actually apply: the socket tells you when the client is not keeping up. A stateless function has no idea a consumer is drowning, because it never sees the consumer twice.
- Warm everything: connection pools, compiled serialisers, prepared statements — all established once at boot rather than re-established on a cold start in front of a waiting user.
- Ordering within a connection: messages from one client arrive at one process in order. Getting that guarantee across independent invocations means a queue and sequence numbers you now maintain.
“Just go serverless” is usually correct — that is what makes it dangerous. The senior skill is recognising the one project in ten where the default answer quietly removes the thing the product is made of.
The bill nobody models
The comparison people run is per-invocation price against per-hour price, and serverless usually wins it. The comparison that decides the question is different: a real-time product bills on connection-minutes and messages, not requests. Ten thousand idle connections cost a long-lived process almost nothing — some file descriptors and a little memory. On a managed WebSocket gateway they are a line item that grows while your users do nothing at all.
The honest cost
I now own things serverless would have owned for me. Deployment without dropping connections means draining: stop accepting, let clients reconnect to the new instance, then exit. Scaling is a decision I make rather than one that happens. A crashed process takes its connections with it, so health checks and restarts are mine to get right. And memory discipline matters in a way it never does in a function that exits after 200ms — every leak I write, I keep.
That is roughly a week of infrastructure work I would not otherwise have done, paid once by me, against latency and cost paid by every user on every session.
Where I would still choose serverless
Without hesitation: the REST API around the real-time core, scheduled jobs, webhook receivers, image processing, anything spiky and stateless. The interesting architecture is not one or the other — it is a long-lived process for the connections and functions for everything else, with the boundary drawn at exactly the point where state stops mattering.
The method, which is the transferable part
Find the constraint that governs the product — here, connections stay open — and check the default answer against it before accepting the default. Most of the time the default survives and you move on in five minutes. Occasionally it does not, and those are the decisions worth writing down.
~/keep-reading
- 6 min readVision Over Syntax: I Design the Entire Product in My Head Before the First CommitSyntax is solved; vision is not. How architecture-first thinking — data contracts, trust boundaries, and deliberate refusals — is the real engineering skill in the AI era, shown through shipped products.
- 6 min readThe 94% Decision: One Architecture Call That Made IntegrateX Feel InstantPersisting React Flow's UI objects nearly sank the product. Drawing a serialization boundary between view and domain cut payloads 94% — the reasoning behind the call, and how to audit your own product for it.
- 8 min readWorker threads in Node, and the pool you actually needCPU-bound work blocks every request, not just its own. When a worker thread is the right tool over a child process or a queue, why a pool beats spawning per task, and what the copy costs.
YK
Yaseen Khatib · MERN + AI Architect
Ships autonomous AI products solo — five in the last twelve months. More about Yaseen →
Need an engineer who can build this?
I'm Yaseen Khatib — a Senior Full-Stack AI Engineer (MERN + TypeScript) who ships production AI systems solo. Open to senior and lead roles, remote or on-site.
Get in touch →See what I've shipped
Originally published at yaseenkhatib.streamerosai.com/blog/node-over-serverless-for-realtime/.
Top comments (0)