본문으로 건너뛰기
김신건의 로그

Stateful

· 수정 · 📖 약 2분 · 873자/단어 #architecture #network #scaling
stateful, 상태유지, Stateful 서버, stateful server

정의

Stateful (상태유지) 서버는 클라이언트와의 세션·연결 컨텍스트를 메모리에 유지한다. 후속 요청·이벤트는 이 컨텍스트를 활용해 처리된다.

같은 클라이언트의 요청은 같은 서버로 가야 의미가 있다. 그래서 수평 확장이 복잡해진다.

전체 비교는 Stateless vs Stateful 페이지를 참조.

핵심 특성

측면동작
세션 상태서버 메모리에 보관
수평 확장Sticky Session 또는 외부 store 필수
장애 복원서버 다운 시 세션 손실 (외부 store 시 완화)
인증 정보세션 ID 발급, 서버가 검증
캐싱사용자별 응답 → 캐시 효율 낮음

어디에 상태가 사는가

1. 인메모리 (가장 흔함)

const sessions = new Map(); // sessionId → user_data

빠르지만 휘발성. 서버 재시작 = 세션 손실.

2. 활성 연결 자체

WebSocket, SSE 의 경우 TCP 연결 자체가 상태 컨텍스트다.

const wss = new WebSocketServer({ ... });
wss.on('connection', (ws) => {
  ws.userId = decodeAuth(ws.upgradeReq);
  ws.rooms = new Set();
  // 이 ws 객체가 사라질 때까지 컨텍스트 유지
});

연결 끊김 = 컨텍스트 손실 → 재연결 + 재인증 필요.

3. 외부 store (사실상 Stateless 화)

세션 데이터를 Redis 같은 외부 store 에 두면 형식상 Stateless 서버가 된다.

// Express + connect-redis
app.use(session({
  store: new RedisStore({ client: redis }),
  secret: 'xxx',
}));

Stateful 서버의 수평 확장

여러 서버로 트래픽을 분산할 때 마주치는 문제.

문제, 같은 클라이언트가 다른 서버로 가면?

Client X 가 Server A 에 로그인 → A의 메모리에 세션 저장
Client X 의 다음 요청 → LB가 Server B 로 라우팅 → B는 세션 모름 → 인증 실패

해결책 1: Sticky Session

LB 가 같은 클라이언트를 같은 서버로. 가장 흔하고 가장 단순.

단점: 서버 다운 시 세션 손실, 부하 불균형 가능.

해결책 2: 외부 세션 store

세션 데이터를 Redis 등에 두면 어느 서버나 조회 가능. 사실상 Stateless 화.

Client X → 아무 서버 → Redis 에서 세션 조회 → 처리

매 요청마다 store I/O 추가.

해결책 3: Pub/Sub bus (지속 연결 전용)

서버 간 메시지 라우팅용. WebSocket 같이 연결 자체가 본질적인 경우.

Server A 가 Client X 보유 / Server B 가 Client Y 보유
A 에 'Y 에게 메시지' 도착 → Redis publish → B subscribe → Y 에게 전달

Socket.IO + Redis Adapter 패턴이 전형.

실무 예시

WebSocket 채팅 서버

import { WebSocketServer } from 'ws';
const rooms = new Map(); // room → Set<ws>

const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const { type, room, payload } = JSON.parse(data);
    if (type === 'join') {
      if (!rooms.has(room)) rooms.set(room, new Set());
      rooms.get(room).add(ws);
    } else if (type === 'message') {
      rooms.get(room)?.forEach((peer) => {
        if (peer !== ws) peer.send(payload);
      });
    }
  });
});

rooms Map 이 메모리 상태. 서버 재시작 = 모든 룸 정보 손실.

실시간 게임 서버

플레이어 위치·인벤토리·전투 상태를 서버 메모리에 유지. tick 단위로 업데이트.

수평 확장은 보통 샤딩, “월드 1 은 Server A, 월드 2 는 Server B” 식으로 분할.

협업 도구 (Figma, Notion)

문서 단위로 stateful 서버 1대가 모든 활성 사용자의 변경사항(OT/CRDT)을 메모리에 유지.

함정과 한계

1. 메모리 폭발

활성 연결마다 메모리. WebSocket + Socket.IO 5K 연결 = ~400-500 MB.

2. 서버 다운 = 사용자 인지 가능한 손실

다운 직후 활성 세션 / 연결이 모두 끊김. 재연결 + 재인증 + 잃어버린 메시지 재요청.

3. 운영 복잡도

  • Sticky Session 설정
  • 배포·롤링 업데이트 시 세션 마이그레이션 또는 graceful drain
  • 모니터링 (활성 연결 수, 메모리, GC 등)

4. 비용

같은 동접 처리 시 Stateless 보다 더 많은 서버 인스턴스 필요 (메모리 빠르게 소진).

통신 프로토콜과의 관계

프로토콜Stateful 여부
HTTP 의미상Stateless
Server-Sent Events✓ Stateful (지속 연결)
WebSocket✓ Stateful (지속 양방향)
WebRTC✓ Stateful (P2P 세션)
Long Polling의미상 Stateless, 운영상 비슷
WebTransport✓ Stateful

지속 연결 기반 프로토콜은 본질적으로 Stateful.

참고

이 글의 용어 (4개)
Pub/Sub Busconcurrency
정의 Pub/Sub Bus (Publish-Subscribe bus)는 메시지 송신자(publisher)와 수신자(subscriber)가 서로를 모르고도 통신할 수 있게 해주는 …
Statelessconcurrency
정의 Stateless (무상태) 서버는 클라이언트의 요청 간 상태를 기억하지 않는다. 각 요청은 완전히 독립적이며, 처리에 필요한 모든 정보는 요청 자체에 담겨 있어야 한다. …
Stateless vs Statefulconcurrency
정의 Stateless (무상태): 서버가 클라이언트 요청 간 상태를 기억하지 않음. 각 요청은 완전히 독립적이며 필요한 모든 정보는 요청에 담겨 있다. Stateful (상태유…
Sticky Sessionconcurrency
정의 Sticky Session (= Session Affinity)은 로드밸런서가 같은 클라이언트의 요청을 항상 같은 백엔드 서버로 라우팅하도록 하는 동작이다. 서버, 즉 세션…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기