카테고리 없음

SWAP은 왜 필요한가?

코딩연습중 2025. 11. 26. 17:30

이전에 OOM 문제를 해결하느라 고생했었는데, 이 때 의문이 드는 부분이 몇 가지 있었다.

첫번째, '나는 t3.micro를 쓰고 있었기 때문에 RAM이 1gb밖에 안 되는데 어떻게 자바에 2.8기가 정도가 할당되었지?'

라는 의문이었다.

 

이걸 이해하려면 가상 메모리(Virtual Memory)랑 물리 메모리(Physical Memory) 개념을 확실히 구분해야 했다.

알고 보니까, 리눅스 커널 로그에 찍힌 메모리 숫자는 실제 RAM 사용량(RSS)이 아니라, 프로세스가 OS한테 “미리 요청해 둔” 가상 메모리 주소 공간(VSZ/Total VM)이었다.

자바 프로세스는 실행되면 힙, 스택 이런 걸 포함해서 “앞으로 이만큼 쓸 거예요~” 하고 OS한테 넉넉하게 메모리를 예약해 둔다. 그리고 리눅스는 실제 RAM이 부족해도 일단은 가상 메모리를 넉넉하게 주는 오버커밋(Overcommit) 정책을 쓰고 있다.

즉, 로그에 보인 2.8GB는 진짜 사용량이 아니라 그냥 자바의 희망 사항(예약된 공간)이었다.
실제로는 물리 메모리 1GB를 거의 다 쓰는 순간, OS가 “안 되겠다” 하고 바로 OOM Killer로 프로세스를 날려버린 거다.

 

그럼 여기에서 궁금증이 생긴다.
“그럼 물리 메모리가 1GB라면 그 이상은 절대 못 쓰는 건가? 그럼 서버는 어떻게 살리지?”

이럴 때 필요한 게 바로 스왑(Swap)이다.
RAM이 부족해지면, SSD(디스크)를 임시 메모리처럼 빌려 쓰는 방식이다.
말 그대로 부족한 메모리를 살짝 ‘확장’하는 역할을 한다.

 

RAM이 1기가였을 때, 2기가짜리의 요청이 온다고 가정하자. 서버는 이 요청을 감당하지 못하고 다운될 것이다. 

만약 RAM을 2기가로 설정한다면 위와 같은 문제는 생기지 않을 것이다. 하지만 제한된 상황(ram은 비싸니까,,)에서 서버가 다운되지 않도록 하려면, SWAP 공간을 할당해서 1기가를 넘는 요청을 swap에 일단 할당한 뒤, ram에 공간이 생겼을 때 swap에서 빼오도록 하면 된다. 

 

SWAP은 그럼 자세하게 무엇일까?

SWAP 메모리

스왑 메모리는 주 메모리의 확장으로 볼 수 있으며, 주로 HDD 또는 SSD에 위치한다. RAM이 가득 찼을 때, 운영체제는 스왑 공간에 저장된 데이터를 RAM과 교환(swap)한다. 이 과정을 통해 시스템은 RAM에 더 많은 공간을 확보할 수 있으며, 더 많은 프로그램을 실행할 수 있게 된다.

 

보조 메모리에 위치하지만, 주 메모리와 유사한 역할을 수행하기 위해 사용된다. 그러나 스왑은 RAM보다 훨씬 느리기 때문에, 스왑을 과도하게 사용하면 시스템 성능이 저하될 수 있다.

 

이렇게 하여 전에 t3.micro에서 swap을 2기가로 설정하였다.

 


 

그럼 완전히 해결된 것일까?

만약 자바 프로세스의 maximum thread 수를 500으로 설정하면 어떻게 될까?

