SSD, 빠르지만 존재하는 의외의 단점
SSD(Solid State Drive)와 관련한 이번 글에서, SSD가 우리가 평소에 자주 사용하는 전자기기들(스마트폰, 태블릿, 노트북, ...)에 들어가는 저장장치이지만 모르고 있던 진실에 대해 파헤쳐보고자 한다.
"SSD는 하드디스크보다 빠르지만, '덮어쓰기(Overwrite)'가 안 된다는 치명적인 단점이 있다."
덮어쓰기가 안 되는데 어떻게 우리가 매일 파일을 수정하고 저장하는 걸까? 오늘은 그 기술 뒤에 숨겨진 NAND Flash의 특성과 이를 제어하는 FTL(Flash Translation Layer), 그리고 수명 연장을 위한 Wear Leveling에 대해 깊이 있게 파고들어 보려 한다.
1. HDD vs SSD
HDD가 회전하는 플래터와 움직이는 암(Arm)이라는 물리적 기계 장치였다면, SSD는 움직이는 부품이 전혀 없는 반도체 메모리다. 이 차이는 성능에서 극명하게 드러난다.
Latency (지연 시간): HDD(약 10ms) vs SSD(약 50µs) -> 약 200배 차이
Throughput (처리량): HDD(100MB/s) vs SSD(수 GB/s)
하지만 단순히 "빠르다"는 것보다 더 중요한 건, 데이터를 저장하는 방식(Cell)이 완전히 다르다는 점이다.
| HDD vs SSD |
2. NAND Flash의 치명적 한계
SSD의 핵심인 NAND Flash는 플로팅 게이트(Floating Gate)라는 곳에 전자를 가두어 데이터를 저장한다. 그런데 이 녀석에게는 아주 까다로운 물리적 제약이 있다.
NAND의 3가지 연산과 비대칭성
Read (읽기): 빠르다. (Page 단위, 약 50µs)
Program (쓰기): 읽기보다 느리다. (Page 단위, 약 500µs)
Erase (지우기): 매우 느리다. (Block 단위, 약 2~4ms)
여기서 문제가 발생한다.
"NAND 셀은 0에서 1로 덮어쓸 수 없다. 반드시 지우기(Erase)를 통해 1로 초기화한 뒤에만 0을 쓸 수 있다."
설상가상으로 읽기와 쓰기는 작은 페이지(Page, 4KB~16KB) 단위로 가능한데, 지우기는 훨씬 큰 블록(Block, 128~256 Pages) 단위로만 수행된다. (추가: 이름이 NAND인 이유는 셀들이 직렬(Series)로 묶여있기 때문이며, 이 덕분에 높은 직접도를 가능하게 했다. 하지만 그로 인해 블록 단위로 지워야 한다.)
| NAND Cell Array |
4KB 데이터를 수정하려고 4MB짜리 블록 전체를 지웠다가 다시 써야 한다면? 성능은 곤두박질칠 것이다. 이것이 SSD 설계의 가장 큰 난제, In-place Update(제자리 덮어쓰기) 불가 문제다.
3. FTL (Flash Translation Layer)
운영체제(OS)는 여전히 디스크를 "덮어쓰기가 가능한 블록 장치"라고 생각한다. OS가 "100번 섹터에 데이터를 써줘"라고 요청했을 때, SSD가 "잠시만요, 블록 전체 지우고 올게요"라고 하면 안 된다.
| HDD vs SSD (in terms of operations) |
그래서 SSD 컨트롤러 내부에는 FTL(Flash Translation Layer)이라는 미들웨어가 존재한다. FTL의 역할은 OS에게는 "덮어쓰기가 되는 척" 거짓말을 하고, 뒤에서는 NAND의 제약 사항을 처리하는 것이다. (일종의 추상화 개념, Page table이 VA를 PA로 매핑하듯, OS가 추상적으로 해석할 수 있다)
| SSD Architecture |
다시말해 FTL은 SSD의 컨트롤러 내 가상의 논리적 섹터를 생성하여, 이를 통해 실제 SSD의 물리적 페이지에 데이터를 기록하도록 도와주는 중간재 역할을 한다. FTL이 논리적 섹터를 물리적 페이지에 저장하기 위해서는 논리적 주소(LPA)를 물리적 주소(PPA) 값으로 변환해야 하는데, 이에 대한 정보는 Mapping table을 참조하여 변환하게 된다. 이때 FTL이 매핑 테이블(Mapping Table)을 어떻게 관리하느냐에 따라 성능과 비용이 천차만별로 달라진다.
| Mapping Table |
(1) Direct Mapped FTL
가장 먼저 떠오르는 단순한 방법은 "논리 주소 N번을 물리 주소 N번에 고정" 시키는 것이다. 마치 배열의 인덱스처럼 말이다.
| Direct Mapped FTL |
작동 방식: 논리 블록 0번은 무조건 물리 블록 0번의 0페이지에 저장한다.
치명적 문제: 쓰기 증폭 (Write Amplification)
만약 물리 페이지 2번에 있는 데이터를 'K'에서 'Z'로 바꾸고 싶다면?
덮어쓰기가 안 되므로 해당 페이지 하나만 바꿀 수 없다.
1단계: 블록 전체(예: 페이지 0~127)를 DRAM으로 읽어온다.
2단계: 데이터를 수정한 뒤, 해당 블록 전체를 Erase(지우기) 한다.
3단계: 수정된 데이터를 포함하여 블록 전체를 다시 Program(쓰기) 한다.
결론: 페이지 하나(4KB)를 고치기 위해 블록 전체(수 MB)를 지우고 다시 쓰는 대참사가 일어난다. 성능은 느려지고, 낸드 플래시의 수명은 순식간에 깎여 나간다. 절대 쓰면 안 되는 방식이다.
(2) Log-structured FTL: 현재의 표준
Direct Mapped 방식의 문제를 해결하기 위해 등장한 것이 로그 구조(Log-structured) 방식이다.
(Log라 해서 수학에서의 로그를 생각했는데 여기서의 로그는 '기록'을 의미함을 주의하자...ㅎㅎ)
원리: 데이터를 수정 요청이 오면, 원래 위치를 건드리지 않고 새로운 빈 페이지(Free Page)에 로그를 쌓듯이 순차적으로 쓴다.
매핑 테이블: 논리 주소(LBA)가 가리키는 물리 주소(PPA)가 계속 바뀌므로, 별도의 매핑 테이블을 DRAM에 유지해야 한다.
장점: 쓰기 증폭이 획기적으로 줄어들고, 랜덤 쓰기를 순차 쓰기로 바꿔주어 성능이 뛰어나다.
단점: 매핑 테이블의 크기가 문제다. 1TB SSD라면 매핑 테이블만 1GB(약 0.1%)의 DRAM이 필요하다. SSD 용량이 커질수록 DRAM 비용이 기하급수적으로 늘어난다.
(3) Demand-based FTL
대용량 SSD에서 DRAM 비용을 줄이기 위해 고안된 방식이다. "모든 매핑 테이블을 비싼 DRAM에 다 올려놓을 필요가 있을까?"라는 의문에서 출발했다.
| Demand-based FTL |
원리: 전체 매핑 테이블은 낸드 플래시(NAND Flash) 자체에 저장한다.
DRAM에는 자주 접근하는 매핑 테이블의 일부(인기 있는 페이지)만 캐싱(Caching)하여 올려둔다.
비유: 도서관의 모든 책 목록(전체 매핑)은 서고에 두고, 자주 찾는 책 목록만 사서 데스크(DRAM)에 두는 것과 같다.
장점: DRAM 용량을 획기적으로 줄일 수 있어 비용 효율적이다. (Log-structured FTL 대비)
단점: 캐시 미스(Cache Miss)가 발생하면, 매핑 정보를 읽기 위해 낸드 플래시를 한 번 더 읽어야 한다. 즉, 랜덤 읽기 성능이 떨어질 수 있다. (높은 Miss Penalty)
4. Garbage Collection (GC)
Log-structured 방식은 필연적으로 "유효하지 않은 쓰레기 데이터(Invalid Page)"를 남긴다. 저장 공간이 가득 차면, 더 이상 새 데이터를 쓸 곳이 없어진다. 이때 수행되는 것이 Garbage Collection(가비지 컬렉션)이다.
| Invalid Page from Log-structured FTL |
유효 데이터가 흩어져 있는 블록들을 고른다.
그 블록들에서 살아있는(Valid) 페이지만 골라내어 다른 깨끗한 블록으로 복사한다.
이제 쓰레기만 남은 원본 블록들을 통째로 Erase 하여 빈 공간(Free Block)으로 만든다.
Trade-off: GC는 쓰기 공간을 확보해주지만, 그 자체로 오버헤드가 크다. SSD가 뜬금없이 느려진다면 백그라운드에서 GC가 열심히 돌고 있을 확률이 높다.
5. Wear Leveling
NAND Flash의 또 다른 단점은 수명이다. 하나의 셀은 약 1만 번에서 10만 번 정도 지우고 쓰기를 반복하면 절연체가 파괴되어 더 이상 데이터를 저장할 수 없게 된다. 특정 블록에만 쓰기가 집중된다면? 그 블록만 먼저 죽어서 SSD 전체 용량이 줄어들거나 고장 날 수 있다.
| Wear Leveling |
이를 막기 위해 FTL은 Wear Leveling(마모 평준화) 기술을 사용한다.
(1) Dynamic Wear Leveling
데이터를 쓸 때, 지우기 횟수(Erase Count)가 적은 '젊은' 블록을 골라서 쓴다.
한계: 한 번 기록되고 거의 수정되지 않는 데이터(Cold Data, 예: OS 커널 파일)가 차지하고 있는 블록은 마모되지 않고 계속 쌩쌩한 상태로 남는다. 결과적으로 전체적인 수명 불균형은 해소되지 않는다.
(2) Static Wear Leveling
이 문제를 해결하기 위해, 가만히 있는 Cold Data를 강제로 다른 블록으로 옮겨버린다.
빈 블록뿐만 아니라 데이터가 있는 블록까지 포함하여, 지우기 횟수가 가장 적은 블록을 찾아내 적극적으로 활용한다.
효과: 모든 블록이 비슷하게 늙어가도록 만들어 SSD의 기대 수명을 극대화한다.
마치며
SSD를 공부하면서 느낀 점은, 완벽한 하드웨어는 없다는 사실이다. NAND Flash는 '덮어쓰기 불가'와 '제한된 수명'이라는 치명적인 단점을 가지고 태어났다. 하지만 컴퓨터 공학자들은 하드웨어를 탓하는 대신, FTL이라는 소프트웨어 계층을 통해 이 문제를 해결했다.
덮어쓰기가 안 되면? -> Log-structured로 빈 곳에 쓰면 된다.
지우기가 느리면? -> GC로 나중에 몰아서 지우면 된다.
특정 부분만 닳으면? -> Wear Leveling으로 골고루 쓰게 만들면 된다.
상황을 바꿀 수 없다면, 그 위에서 작동하는 논리(Architecture)를 바꾸면 된다. SSD는 단순한 저장 장치가 아니라, 제약을 극복하는 엔지니어링의 노력을 엿볼 수 있었던 기회였다.
지난 시간에는 HDD를, 이번 시간에는 SSD에 대해 알아보았으니, 다음 시간에는 이런 저장장치들을 OS가 가상화(Virtualization)하는 방법에 대해 알아보고자 한다.
추천글:
[운영체제] Operating System 전체 포스팅 모음집
[운영체제] 페이징(Paging) | Page Table, Copy-on-Write, TLB
[운영체제] 가상 메모리(Virtual Memory) | 운영체제는 어떻게 프로세스에게 독점 공간을 제공할까?
[운영체제] I/O 시스템, HDD, Disk Scheduling, RAID | CPU와 디스크의 물리적 속도 차이를 극복하는 설계