서론
최근에 기술면접을 보다가 포트폴리오에 기재해둔 Auto Scaling 구성 관련 내용에서 '무상태성'에 대해 설명해달라는 질문을 받게되었다. 어떠한 개념이 흐릿하게 떠올랐으나, 표현의 한계로 인해 뜸 들이다가 "잘 모르겠다. 더 공부해보겠습니다."라는 답변을 드렸다.
무지함을 반성하는 의미에서 이번 포스팅에서는 '무상태성'이라는 개념에 대해 정리해보고자 한다.
본론
무상태성이란?
무상태성이란 서버가 클라이언트의 이전 요청 상태(세션, 로그인 정보 등)를 서버 내부 메모리 등에
저장하지 않는 설계 방식을 의미한다.
즉, "각각의 요청은 독립적이며, 서버는 요청을 처리하는 데 필요한 모든 정보를 요청 메시지 그 자체나 외부 저장소(Redis, DB)에서 가져와야 한다"는 원칙이다.
- Stateful (상태 유지): 서버가 클라이언트의 상태(세션 등)를 메모리에 들고 있음. 특정 서버에 종속됨.
- Stateless (무상태): 서버는 '계산'만 담당하고, '데이터(상태)'는 외부 공유 저장소에 둠. 어떤 서버가 요청을 받아도 결과가 동일함.
예시는 아래와 같다.
Stateful (상태 유지)
서버가 사용자 상태를 기억함
1. 로그인 요청 → 서버가 세션 저장
2. 다음 요청 → 서버가 "얘 로그인 했던 애" 기억함
예를 들어 다중 서버 인스턴스 환경에서의 문제는 이렇게 발생된다. 서버가 "A 사용자는 방금 로그인했음"이라는 정보를 자기 메모리에 들고 있다. 그런데 만약 A 사용자의 다음 요청이 다른 서버 인스턴스로 가면, 그 서버는 "누구?"라고 묻게 된다. 이는 시스템적으로 확장성이 떨어지게 된다.
서버 내부 메모리에 저장한다는 건 무슨 의미인가?
서버 내부 메모리에 저장한다 = 서버 프로세스의 RAM에 상태를 들고 있는 것,
애플리케이션(JVM) 내부 객체로 상태를 유지한다
서버 내부 메모리에 저장한다는 것은 서버가 돌아갈 때, JVM(Spring Boot 기준) 등이 메모리(RAM) 위에서 실행되고, JVM 힙 메모리에 저장되는 것을 말한다.
프로그래밍 관점에서 보면, 서버 코드 안에 static 변수나 HttpSession, Map, List 같은 자료구조를 만들고 거기에 데이터를 집어넣는 것을 의미한다. (JVM 힙 메모리(RAM)에 저장됨)
아래 코드는 실제로 JVM 힙 메모리 안에 Map<sessionId, userInfo> 라는 구조로 저장된다.
session.setAttribute("userId", 123);
// userId 정보가 DB가 아닌 JVM 메모리 안에 저장됨
- 동작 방식: 사용자가 로그인하면 서버는 Map<SessionID, UserInfo> 같은 변수에 로그인 정보를 기록
- 속도: RAM은 전기적 신호로 작동하므로 DB(디스크)보다 수백 배 이상 압도적으로 빠름
- 휘발성: 서버 프로세스를 재시작하거나 서버 컴퓨터를 끄면 메모리에 있던 데이터는 모두 증발
- 서버 재시작 = 데이터 날아감 (JVM 꺼지면 메모리 초기화됨)
- 서버가 여러 대면 공유가 안됨 (JVM은 프로세스마다 따로라서 공유 안됨)
서버 A (JVM) → 세션 있음
서버 B (JVM) → 세션 없음
그래서 나온 해결책
1. Redis 같은 외부 저장소
세션을 JVM 말고 Redis에 저장한다.
2. Stateless(JWT)
아예 서버에 저장을 안하고, 요청 시마다 토큰 정보를 받아오는 식으로 처리한다.
Spring Boot 환경에서는 서버 내부 메모리에 저장한다는 것은 JVM 힙 영역에 세션 객체 형태로 사용자 상태를 저장하는 것을 의미한다. 이 방식은 서버가 상태를 직접 유지하기 때문에 stateful 구조가 되며, 서버 재시작 시 데이터가 사라지고, 여러 서버 간 공유가 어렵다는 단점이 있다.
Stateless(무상태)
서버는 아무것도 기억하지 않는다. 클라이언트가 요청할 때마다 토큰 정보가 들어온다. 서버는 그저 들어온 요청만 처리하고 잊어버린다.
1. 로그인 요청 → 토큰 발급
2. 이후 요청 → 매번 토큰 포함
따라서 무상태이면 서버1 -> 서버2 -> 서버 3을 왔다갔다 해도 세션 정보를 기억하지 않기에 처리가 원활하다. (로드밸런싱이 쉬워진다)
뿐만 아니라, 서버1에 장애가 생겨서 서버가 죽었을 경우, 서버2로 바로 요청하면 되기에 장애 대응도 수월하다.
이러한 '무상태성'은 보통 아래와 같은 환경/맥락에서 많이 등장한다.
1. 웹/서버 아키텍처
대표적으로 HTTP 기반 서버를 예시로 들 수 있겠다. 기본적으로 HTTP는 무상태 프로토콜을 특징으로 가지고 있다.
요청1 : 로그인
요청2 : 사용자 정보 조회
만약 요청1 직후에 요청2를 호출하게 되면 무상태의 경우, 요청2가 "이미 로그인을 한" 상태라는 것을 기억하지 못할 것이다.
그래서 이미 로그인을 완료했음에도 불구하고, 매 요청마다 JWT 토큰이나 세션ID를 같은 것들을 같이 보내야 한다. (각 요청이 서로 독립적이어야 한다.)
2. REST API 설계
REST의 핵심 원칙 중 하나로 Stateless(무상태성)이 있다.
- 각 요청은 독립적이어야 함
- 이전 요청에 의존하면 안 됨
3. 분산 시스템/클라우드
- 서버가 여러 대 (로드밸런싱)일 경우
- 요청이 아무 서버로 가도 문제 없어야 함
정리하자면
무상태성은 웹/분산 환경에서 사용하는 설계방식이다.
특히 위에서 설명한 바와 같이 HTTP 서비스, REST API, MSA 구조에서는 거의 필수적인 개념이다.
Auto Scaling 환경에서의 무상태성 (수평적 확장성)
Q. 서버 인스턴스가 1개에서 5개로 늘어날 때, 무상태성을 고려하여 처리했나요? 무상태성 개념에 대한 설명도 부탁드려요.
이 부분이 바로 면접에서 질문받은 부분이다. 이에 대해 질문한 의도는 다음과 같을 것으로 짐작된다.
서버가 늘어나거나 교체될 때 데이터가 유실되거나 꼬이지 않게 설계했는가?
즉, 서버가 N개로 늘어나도 데이터 정합성에 문제가 없는가?

