야심차게 준비한 사이드 프로젝트, 혹은 이제 막 시작하는 스타트업의 서비스를 AWS의 프리티어(Free Tier) EC2 인스턴스에 배포하셨나요? 저렴한 비용, 혹은 무료로 나만의 서버를 운영할 수 있다는 설렘도 잠시, 매일 혹은 불특정한 주기로 서버가 응답하지 않는 현상을 겪고 계실지도 모르겠습니다. SSH 접속은 끊기고, 웹사이트는 '연결할 수 없음' 페이지만 띄우며, 재부팅을 해야만 잠시 정상으로 돌아오는 악순환. 로그를 뒤져봐도 뚜렷한 원인을 찾기 힘든 이 미스터리한 서버 다운의 주범은 바로 '메모리 부족(Out of Memory)'일 가능성이 매우 높습니다.
특히 Spring Boot와 같은 JVM(Java Virtual Machine) 기반의 프레임워크와 MySQL, PostgreSQL 같은 데이터베이스를 1GB 램(RAM)을 가진 t2.micro
나 t3.micro
같은 인스턴스에서 함께 운영한다면, 이는 예견된 문제나 다름없습니다. 이 글에서는 AWS EC2 환경에서 발생하는 메모리 부족 문제의 근본적인 원인을 심층적으로 분석하고, '스왑 메모리(Swap Memory)'라는 가장 현실적이고 효과적인 해결책을 A부터 Z까지 상세하게 알려드립니다. 더 이상 원인 모를 서버 다운 때문에 스트레스받지 마세요. 이 가이드를 통해 서버 안정성을 확보하고, 개발에만 집중할 수 있는 환경을 구축해 보세요.
문제의 근원: 왜 내 서버는 자꾸만 멈추는가?
문제를 해결하기 위해선 먼저 원인을 정확히 알아야 합니다. '서버가 죽는다'는 현상은 여러 가지 이유로 발생할 수 있지만, 저사양 EC2 인스턴스에서는 그 원인이 메모리와 직결된 경우가 대부분입니다.
프리티어의 함정: 1GB 메모리의 한계
AWS 프리티어는 개발자들에게 클라우드 환경을 맛볼 수 있는 훌륭한 기회를 제공합니다. 대표적으로 t2.micro
인스턴스는 12개월 동안 매월 750시간을 무료로 제공하죠. 하지만 여기에는 명확한 한계가 존재합니다. 바로 물리 메모리(RAM)가 1GB에 불과하다는 점입니다.
1GB는 현대적인 웹 애플리케이션 스택을 구동하기에 결코 넉넉한 용량이 아닙니다. 운영체제(OS) 자체가 기본적인 작동을 위해 일정량의 메모리를 점유하고, 여기에 우리가 배포한 애플리케이션(예: Spring Boot), 데이터베이스(예: MySQL), 그리고 웹 서버(예: Nginx) 등이 추가로 메모리를 할당받아 사용합니다. 사용자가 접속하여 요청을 처리하기 시작하면 메모리 사용량은 더욱 가파르게 증가합니다.
리눅스의 암살자, OOM Killer의 등장
리눅스 커널은 시스템 전체의 안정성을 유지하기 위해 매우 중요한 메커니즘을 가지고 있습니다. 바로 OOM(Out of Memory) Killer입니다. 시스템의 가용 메모리가 거의 소진되어 더 이상 정상적인 운영이 불가능하다고 판단될 때, OOM Killer가 강제로 등판합니다.
OOM Killer는 무작위로 프로세스를 종료시키는 것이 아니라, 정해진 규칙(OOM Score)에 따라 시스템에 가장 큰 부담을 주고 있다고 판단되는, 즉 메모리를 가장 많이 차지하고 있는 프로세스를 찾아내 강제로 종료(kill)시킵니다. 이는 시스템 전체가 다운되는 최악의 상황을 막기 위한 운영체제의 자기방어 수단입니다. 안타깝게도, Spring Boot 애플리케이션(JVM)이나 MySQL 서버는 보통 시스템에서 가장 많은 메모리를 사용하는 주범들이기 때문에 OOM Killer의 첫 번째 타겟이 될 확률이 매우 높습니다. 바로 이것이 우리가 겪는 '원인 모를 서버 다운'의 실체입니다.
메모리를 삼키는 하마: Spring Boot와 MySQL
왜 이 두 조합이 유독 메모리 문제에 취약할까요?
- Spring Boot (JVM): 자바 애플리케이션은 JVM 위에서 동작합니다. JVM은 시작 시 운영체제로부터 큰 메모리 덩어리(Heap, Metaspace 등)를 할당받습니다. 특히 애플리케이션의 규모가 커지고, 처리하는 데이터나 로직이 복잡해질수록 필요한 힙 메모리(Heap Memory)의 크기는 기하급수적으로 늘어납니다. 1GB 램 환경에서 JVM에 할당된 메모리만으로도 이미 시스템의 절반 가까이를 차지할 수 있습니다.
- MySQL: 데이터베이스는 빠른 데이터 접근을 위해 '버퍼 풀(Buffer Pool)'이나 '캐시(Cache)'와 같은 다양한 메모리 영역을 사용합니다. 자주 사용되는 데이터나 인덱스를 디스크가 아닌 메모리에 올려두고 사용함으로써 성능을 극대화하는 것이죠. 이 역시 상당한 양의 메모리를 요구하며, 쿼리가 복잡해지거나 동시 접속자가 늘어날수록 사용량은 증가합니다.
결국, 1GB라는 제한된 공간에 운영체제, JVM, MySQL이 함께 살아가다 보니, 순간적인 트래픽 증가나 특정 작업(배치, 복잡한 쿼리 등)으로 인해 메모리 사용량이 임계점을 넘어서게 되고, OOM Killer가 출동하여 우리에게 소중한 애플리케이션이나 데이터베이스 프로세스를 강제 종료시키는 시나리오가 반복되는 것입니다.
원인 분석: 내 서버의 메모리 상태 정밀 진단하기
이제 이론적인 배경을 알았으니, 실제 내 서버에서 어떤 일이 벌어지고 있는지 직접 확인해 볼 차례입니다. 진단은 AWS 콘솔과 SSH 터미널 두 가지 방법으로 진행합니다.
1단계: AWS 콘솔에서 이상 징후 포착하기
가장 먼저, 그리고 가장 쉽게 접근할 수 있는 곳이 바로 AWS Management Console입니다.
- AWS 콘솔에 로그인하여 EC2 대시보드로 이동합니다.
- 문제가 발생한 인스턴스를 선택합니다.
- 하단 탭에서 [모니터링(Monitoring)] 탭을 클릭하여 CPU 사용률, 네트워크 입출력 등의 기본 지표를 확인합니다. 메모리 부족 현상이 발생하기 직전, CPU 사용률이 급증하는 패턴을 보이는 경우도 많습니다.
- 더 결정적인 증거를 찾기 위해, 인스턴스를 우클릭하거나 상단의 [작업(Actions)] 메뉴를 클릭한 뒤, [모니터링 및 문제 해결(Monitor and troubleshoot)] > [시스템 로그 가져오기(Get system log)]를 선택합니다.
- 방대한 양의 로그가 나타날 것입니다. 여기서 'Out of Memory' 또는 'oom-killer'와 같은 키워드로 검색해 보세요. 아래와 비슷한 로그를 발견했다면, 메모리 부족이 문제의 원인임을 100% 확신할 수 있습니다.
[... EIP is at do_exit+0x80/0x90] Out of memory: Kill process 12345 (java) score 850 or sacrifice child Killed process 12345 (java) total-vm:2345678kB, anon-rss:876543kB, file-rss:0kB, shmem-rss:0kB oom_reaper: reaped process 12345 (java), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
- 또 다른 유용한 기능은 [인스턴스 스크린샷 가져오기(Get instance screenshot)] 입니다. 서버가 완전히 멈춘 상태(Kernel Panic 등)일 때, 스크린샷을 통해 마지막 화면 상태를 확인할 수 있습니다.
AWS 콘솔의 '모니터링 및 문제 해결' 메뉴를 통해 시스템 로그와 스크린샷을 확인할 수 있습니다.
2단계: SSH 접속 후, 터미널에서 직접 확인하기
서버가 아직 살아있을 때, 혹은 재부팅 직후에 SSH로 접속하여 메모리 상태를 직접 확인하는 것은 매우 중요합니다. 가장 대표적인 명령어는 free
입니다.
# -h 옵션은 사람이 읽기 쉬운 단위(GB, MB, KB)로 보여줍니다.
$ free -h
이 명령어를 실행하면 다음과 비슷한 결과를 보게 될 것입니다. (스왑 설정 전)
total used free shared buff/cache available
Mem: 981Mi 850Mi 50Mi 0.0Ki 81Mi 45Mi
Swap: 0Bi 0Bi 0Bi
여기서 주목해야 할 부분은 'Swap' 행입니다. total, used, free가 모두 0으로 표시되어 있다면, 현재 시스템에는 스왑 메모리가 전혀 설정되어 있지 않다는 의미입니다. 즉, 981MB의 물리 메모리가 소진되면 시스템은 더 이상 기댈 곳이 없는 상태인 것입니다. 'free'와 'available' 메모리가 매우 낮게 유지되고 있다면 OOM Killer의 등장이 임박했다는 신호입니다.
실시간으로 어떤 프로세스가 메모리를 많이 사용하는지 확인하고 싶다면 top
이나 htop
명령어를 사용하세요. htop
은 top
보다 시각적으로 보기 편하며, 설치가 필요할 수 있습니다(sudo apt-get install htop
또는 sudo yum install htop
).
해결책: 스왑 메모리(Swap Space) 생성으로 EC2에 인공호흡하기
원인 분석을 통해 우리 서버가 메모리 부족으로 고통받고 있다는 사실을 확인했습니다. 가장 확실한 해결책은 인스턴스 사양을 업그레이드(Scale-up)하는 것이지만, 이는 즉각적인 비용 상승을 의미합니다. 사이드 프로젝트나 초기 서비스 단계에서는 부담스러울 수 있죠. 이때 가장 경제적이면서도 효과적인 대안이 바로 스왑 메모리(Swap Space)를 설정하는 것입니다.
스왑 메모리란 무엇인가? (가상 메모리의 개념)
스왑 메모리는 물리적인 RAM이 부족할 때, 하드 디스크(EC2의 경우 EBS 볼륨)의 일부 공간을 마치 RAM처럼 사용하는 가상 메모리 기술입니다. 운영체제는 당장 사용하지 않는 메모리 페이지(데이터 조각)를 RAM에서 디스크의 스왑 공간으로 옮겨(Swap-out) RAM 공간을 확보합니다. 그리고 나중에 해당 데이터가 다시 필요해지면 디스크의 스왑 공간에서 RAM으로 다시 불러옵니다(Swap-in).
- 장점: 물리 메모리(RAM) 용량을 초과하는 메모리를 사용할 수 있게 되어 시스템의 안정성이 크게 향상됩니다. OOM Killer에 의해 애플리케이션이 갑자기 종료되는 현상을 방지할 수 있습니다.
- 단점: 디스크 I/O는 RAM에 비해 수백, 수천 배 느립니다. 따라서 스왑이 빈번하게 발생하면 시스템 전반의 성능 저하를 유발할 수 있습니다.
즉, 스왑은 비상시에 대비한 '보험'과도 같습니다. 평소에는 사용하지 않다가, 갑작스러운 메모리 사용량 급증으로 시스템이 다운되는 최악의 상황을 막아주는 역할을 합니다. 프리티어 인스턴스에서는 이 보험이 필수적입니다.
스왑 파일 생성 실전 가이드 (Step-by-Step)
리눅스에서 스왑 공간을 만드는 방법은 '스왑 파티션'과 '스왑 파일' 두 가지가 있습니다. 이미 시스템이 운영 중인 EC2 환경에서는 파티션을 나누기 번거로우므로, 파일을 이용하는 것이 훨씬 간편하고 유연합니다. 이제부터 2GB 크기의 스왑 파일을 생성하고 적용하는 과정을 단계별로 진행해 보겠습니다. (EC2 인스턴스의 RAM이 1GB이므로, 2배인 2GB를 스왑으로 할당하는 것이 일반적인 권장 사항입니다.)
EC2 인스턴스에 SSH로 접속한 후, 아래 명령어들을 순서대로 실행하세요.
1. 현재 스왑 공간 확인
먼저, 혹시 모를 기존 스왑 설정을 확인합니다. 이미 위에서 free -h
로 확인했지만, 다시 한번 swapon
명령어로 확인해 봅니다. 아무것도 출력되지 않으면 스왑이 없는 것이 맞습니다.
sudo swapon --show
2. 스왑 파일 생성
dd
명령어를 사용하여 스왑으로 사용할 파일을 생성합니다. 이 파일은 EBS 볼륨의 루트 디렉토리(/
)에 swapfile
이라는 이름으로 생성됩니다. dd
는 디스크 블록 단위로 데이터를 복사하는 강력한 도구입니다.
if=/dev/zero
: 0으로 채워진 특수 파일(null-byte stream)을 입력 소스로 사용합니다.of=/swapfile
:/swapfile
이라는 이름의 파일을 출력 대상으로 지정합니다.bs=1G
: 블록 크기(block size)를 1기가바이트(GB)로 설정합니다.count=2
: 1GB 크기의 블록을 2개 생성합니다. 결과적으로 2GB 크기의 파일이 만들어집니다.
sudo dd if=/dev/zero of=/swapfile bs=1G count=2
(참고: 구형 시스템이나 가이드에서는 bs=1M count=2048
과 같이 메가바이트 단위를 사용하기도 합니다. 결과는 동일하지만, 블록 크기를 크게 잡는 것이 일반적으로 더 효율적입니다.)
3. 스왑 파일 권한 설정 (⭐ 매우 중요!)
생성된 스왑 파일은 민감한 정보를 담을 수 있으므로, 보안을 위해 root 사용자만 읽고 쓸 수 있도록 권한을 변경해야 합니다. 이 단계를 건너뛰면 시스템이 스왑 파일 사용을 거부할 수 있습니다.
sudo chmod 600 /swapfile
4. 파일을 스왑 영역으로 포맷
이제 일반 파일인 /swapfile
을 리눅스가 스왑 공간으로 인식할 수 있도록 포맷해야 합니다. mkswap
명령어가 이 역할을 수행합니다.
sudo mkswap /swapfile
명령어를 실행하면 "Setting up swapspace version 1, size = 2 GiB (2147479552 bytes)"와 같은 메시지와 함께 고유한 UUID가 출력될 것입니다.
5. 스왑 파일 활성화
마지막으로, 생성하고 포맷한 스왑 파일을 시스템이 즉시 사용하도록 활성화합니다.
sudo swapon /swapfile
이제 모든 설정이 끝났습니다. 정말로 스왑 메모리가 적용되었는지 확인해 봅시다.
변경 사항 확인 및 영구 적용
1. 스왑 적용 확인
다시 한번 free -h
와 swapon --show
명령어를 실행해 보세요.
$ free -h
total used free shared buff/cache available
Mem: 981Mi 850Mi 50Mi 0.0Ki 81Mi 45Mi
Swap: 2.0Gi 0Bi 2.0Gi
$ sudo swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 2G 0B -2
free -h
출력 결과의 'Swap' 행에 2.0GiB가 할당된 것을 볼 수 있습니다. swapon --show
결과에서도 /swapfile
이 정상적으로 등록된 것을 확인할 수 있습니다. 이제 여러분의 EC2 인스턴스는 1GB의 물리 메모리와 2GB의 가상 메모리, 총 3GB에 가까운 메모리 풀을 가지게 된 것입니다!
2. 재부팅 후에도 유지하기 (영구 적용)
여기서 끝이 아닙니다. sudo swapon /swapfile
명령어는 현재 세션에만 스왑을 활성화합니다. 만약 EC2 인스턴스가 재부팅되면 스왑 설정은 사라지고 다시 원점으로 돌아갑니다. 스왑 설정을 영구적으로 유지하려면, 시스템 부팅 시 파일 시스템 정보를 담고 있는 /etc/fstab
파일에 스왑 정보를 등록해야 합니다.
(주의) /etc/fstab
파일은 시스템 부팅에 매우 중요한 파일입니다. 잘못 수정하면 시스템이 부팅되지 않을 수 있으니, 반드시 백업 후 신중하게 작업하세요.
먼저, 만약을 대비해 원본 파일을 백업합니다.
sudo cp /etc/fstab /etc/fstab.bak
그 다음, echo
와 tee
명령어를 사용하여 파일의 맨 끝에 스왑 설정 라인을 안전하게 추가합니다. (직접 편집기를 여는 것보다 실수를 줄일 수 있습니다.)
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
위 명령어는 '/swapfile none swap sw 0 0'
라는 문자열을 /etc/fstab
파일의 끝에 추가(-a
옵션)합니다. 각 항목의 의미는 다음과 같습니다.
/swapfile
: 스왑으로 사용할 파일의 경로none
: 마운트 포인트 (스왑은 특정 디렉토리에 마운트되지 않으므로 none)swap
: 파일 시스템의 종류sw
: 스왑 옵션0
: dump 명령어로 덤프할지 여부 (0은 덤프 안 함)0
: 부팅 시 파일 시스템 체크 순서 (0은 체크 안 함)
고급 튜닝: 스왑 사용 최적화하기
스왑 설정을 완료했지만, 여기서 만족하지 않고 한 걸음 더 나아가 시스템을 최적화할 수 있습니다. 바로 커널이 스왑 공간을 얼마나 '공격적으로' 사용할지 조절하는 것입니다.
'Swappiness' 값 조절하여 성능 저하 최소화하기
리눅스 커널에는 vm.swappiness
라는 파라미터가 있습니다. 이 값은 0부터 100까지 설정할 수 있으며, 시스템이 스왑을 얼마나 자주 사용할지를 결정합니다.
- 100: 매우 공격적으로 스왑을 사용합니다. RAM에 여유 공간이 있어도 비활성 메모리 페이지를 스왑으로 옮기려 합니다. (데스크톱 환경에 적합)
- 0: RAM이 완전히 소진되기 전까지는 절대 스왑을 사용하지 않습니다.
- 기본값: 대부분의 리눅스 배포판에서 기본값은 60입니다.
서버 환경에서는 디스크 I/O로 인한 성능 저하를 최소화하는 것이 중요합니다. 따라서 물리 RAM을 최대한 활용하고, 정말 필요할 때만 스왑을 사용하도록 swappiness 값을 낮춰주는 것이 좋습니다. 일반적으로 서버에는 10 정도의 낮은 값이 권장됩니다.
먼저 현재 설정된 swappiness 값을 확인합니다.
cat /proc/sys/vm/swappiness
아마 60 또는 30 같은 값이 나올 것입니다. 이 값을 10으로 임시 변경해 보겠습니다.
sudo sysctl vm.swappiness=10
이 설정 또한 재부팅하면 초기화됩니다. 영구적으로 적용하려면 /etc/sysctl.conf
파일을 수정해야 합니다.
# /etc/sysctl.conf 파일 끝에 다음 라인을 추가합니다.
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
이제 여러분의 서버는 RAM을 최대한 활용하다가, 정말 위급한 순간에만 스왑 공간을 사용하게 되어 안정성과 성능 사이의 균형을 맞출 수 있게 되었습니다.
모니터링의 중요성: 스왑이 만병통치약은 아니다
스왑 메모리는 저사양 인스턴스의 안정성을 비약적으로 높여주는 훌륭한 도구이지만, 만병통치약은 아닙니다. 만약 시스템이 지속적으로 스왑 공간을 사용하고 있다면(이를 '스래싱(thrashing)'이라고 합니다), 이는 애플리케이션이 요구하는 메모리가 인스턴스의 물리적인 한계를 근본적으로 초과했다는 신호입니다.
vmstat
명령어를 사용하면 스왑 인(si)/아웃(so) 활동을 실시간으로 모니터링할 수 있습니다.
# 1초 간격으로 시스템 상태를 출력합니다.
vmstat 1
출력되는 결과에서 si
(swap-in, 디스크에서 메모리로)와 so
(swap-out, 메모리에서 디스크로) 컬럼의 숫자가 지속적으로 0이 아닌 높은 값을 유지한다면, 심각한 성능 저하가 발생하고 있다는 뜻입니다. 이 경우에는 애플리케이션의 메모리 사용량을 최적화하거나, 더 이상 미루지 말고 EC2 인스턴스 사양을 한 단계 높은 것(예: t3.small
)으로 업그레이드하는 것을 심각하게 고려해야 합니다.
결론: 안정적인 서버 운영을 위한 첫걸음
지금까지 AWS EC2 프리티어 인스턴스에서 빈번하게 발생하는 서버 다운 현상의 원인이 '메모리 부족'과 'OOM Killer'에 있음을 파악했습니다. 그리고 이에 대한 가장 현실적인 해결책으로 '스왑 파일'을 생성하고, 활성화하고, 영구적으로 적용하는 모든 과정을 상세하게 살펴보았습니다. 더 나아가 'swappiness' 값 조정을 통해 성능을 최적화하는 방법까지 알아보았습니다.
스왑 메모리를 설정하는 것은 단순히 몇 가지 명령어를 입력하는 행위를 넘어, 내 서버의 한계를 명확히 인지하고 그에 맞는 대비책을 마련하는 과정입니다. 이는 안정적인 서비스를 운영하기 위한 가장 기본적인 첫걸음입니다. 이제 여러분의 소중한 애플리케이션은 예기치 않은 메모리 부족 사태에도 버틸 수 있는 맷집을 가지게 되었습니다. 서버 다운의 공포에서 벗어나, 이제 다시 즐거운 개발의 세계로 돌아갈 시간입니다!
0 개의 댓글:
Post a Comment