자바가 실행될 때, minimum thread 수에 따라 처음에 thread를 생성하여 thread pool에서 관리하게 된다. 이 때 각각의 thread는 1MB를 갖게 되고, 가상 메모리 공간에서 설정했던 최댓값에 따라 약 1MB씩의 공간(Stack)을 예약하게 된다. 단순 계산으로 스레드 500개면 500MB다. 1GB 램을 가진 서버에서 절반을 스레드 생성에만 예약해버리는 셈이다.

 

여기서 스택(Stack)과 힙(Heap)의 차이를 알아야 한다.

1. 스택(Stack) vs 힙(Heap): 메모리 땅따먹기

사실 서버가 터지는 이유는 단순하다.
1GB밖에 안 되는 메모리를 두고, 스택과 힙이 서로 더 많이 차지하려고 싸우기 때문이다.

  • 스택 (Stack) = "스레드들의 개인 수첩"
    • 누가 쓰나? 스레드(작업자) 하나당 개인 수첩 하나씩 지급됨.
    • 뭐가 들어가나? 메서드 실행 순서, 지역 변수 등 당장 일하는 데 필요한 정보.
    • 특징: 요청이 끝나면 바로 지워진다.
      하지만 스레드를 너무 많이 만들면(예: max-threads 500),
      스레드 500명이 수첩 500개 들고 와서 메모리를 다 잡아먹어버림.
      그러면 정작 데이터를 저장해야 할 힙 공간이 부족해진다.
  • 힙 (Heap) = "모두가 공유하는 거대한 창고"
    • 누가 쓰나? 모든 스레드가 공유한다.
    • 뭐가 들어가나? new Member(), new Order() 처럼 우리가 코드로 만든 객체(데이터)들.
    • 특징: -Xmx 값으로 개발자가 직접 크기를 정할 수 있다.
      이 창고가 꽉 차면 바로 그 유명한 Heap Space OOM이 난다.

2. t3.micro(1GB)를 위한 최적의 비율 찾기

만약 내가 욕심을 부려서 "동시 접속자 500명 받아야지!" 하고 스레드를 500개로 설정했다고 치자. 그리고 데이터도 많이 저장하려고 힙 메모리도 800MB로 잡았다고 가정해보자.

  • 계산: 힙(800MB) + 스레드 500개(약 500MB 예약) + OS 기본 사용량(약 200MB) = 1.5GB 필요
  • 결과: 물리 램 1GB를 훨씬 초과한다. 스왑(Swap)이 있어도 버거울 수 있고, 무엇보다 CPU가 스레드 500개를 왔다 갔다 하느라(Context Switching) 실제 일은 못 하고 서버가 멈춰버린다.

그래서 나는 선택과 집중을 해야 했다. t3.micro 환경에서 서버가 죽지 않고 가장 효율적으로 도는 비율은 다음과 같았다.

  1. 힙 메모리 (Heap): 512MB
    • 전체 1GB의 딱 절반인 50%를 데이터 저장용으로 할당했다.
    • 도커파일이나 실행 명령어에 -Xmx512m 옵션으로 명시했다.
    • 이 정도면 작은 서비스 기준으로 충분히 안정적이었다
  2. 스레드 (Thread): Max 200개
    • t3.micro는 CPU 코어가 2개뿐이다. 스레드가 너무 많아봤자 효율이 떨어진다.
    • 200개면 메모리(Stack)도 적당히 차지하면서, 동시 요청도 충분히 처리할 수 있다.
    • 만약 200명이 넘게 오면? accept-count 설정으로 대기열에 줄을 세우는 게 서버가 터지는 것보다 낫다.
  3. 나머지 공간 & 스왑 (Swap):
    • 남은 물리 램(약 300~400MB)은 OS와 스레드들이 나눠 쓴다.
    • 혹시라도 이 공간이 부족해지면, 미리 설정해둔 2GB 스왑 메모리가 '에어백' 역할을 하여 서버 다운을 막아준다.

 

이렇게 OS에 대한 이해가 중요하다는 걸 다시 한번 깨닫게 되고,, 다시 깊이 공부를 해야겠다는 다짐을 했다,,