Auto Scaling은 트래픽에 따라 서버를 유동적으로 늘리고 줄인다. 만약 엄청난 트래픽이 들어와서 서버 인스턴스가 1개에서 5개로 늘어났을 때, 로드밸런서(ALB)는 이러한 요청들을 여러 서버로 분산할 것이다. 이때 유저 정보나 상태가 특정 서버 메모리에만 있다면(Stateful 상태이면), 유저가 다른 서버로 연결될 때 로그아웃되거나 데이터가 꼬일 가능성이 커진다.
서버 인스턴스가 1개에서 5개로 늘어난다는 것은, 클라이언트의 요청이 어떤 인스턴스로 전달될지 예측할 수 없음을 의미한다.
즉, 아래와 같은 문제가 발생할 것이다.
- 세션 불일치: 1번 서버에 로그인한 유저의 다음 요청이 3번 서버로 가면 로그아웃(세션 정보가 없어서) 처리됨
- 데이터 파편화: 서버 내부 메모리에 저장한 대기열 정보가 서버 삭제 시 함께 증발함
- 결과: 무상태 설계가 되어있지 않으면 Auto Scaling은 시스템 장애의 원인이 됨
우리 프로젝트에서의 무상태성
따라서, Auto Scaling 환경에서의 '무상태성'은 시스템의 확장성을 보장하는 중요한 요소이다. 서버 인스턴스가 1개에서 5개로 동적으로 늘어날 때, 어떤 인스턴스라도 즉시 클라이언트의 요청을 처리할 수 있어야 진정한 부하 분산이 가능하기 때문이다.
우리 RUSH DEAL 프로젝트에서는 ECS Fargate와 Auto Scaling을 적용하며 다음과 같이 무상태성을 확보하였다.
인증의 무상태화 (JWT & API Gateway)
유저 정보를 JWT 토큰에 담아 클라이언트가 관리하게 했다. API Gateway에서 모든 요청의 토큰을 검증하므로, 뒤에 있는 5개의 MSA 인스턴스 중 어느 서버가 요청을 받아도 별도의 로그인 절차 없이 동일한 권한으로 즉시 로직을 수행할 수 있게 하였다.
서버 세션 방식이라면 서버가 늘어날 때 세션 클러스터링을 하거나 Sticky Session을 써야 함
상태의 외부화(분산 저장소: Redis)
공유가 필요한 데이터(대기열 순번, 분산 락 등)는 공통의 AWS ElastiCache(Redis)에 두었다. 결과적으로 각 서버 인스턴스는 데이터를 가지지 않고 '로직'만 처리하는 Stateless 상태가 되었으며, 덕분에 Auto Scaling으로 인스턴스가 급격히 늘어나도 데이터 정합성 문제 없이 부하를 안정적으로 분산할 수 있게 하였다.
통신의 무상태성(Kafka)
서버 간 통신 시 직접적인 의존성(상태)을 가지지 않고 Kafka를 통한 비동기 메시징을 활용하였다. 특정 서버가 바빠서 처리를 못 해도 다른 인스턴스가 메시지를 가져와 처리할 수 있으므로 시스템 전체의 가용성을 극대화할 수 있게 하였다.
이러한 무상태 설계 덕분에, k6 테스트 시 CPU 점유율이 98%에 도달했을 때 새롭게 투입된 4개의 인스턴스가 즉시 트래픽을 나눠 가질 수 있었고, 결과적으로 전체 시스템의 CPU 평균을 4%대까지 안정화할 수 있었다.
서버리스(ECS Fargate) 환경에서의 무상태성 (휘발성)
ECS Fargate는 서버 관리를 AWS가 전담하는 서버리스 컨테이너 환경이다. (물리 서버를 관리하지 않는 추상화된 컨테이너 서비스) Fargate 컨테이너는 언제든 생성되고 소멸되늰데, 로컬 파일 시스템이나 메모리는 컨테이너가 꺼지는 순간 즉시 삭제되게 된다.

