"DB가 너무 느려서 쿼리를 튜닝했습니다"
초보 시절, 서비스가 느려졌을 때 선배가 묻더군요. "너 DB 서버 스토리지 뭐 쓰니?" "아마... 그냥 하드 아닌가요?" 선배는 한심하다는 듯이 SSD로 교체하라고 했고, 거짓말처럼 모든 문제가 해결됐습니다. 저는 밤새 SQL 쿼리를 튜닝하고 있었는데, 그냥 장비 탓이었던 거죠.
도대체 SSD와 HDD는 뭐가 다르길래 이런 차이를 만들까요? 이건 LP판과 USB 메모리의 차이였습니다.
1. HDD는 도서관 사서처럼 "걸어서" 데이터를 가져온다
처음엔 HDD가 그냥 "느린 SSD"라고 생각했습니다. 속도만 다른 같은 종류의 저장장치라고요. 아니었습니다. 작동 방식 자체가 완전히 달랐습니다.
HDD는 LP판(레코드)입니다.
- 구조: 자석으로 된 원판(Platter)이 분당 7,200회(7200 RPM) 혹은 10,000회(10K RPM)로 회전합니다. 바늘(Head)이 데이터를 읽습니다.
- 문제점:
- 원판이 돌아가야 읽을 수 있다 (Rotational Latency, 회전 대기 시간).
- 바늘이 데이터가 있는 위치까지 이동해야 한다 (Seek Time, 탐색 시간).
- 랜덤한 데이터를 읽으려면 바늘이 쉴 새 없이 춤을 춰야 한다.
이 "물리적 이동" 때문에 속도에 명확한 한계가 있습니다.
도서관 사서 비유를 떠올려봤습니다. HDD는 책장에서 책을 찾아 걸어오는 사서입니다. 책이 멀수록 느립니다. 10권의 책이 제각각 다른 서가에 있다면? 사서는 도서관을 10번 왕복해야 합니다.
그래서 DB 서버에서 HDD를 쓰면 재앙입니다. 데이터베이스는 "한 번에 1,000개의 작은 데이터를 조금씩 읽는" 작업이 흔합니다. HDD 바늘은 그 1,000개의 위치를 찾느라 발광합니다.
2. SSD는 순간이동한다
SSD는 USB 메모리입니다.
- 구조: 반도체 칩(Flash Memory)에 전자를 가둬서 데이터를 저장합니다.
- 특징: 물리적으로 움직이는 부품이 하나도 없습니다.
- 장점:
- 데이터 위치가 어디든 접근 속도가 똑같습니다 (Random Access).
- 바늘이 도달할 때까지 기다릴 필요가 없습니다. 빛(전기)의 속도로 읽습니다.
순간이동하는 사서라고 생각했습니다. 책 위치를 아는 순간 그 자리에 순간이동해서 바로 가져옵니다. 10권의 책이 흩어져 있어도 상관없습니다. 그냥 10번 순간이동하면 됩니다.
여기서 핵심 깨달음이 왔습니다. "SSD는 랜덤 액세스가 빠르고, HDD는 순차 액세스가 빠르다"는 말이 이제 와닿았습니다.
- 순차 읽기 (Sequential Read): 영화 파일 1GB를 처음부터 끝까지 쭉 읽기 → HDD도 괜찮음 (바늘이 한 방향으로만 움직이면 됨)
- 랜덤 읽기 (Random Read): 데이터베이스에서 흩어진 레코드 1,000개 읽기 → HDD 지옥, SSD 천국
3. NAND Flash: SSD도 완벽하진 않다
SSD는 마법이 아닙니다. 반도체 칩 안에 전자를 가둬서 데이터를 저장하는데, 이 "전자 감옥"이 영원하진 않습니다.
NAND Flash 종류 - 셀 하나에 몇 비트를 넣을까?
SSD의 핵심인 NAND Flash에는 종류가 있습니다. 셀(Cell) 하나에 몇 비트를 저장하느냐에 따라 나뉩니다.
| 타입 | 비트/셀 | 수명 (P/E Cycles) | 속도 | 가격 | 용도 |
|---|---|---|---|---|---|
| SLC (Single-Level Cell) | 1 | 100,000회 | 가장 빠름 | 매우 비쌈 | 기업용 서버, 산업용 |
| MLC (Multi-Level Cell) | 2 | 10,000회 | 빠름 | 비쌈 | 고급 노트북, 워크스테이션 |
| TLC (Triple-Level Cell) | 3 | 3,000회 | 보통 | 적당함 | 일반 소비자용 (우리가 쓰는 SSD 대부분) |
| QLC (Quad-Level Cell) | 4 | 1,000회 | 느림 | 저렴함 | 대용량 저장용 (읽기 위주) |
P/E Cycles는 Program/Erase Cycles입니다. 쉽게 말해 "몇 번 쓰고 지울 수 있냐"입니다.
처음엔 이 수치를 보고 놀랐습니다. "TLC는 3,000번밖에 못 쓴다고? 그럼 금방 고장나는 거 아냐?"
알고 보니 Wear Leveling 덕분에 괜찮았습니다.
Wear Leveling: 골고루 닳게 하기
SSD 컨트롤러는 똑똑합니다. 같은 셀만 계속 쓰지 않고, 모든 셀을 골고루 사용합니다.
예를 들어, 256GB SSD에 10GB 파일을 매일 쓰고 지운다고 칩시다. 컨트롤러는 이 파일을 매번 다른 물리적 위치에 씁니다. 256GB 전체 영역을 돌아가며 쓰는 거죠.
결과: TLC SSD (3,000 P/E Cycles, 256GB)는 이론상 256GB × 3,000회 = 768TB를 쓸 수 있습니다. 하루에 10GB씩 쓴다면? 210년 쓸 수 있습니다.
이제 이해했습니다. SSD는 생각보다 훨씬 오래 갑니다.
Write Amplification: 쓰기 증폭
그런데 SSD에는 함정이 하나 더 있습니다. 파일을 1GB 쓰면, 실제로는 1.5GB를 쓴다는 겁니다.
왜냐하면 SSD는 덮어쓰기가 안 되기 때문입니다.
- HDD: 같은 자리에 바로 덮어씁니다.
- SSD: 기존 데이터를 먼저 지워야 합니다. 그런데 "지우기"는 블록(Block) 단위로만 가능합니다.
블록 안에는 여러 페이지(Page)가 있습니다.
- 쓰기/읽기: 페이지 단위 (4KB ~ 16KB)
- 지우기: 블록 단위 (256KB ~ 4MB)
4KB 파일 하나를 수정하려면?
- 해당 블록 전체(256KB)를 읽어옵니다.
- 수정할 부분만 바꿉니다.
- 블록을 지웁니다.
- 전체를 다시 씁니다.
4KB를 수정하는데 256KB를 쓴 셈입니다. 이게 Write Amplification입니다.
이 문제는 TRIM 명령어로 완화됩니다.
TRIM: OS가 SSD에게 "이 데이터 지워도 돼"라고 알려주기
파일을 삭제하면, OS는 실제로 데이터를 지우지 않습니다. 그냥 파일 테이블에서 "이 공간은 빈 공간이야"라고 표시만 합니다.
문제는 SSD는 그걸 모릅니다. 여전히 그 공간에 유효한 데이터가 있다고 생각합니다. 나중에 그 공간에 새 데이터를 쓰려고 하면? 앞서 설명한 "읽기 → 수정 → 지우기 → 쓰기" 과정을 거칩니다.
TRIM 명령어는 OS가 SSD에게 이렇게 말합니다. "이 블록들은 더 이상 안 쓰는 데이터야. 미리 지워도 돼."
SSD는 한가할 때 이 블록들을 미리 지웁니다. 나중에 새 데이터를 쓸 때 훨씬 빨라집니다.
리눅스에서 TRIM이 활성화돼 있는지 확인하는 법:
# TRIM 스케줄이 활성화돼 있는지 확인
systemctl status fstrim.timer
# 수동으로 TRIM 실행 (SSD 성능이 떨어졌을 때)
sudo fstrim -v /
저도 처음엔 이걸 몰라서, 몇 달 쓴 SSD가 느려진 적이 있습니다. TRIM을 한 번 돌리니까 다시 빨라지더군요.
4. 실제로의 적용 - IOPS (초당 입출력 횟수)
서버 성능에서 가장 중요한 지표 중 하나가 IOPS(Input/Output Operations Per Second)입니다. 쉽게 말해 "1초에 몇 번 읽고 쓸 수 있냐"입니다.
HDD vs SSD 비교
- HDD (7200 RPM): 약 100 ~ 200 IOPS
- SSD (SATA): 약 50,000 ~ 100,000 IOPS
- NVMe SSD: 약 500,000+ IOPS
보이시나요? 100 vs 50,000입니다. DB처럼 자잘한 데이터를 수시로 읽고 쓰는 작업에서 HDD를 쓰는 건, 페라리 엔진(CPU)을 달고 타이어 대신 맷돌(HDD)을 끼운 것과 같습니다.
AWS EBS 볼륨 타입 - 돈으로 IOPS를 산다
AWS에서 서버를 돌리면 EBS(Elastic Block Store) 볼륨을 선택해야 합니다. 처음엔 이게 뭔지 몰라서 그냥 기본값(gp2)을 썼습니다.
나중에 알고 보니, IOPS에 따라 가격이 천차만별이더군요.
| 타입 | IOPS | 처리량 | 용도 | 가격 (대략) |
|---|---|---|---|---|
| gp3 (범용 SSD) | 3,000 ~ 16,000 (설정 가능) | 125 ~ 1,000 MB/s | 대부분의 워크로드 | 저렴 (기본) |
| gp2 (구형 범용 SSD) | 100 ~ 16,000 (용량에 비례) | 250 MB/s | 예전 기본값 | gp3보다 비쌈 |
| io1 (프로비저닝 IOPS) | 최대 64,000 | 1,000 MB/s | DB 서버, 고성능 필요 | 매우 비쌈 |
| io2 (차세대 io1) | 최대 64,000 | 1,000 MB/s | 미션 크리티컬 DB | 더 비쌈 (내구성 99.999%) |
| st1 (HDD) | 500 | 500 MB/s | 로그, 빅데이터 | 저렴 |
| sc1 (콜드 HDD) | 250 | 250 MB/s | 아카이브, 백업 | 가장 저렴 |
핵심 깨달음:
- gp3는 IOPS와 처리량을 따로 설정할 수 있습니다. gp2는 용량에 따라 자동으로 IOPS가 결정됩니다.
- io1/io2는 "최소 IOPS를 보장"합니다. 돈을 더 내는 대신, 성능이 절대 떨어지지 않습니다.
제 경험상, 대부분의 경우 gp3로 충분했습니다. DB 서버가 초당 수만 건의 쿼리를 처리하는 게 아니라면요.
만약 RDS 성능이 느리다면, 우선 CloudWatch에서 IOPS 사용량을 확인해봐야 합니다.
# EC2 인스턴스에서 디스크 I/O 모니터링
iostat -x 1
# 어떤 프로세스가 디스크를 가장 많이 쓰는지 확인
sudo iotop
저도 처음엔 이 명령어들을 몰랐습니다. 서버가 느려졌을 때 CPU, 메모리만 보다가, 디스크 I/O가 병목이라는 걸 뒤늦게 발견한 적이 있습니다.
디스크 벤치마크 - fio로 IOPS 측정하기
"내 서버 SSD가 정말 빠른 걸까?" 의심이 들 때가 있습니다. 특히 클라우드 서버는 눈에 보이지 않으니까요.
fio(Flexible I/O Tester)로 직접 벤치마크할 수 있습니다.
# fio 설치 (Ubuntu)
sudo apt install fio
# 랜덤 읽기 IOPS 테스트 (4KB 블록)
fio --name=random-read \
--ioengine=libaio \
--iodepth=64 \
--rw=randread \
--bs=4k \
--direct=1 \
--size=1G \
--numjobs=4 \
--runtime=60 \
--group_reporting
# 랜덤 쓰기 IOPS 테스트 (4KB 블록)
fio --name=random-write \
--ioengine=libaio \
--iodepth=64 \
--rw=randwrite \
--bs=4k \
--direct=1 \
--size=1G \
--numjobs=4 \
--runtime=60 \
--group_reporting
결과 해석:
- IOPS:
read: IOPS=12345같은 항목을 보면 됩니다. - HDD라면: 100 ~ 200 IOPS
- SATA SSD라면: 10,000 ~ 100,000 IOPS
- NVMe SSD라면: 100,000+ IOPS
저는 이 테스트를 한 번 돌려보고 깨달았습니다. "아, 내 로컬 맥북 SSD는 진짜 빠르구나. AWS gp3는 생각보다 느리네."
클라우드는 공유 스토리지라서, 옆 서버가 디스크를 많이 쓰면 내 성능도 떨어집니다. 그래서 프로비저닝 IOPS(io1/io2)가 비싼 겁니다. 전용 성능을 보장하니까요.
5. Hot Data vs Cold Data: 적재적소에 배치하기
그렇다고 무조건 SSD가 답일까요? 비용이 문제입니다. HDD는 용량 대비 가격이 압도적으로 저렴합니다.
- HDD: 1TB당 약 $20 ~ $30
- SSD: 1TB당 약 $100 ~ $150
5배 차이입니다.
그래서 "데이터를 얼마나 자주 쓰느냐"에 따라 저장 장치를 나눠야 합니다.
Hot Data (뜨거운 데이터) - 자주 쓰는 데이터
- 예시: DB, 캐시(Redis), OS, 로그 분석 중인 파일
- 저장소: 무조건 SSD
- 이유: 랜덤 액세스가 많고, IOPS가 높아야 함
Warm Data (따뜻한 데이터) - 가끔 쓰는 데이터
- 예시: 최근 3개월 로그, 백업 파일(주 1회 복구 테스트)
- 저장소: SSD (gp3) 혹은 저속 SSD
- 이유: 자주 쓰진 않지만, 필요할 때 빨리 읽어야 함
Cold Data (차가운 데이터) - 거의 안 쓰는 데이터
- 예시: 3년 전 로그, CCTV 영상, 법적 보관 의무 데이터
- 저장소: HDD (S3 Glacier, AWS st1/sc1)
- 이유: 용량이 크고, 읽을 일이 거의 없음
실제로 이걸 어떻게 적용했냐면,
1. RDS DB 서버
- 본 서버: io2 (프로비저닝 IOPS, 64,000 IOPS 보장)
- 읽기 전용 복제본: gp3 (저렴하게)
2. 로그 저장
- 최근 7일: EBS gp3 (ElasticSearch 인덱싱)
- 8 ~ 90일: S3 Standard
- 91일 이상: S3 Glacier (법적 보관)
3. 백업
- 일일 백업 (최근 30일): S3 Standard (빠른 복구)
- 월간 백업 (1년): S3 Glacier (저렴함)
이렇게 하니까 스토리지 비용이 60% 줄었습니다.
6. 정리 - 회전하는 것은 느리다
| 구분 | HDD | SSD |
|---|---|---|
| 비유 | LP판 (레코드) | 스마트폰 메모리 |
| 작동 방식 | 물리적 회전 (모터) | 반도체 (전자 회로) |
| IOPS | 100 ~ 200 | 50,000+ |
| 용도 | 백업, 대용량 아카이브 | OS, DB, 웹 서버 |
"물리적으로 움직이는 부품은 무조건 느리고 고장 잘 난다."
이것만 기억해도 서버 장애의 절반은 예방할 수 있습니다.
그리고 또 하나. "데이터를 얼마나 자주 쓰느냐에 따라 저장소를 나눠라."
HDD는 느리지만 저렴합니다. Cold Data에는 최고입니다. SSD는 빠르지만 비쌉니다. Hot Data에만 써야 비용이 터지지 않습니다.
제 삽질 경험상, 이 두 가지만 제대로 지켜도 스토리지 문제는 거의 없습니다.