Lessons from 100K Connections: SSE Uses 40% Less Memory Than WebSocket

·Platform Decision·5 min read

Translated from the original Korean post. 한국어 원문 보기 →

The choice that looked obvious

Picking WebSocket for a real-time dashboard is basically a reflex. Live prices, inventory counts, notifications on screen? WebSocket. That's what the tutorials say, and the senior folks on the forums all say the same thing.

We never questioned it either. 10k connections were fine. 30k connections, the graphs still looked calm. Then the 100k load test blew up. Memory shot straight up and the system just stopped.

Four months and about $3,200 in wasted compute later, it finally clicked. The problem wasn't our tuning. It was a question we never asked at the start.

WebSocket vs SSE: the structure is what costs you

The big difference is whether you stay on HTTP or leave it. That single fork decides your operational bill.

WebSocket (전이중)
클라이언트 <──────────────> 서버
         송수신
         [ 상태 유지, 사용자 지정 프로토콜 ]

SSE (반이중)
클라이언트 <────────────── 서버
         수신 전용
         [ 일반 HTTP, 브라우저 자동 재연결 ]

WebSocket does an HTTP upgrade handshake and then switches to its own protocol. From that point on the server holds every socket's state itself, and it has to keep ping/pong heartbeats running to know whether a connection is still alive. Each connection becomes a live object the server has to babysit.

SSE works differently. You just leave the HTTP response open and push text events down it. No custom frames, no negotiation. If the connection drops, the browser reconnects on its own. It rides on the HTTP stack you already have, so there's very little new state to carry.

What the numbers actually said

Same environment, 100k connections. EC2 c5.2xlarge, one update per second per client.

메트릭 WebSocket SSE 차이
연결당 메모리 ~3.5KB ~1.2KB -65%
10만 연결 시 총 RAM ~342MB ~205MB -40%
CPU 유휴 부하 18% 11% -39%
재연결 로직 수동 구현 브라우저 내장 개발 편의성 ↑

That 40% memory saving didn't come from clever tuning. It came from structure. WebSocket carries a buffer, a state machine, and protocol metadata per connection. SSE reuses the HTTP path the server already runs. The connection isn't what's expensive — it's everything bolted onto it.

The scary part was something else. With 100k WebSocket connections open, one traffic spike didn't slow the system down. It killed it. Estimated recovery: 22 minutes. When real-time data is the product, a 22-minute hole is an incident.

Separate cause from effect and it's simple. Memory pressure was the cause, the system halting was the effect. And the root of that pressure was WebSocket's design: state per connection, always.

The code makes it obvious

Compare what we actually ran in production and the complexity gap shows up fast. The SSE server side is clearly simpler.

const http = require('http');

http.createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  
  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({price: getPrice()})}\n\n`);
  }, 1000);
  
  req.on('close', () => clearInterval(interval));
}).listen(8080);

The client reconnects by itself.

const src = new EventSource('/events');
src.onmessage = (e) => {
  const { price } = JSON.parse(e.data);
  updateUI(price);
};

No external library. No reconnect logic to write. The browser spec handles it. During the migration we deleted about 200 lines of retry handling, and nobody on the team missed it. The reaction was closer to "this is what we've been maintaining?" From an ops PM's seat, less code also means fewer places to fail.

The decision rule is one question

What I took away is simple. If the client only receives data and has nothing to send back, use SSE. Live feeds, dashboards, notifications, deploy progress, log streaming — all of it. If the client has to push data up to the server, use WebSocket. Chat, collaborative editing, multiplayer games, where two-way traffic is the whole point.

Our mistake wasn't choosing WebSocket. It was never asking whether we needed two-way communication in the first place. A dashboard's data flows one direction. We'd laid down a bidirectional channel and were paying for the half we never used.

Looking back, the expensive thing in a tech decision isn't the wrong tool. It's the question you skipped.

The detail people miss

Bring up SSE and someone will reflexively cite the six-connections-per-domain limit on HTTP/1.1. Fair point, but it's missing a premise. On HTTP/2 that limit is gone, because connections get multiplexed at the transport layer.

As of 2026, most production environments already run on HTTP/2. So this rarely bites in practice. Just check that your server config has HTTP/2 turned on. The reason the old objection still circulates as received wisdom is that the tech moved and the folklore didn't.

Wrapping up

Four months and $3,200. Expensive lesson, but worth what we paid for it.

What we actually gained wasn't SSE. It was a habit: stop in front of the "obvious" choice and ask whether that structure fits our problem first. Tools keep changing. What doesn't is picking based on structure instead of surface popularity.

Was this post helpful?

One click helps me write the next one

#WebSocket#SSE#Real-time Communication#Performance Optimization#Backend