위 아키텍처 이미지를 보면 Redis와 RDS가 중앙에 위치해있는데, 모든 컨테이너가 이 중앙 저장소를 바라보고 있는 형태로 되어 있다. 그렇기 때문에 어떤 서버 인스턴스가 죽고 새로 생겨도(Scale-out) 데이터 정합성이 유지된다.
Fargate에서는 무상태성을 지키지 않으면 오토스케일링 자체가 작동할 수 없다.
따라서 ECS Fargate 환경에서 서버는 언제든 늘어나거나 사라질 수 있으니, '중요한 데이터는 서버 외부에 두어라'라는 관점에서 Auto Scaling 에서의 '무상태성' 개념과 같다고 볼 수 있다.
RUSH DEAL 프로젝트에서는 ECS Fargate를 사용했기 때문에, 인스턴스의 잦은 교체와 확장에 대비해 무상태성 설계가 필수적이었다.
Auto Scaling(EC2) vs ECS Fargate의 무상태성
두 환경 모두 '서버가 상태를 가지지 않아야 확장과 축소가 자유롭다'는 본질은 같다. 하지만 운영/관점 측면에서는 아래와 같은 차이가 있따.
- Auto Scaling 관점: "서버가 1대에서 5대로 늘어날 때, 유저의 요청이 1번 서버가 아닌 새로 생긴 5번 서버로 가도 로그인이 유지되는가?" (확장성)
- 서버리스(Fargate) 관점: "Fargate는 인프라를 AWS가 관리하므로 언제든 컨테이너가 죽고 새로 뜰 수 있는데, 그때 서버 안에 저장된 데이터가 날아가도 서비스에 지장이 없는가?" (휘발성/교체 가능성)
결국 무상태성의 핵심은 "서버는 언제든 죽거나 새로 생길 수 있어야 한다"는 것으로 정리될 수 있다.