모든 글

서버가 2대가 되는 순간 깨지는 코드들 — 무상태(Stateless) 서버

서버 1대일 땐 멀쩡하던 로그인이 2대로 늘리자 3번에 1번꼴로 풀렸다. 원인은 서버가 자기 메모리에 상태를 들고 있던 것. 무상태 서버와 수평 확장을, 퍼블리셔의 인라인 스타일 비유로 정리한다.

프론트만 하다 풀스택으로 넘어오면 낯선 버그를 만나요.

서버가 1대일 땐 잘 돌던 코드가, 2대로 늘리는 순간 이상하게 동작하는 거예요.

원인은 대부분 하나예요.

서버가 자기 메모리나 로컬 디스크에 사용자별 상태를 들고 있어서.

서버를 여러 대 두면 앞에 로드밸런서(요청을 나눠주는 교통정리 장치)가 붙어요.

이게 매 요청을 아무 서버에나 보내요.

그러니 1번 서버 메모리에 저장한 세션을 2번 서버가 알 리가 없죠.

퍼블리싱으로 치면, 공통 스타일을 CSS 파일에 두지 않고 특정 페이지에만 인라인 style로 박아둔 거랑 비슷해요.

그 페이지에선 멀쩡하지만 다른 페이지로 넘어가면 스타일이 사라지잖아요.

서버 2대가 되면 이런 게 깨져요.

// 1. 세션을 서버 메모리에
const sessions = new Map();   // 이 Map은 "이 프로세스" 안에만 존재
app.post('/login', (req, res) => {
  const sid = crypto.randomUUID();
  sessions.set(sid, { userId: 1 });   // 1번 서버에만 저장됨
  res.cookie('sid', sid);
});
// → 다음 요청이 2번 서버로 가면 sessions.get(sid) === undefined → 로그아웃

// 2. 업로드 파일을 로컬 디스크에
fs.writeFileSync(`./uploads/${file.name}`, buf);
// → 1번 서버 디스크에만 존재. 2번 서버로 간 조회 요청은 404.
//   게다가 컨테이너가 재시작되면 그냥 증발한다.

// 3. 캐시를 전역 변수에
let cachedRates = null;
// → 서버마다 값이 달라서, 새로고침할 때마다 환율이 왔다 갔다 한다.

로그인 후 새로고침하면 3번에 1번꼴로 로그아웃.

이게 전형적인 증상이에요.

쿠키는 잘 실려 오지만 그 sid를 아는 건 로그인을 처리했던 그 1대뿐이라, 다른 서버로 갈 때만 실패하는 거죠.

”가끔씩만 실패한다”는 증상 자체가 무상태 문제의 신호예요.

해법은 상태를 서버 안이 아니라 공용 저장소(Redis·DB·S3)에 위임하는 거예요.

await redis.set(`sess:${sid}`, JSON.stringify({ userId: 1 }), 'EX', 3600);
await s3.putObject({ Bucket: 'my-uploads', Key: file.name, Body: buf });
await redis.set('rates', JSON.stringify(rates), 'EX', 60);

이렇게 서버가 무상태 — 상태를 자기 안에 두지 않는 상태 — 가 되면, 어느 서버로 요청이 가도 같은 결과가 나와요.

그래야 클라우드의 오토스케일링(부하에 따라 서버 대수를 자동으로 늘리고 줄이는 것)이 제대로 동작하고요.

조회수 카운터도 같은 함정이에요.

const viewCounts = new Map()에 세면 서버 3대면 카운트도 3벌로 쪼개지고, 5분마다 setInterval로 DB에 반영하는 방식은 오토스케일링이 인스턴스를 예고 없이 종료할 때 아직 반영 안 된 메모리가 통째로 증발해요.

redis.incr('views:{id}') 같은 공용 카운터로 옮기거나, 최소한 종료 시그널(SIGTERM)에서 flush 해야 하고요.

클라우드에서 “서버 늘려서 트래픽 견디자”가 가능하려면 애플리케이션이 먼저 무상태여야 해요.

인프라 설정만으로는 안 돼요.

프론트도 마찬가지고요 — Next.js를 서버리스(Vercel·Lambda)에 올리면 요청마다 다른 인스턴스가 뜨니까, 라우트 핸들러의 모듈 전역 변수는 절대 신뢰하면 안 돼요.

서버 코드에서 new Map()·let cache·로컬 파일 저장이 보이면 한 번 물어보세요.

서버가 2대가 돼도 이게 맞나.

아니라면 그 상태는 서버 밖으로 빼야 해요.

N
NACRE
Writing on less · Seoul
다른 글 보기

댓글

  1. 불러오는 중…