[OSTEP][영속성] CH44 플래시 기반의 SSD
발표 날짜 2024.12.09 MON 10시a.m.
CH44 플래시 기반의 SSD
솔리드-스테이트 저장 장치 Solid-State Storage 는
하드 디스크처럼 기계적 요소나 회전체가 없고, 메모리와 프로세서처럼 트랜지스터로만 만들어졌다
DRAM 과 달리 SSD 는 전력이 없더라도 정보를 유지할 수 있기 때문에 데이터를 영구적으로 저장할 수 있다
이번 장에서 다룰 플래시 Flash (낸드 기반 플래시 NAND-based Flash) 는
어떤 덩어리 (플래시 페이지 Flash Page) 를 쓰려고 하면, 그보다 더 큰 덩어리 (플래시 블럭 Flash Block) 을 삭제해야 하고,
페이지를 너무 자주 쓰면 마모 Wear Out 되는 독특한 성질을 가지고 있다
핵심 질문 - 플래시 기반의 SSD 를 어떻게 만드는가
어떻게 플래시 기반 SSD 를 만들 수 있을까? 지우기는 비싸다는 성질을 어떻게 다루면 될까? 반복해서 덮어쓰기를 하면 장치를 마모시킬 수 있다는 전제 조건 아래에서 어떻게 하면 장기간 쓸 수 있는 장치를 만들 수 있을까? 기술의 발전이 계속 지속될 수 있을까? 아니면 멈추게 될까?
44.1 하나의 비트를 저장하기
플래시 칩은 하나의 트랜지스터에 하나 또는 그 이상의 비트들을 저장할 수 있도록 설계되어 있고,
트랜지스터의 전하 상태를 이진 값에 대응시켜 데이터를 저장한다
단일 레벨 셀 Single-Level Cell, SLC - 오직 하나의 비트 (1 or 0) 만 트랜지스터에 저장
다중 레벨 셀 Multi-Level Cell, MLC - 두 개의 비트로 아주 적음 00, 적음 01, 많음 10, 아주 많음 11 에 대응한다
삼중 레벨 셀 Triple-Level Cell, TLC - 한 셀의 전하량이 3비트에 대응된다
전반적으로 SLC 칩의 성능이 가장 좋지만, 가장 비싸다
44.2 비트에서 뱅크와 플레인으로
플래시 칩은 많은 수의 셀들로 이루어진 뱅크나 플레인으로 구성되어 있다
뱅크는 삭제 단위 블럭 Erase Block 과 페이지 Page 단위로 접근할 수 있는데 각 뱅크 내에는 많은 수의 블럭들이 존재하고, 각 블럭 내에는 많은 수의 페이지들이 있다
44.3 기본 플래시 작업
플래시 칩이 지원하는 저 수준 작업 세 가지가 있다
* 읽기 (페이지) - 읽기 명령을 요청하면 페이지의 위치에 상관없이 균일한 속도로 읽을 수 있다, 임의 접근 가능 Random Access 장치
* 삭제 (블럭) - 플래시에 페이지를 쓰기 전에 해당 페이지를 포함하는 블럭 전체를 먼저 삭제해야 하는데, 삭제 시 블럭 내의 내용을 모두 1 로 채워서 없애버린다, 중요한 데이터가 해당 블럭에 있다면, 다른 메모리 또는 다른 플래시 블럭으로 모두 복사해야 한다
* 프로그램 (페이지) - 프로그램 명령을 통해 페이지의 1 값을 0 으로 변경하여, 원하는 내용을 플래시의 페이지에 쓸 수 있다
페이지가 프로그램되면 그 내용을 변경하는 유일한 방법은 변경하고자 하는 페이지가 있는 블럭을 완전히 삭제하는 것이다
페이지를 읽는 것은 그냥 읽으면 되지만 (플래시 칩은 읽기를 참 빠르게 잘한대) 페이지를 쓰는 것은 복잡하고 비싸다
또한 반복적으로 프로그램과 삭제를 수행하게 되면 플래시 칩의 마모도가 높아지기 때문에 쓰기의 성능과 신뢰성이 가장 중요한 주제이다
44.4 플래시 성능과 신뢰성
플래시 칩의 읽기 지연 시간은 매우 좋은 성능을 보인다
프로그램 지연 시간은 그보다는 좀 더 느리고 종류에 따라 성능 차이가 보인다, 쓰기 성능을 높이기 위해서는 여러 플래시 칩들을 병렬적으로 활용해야 한다
삭제 명령은 수 밀리초까지 지연이 되기 때문에, 이 지연 시간을 어떻게 다루느냐가 저장 장치 설계의 중심이다
플래시 칩은 순수 반도체로 이루어져 있기 때문에 기계적인 이유 때문에 걱정할 일은 없다
가장 중요한 신뢰성 문제는 마모도 Wear Out 이다, 플래시 블럭이 삭제와 프로그램 동작을 수행할 때 약간의 잉여 전하가 누적되는 현상이 생기는데, 누적 전하량이 커지면 0 과 1 을 구분하기 힘들어지고, 구분이 불가능해지면 해당 블럭은 사용할 수 없게 된다
🐣 누적 전하량에 대해서는 SLC 이 제일 강하고, MLC TLC 은
또 다른 신뢰성 문제는 간섭 Disturbance 현상이다, 플래시에서 특정 페이지를 읽을 때 주변에 있는 페이지들의 비트를 반전시키는 경우가 발생하는데, 읽기 시에 일어나면 읽기 간섭 Read Disturbance, 프로그램 시에 일어나면 프로그램 간섭 Program Disturbance 라고 한다
44.5 플래시에서 플래시 기반 SSD 로
플래시 기반 SSD 의 일은 플래시 칩으로 이루어진 장치 위에서 동작하는 블럭 인터페이스를 제공하는 것이다
내부적으로 SSD 는 플래시 칩들로 구성되어 있고, 크기는 작지만 비영속 성질의 SRAM 과 같은 휘발성 메모리도 가지고 있다
이 메모리는 데이터의 캐싱과 버퍼링에 쓰이기도 하며, 매핑 테이블을 저장하기 위해 쓰인다
SSD 는 작업을 총괄하기 위한 제어 로직도 가지고 있다, 단순하게 표현된 구조는 아래 사진을 참고한다
이 제어 로직의 핵심적인 기능은 사용자의 읽기와 쓰기 요청에 응답하고, 그 요청을 내부적 플래시 명령들로 변환하여 요청을 처리하는 것인데 플래시 변환 계층 Flash Translation Layer, FTL 이 이 기능을 제공한다, FTL 은 논리적 블럭을 사용한 읽기와 쓰기 요청을 플래시 장치의 구성 요소인 물리적 블럭과 물리적 페이지를 다루는 저수준의 읽기, 삭제와 프로그램 명령으로 변환시킨다
또한 FTL 이 우수한 성능을 얻는 방법은 아래 기법들의 조합으로 진행된다
* 여러 플래시 칩들을 병렬로 활용
* 쓰기 증폭 Write Amplification 을 줄이기 - 쓰기 증폭 = 사용자가 SSD 에 요청한 총데이터 양 / 플래시 칩에 요청한 쓰기 데이터 양
* 마모도 평준화 Wear Leveling - 쓰기 요청을 모든 블럭에 균등하게 분산시켜 모든 블럭들이 비슷한 수준으로 마모되도록 한다
* 순차적으로 프로그램하기 - 순차적으로 프로그램하는 방식을 쓰면 간섭을 최소화할 수 있다
44.6 FTL 구성 - 잘못된 접근
가장 단순한 FTL 의 구성은 직접 매핑 Direct Mapped 이다, 직접 매핑 FTL 은 엄청난 쓰기 증폭 효과가 나타나기 때문에 아주 안 좋은 쓰기 성능을 얻고, 파일 시스템 메타데이터 또는 사용자 파일 데이터가 반복적으로 갱신된다면 빠르게 마모가 될 것이며 데이터 손실 가능성이 생긴다, 신뢰성과 성능 두 측면 모두에서 직접 매핑 FTL 은 좋지 않다
44.7 로그 기반 FTL
대부분 최신의 FTL 은 로그 기반 Log Structured 이다, 저장 장치와 그 위에서 동작하는 파일 시스템 (로그 기반 파일 시스템 Log-Structured File System) 에 유용하기 때문이다
논리적 블럭 N 을 쓰면 장치는 현재 쓰기 중인 블럭의 빈 곳에다 쓰기를 처리하고, 이 처리 방식을 쓰기 로깅 Logging 이라고 한다
블럭 N 을 읽을 수 있도록 하기 위해, 시스템의 각 논리적 블럭의 물리적 주소를 저장하는 매핑 테이블 Mapping Table 을 관리한다
로그 기반 방식은 (삭제가 가끔 발생하며, 비싼 읽기-변경-쓰기 작업을 피하기 때문에) 성능을 개선하며 신뢰성도 높여주지만
논리 블럭을 덮어 쓰는 가비지 Garbage 를 남기고, 메모리 내의 매핑 테이블의 비용이 커지는 단점이 있다
🐣 로그 기반에서는 물리 주소가 계속 갱신되니까 매핑 테이블을 갱신해줘야 한다
그리고 SSD 는 쓸 때마다 다 지우고 다시 쓰는 것이 아니고, 데이터가 있는 곳에 쓸 때 (갱신해야 할 때) 지우고 쓰는 거라서, 직접 매핑은 해당 페이지를 계속 갱신해줘야 해서 마모의 위험이 크고, 로그 기반은 새로운 페이지에 쓰면 돼서 갱신을 많이 안 해줘도 된다 🐣
여담 - FTL 매핑 정보의 영속성
장치에 전원이 나가면, 메모리 내의 매핑 테이블이 없어질까? SSD 는 매핑 정보를 복구할 수 있는 방법을 가지고 있어야 한다
가장 간단한 방법은 각 페이지를 기록할 때 범위 밖 영역 Out-of-band, OOB 라는 곳에 매핑 정보의 일부를 같이 기록한다
SSD 의 용량이 크면 필요한 맵핑 정보를 모두 탐색하기 위해 많은 시간이 걸린다, 이를 극복하기 위해 복잡한 로깅 Logging 과 체크포인트 Checkpoint 기법을 사용하여 복구 시간을 단축시킬 수 있다
44.8 가비지 회수 작업
로그 기반 접근 방식의 첫번째 비용은 가비지 회수 (죽은 블럭 회수) 작업이다
가비지 블럭 (또는 죽은 블럭) 을 찾아 회수하는 작업을 가비지 회수라고 부르며, 가비지 페이지를 포함하는 블럭을 찾고,
가비지가 아닌 살아 있는 페이지들만 읽어 들여서 이 페이지들을 로그에 쓰고, 해당 블럭을 완전히 회수하면 작업이 끝난다
가비지 회수 작업이 동작하려면 각 페이지 유효성 여부를 SSD 가 결정하기 위한 충분한 정보가 각 블럭에 있어야 하는데,
해당 블럭 어딘가에 어떤 논리적 블럭이 각 페이지에 저장되어 있는지 기록해 놓는 방식으로 해결한다
그러면 장치는 매핑 테이블을 활용하여 블럭 내의 각 페이지의 데이터를 사용할 수 있는지 판단할 수 있다
가비지 회수 작업은 살아 있는 데이터를 읽고 다시 써야 하기 때문에 비용이 비싸질 수 있는데
그런 경우 데이터 이동 (비용 비쌈) 없이 블럭을 삭제하기만 하면 즉시 새로운 데이터를 받을 수 있다
GC 비용을 줄이기 위해 용량을 과공급 Overprovision 하기도 한다,
청소 작업을 배경 작업 Background 로 미뤄서 장치가 덜 바쁠 때 처리될 수 있도록 하고,
내부 대역폭도 증가해서 사용자가 느끼는 대역폭에 영향을 끼치지 않으면서 회수 작업을 처리할 수 있다
용량을 과공급하는 것은 전체적으로 우수한 성능을 얻는데 핵심이기도 하다
44.9 매핑 테이블의 크기
로그 기반 구조의 두번째 비용은 맵핑 테이블의 크기가 매우 커질 수 있다는 것이다
4KB 페이지마다 하나의 관계 정보가 저장되기 때문에 페이지 단위 FTL 기법은 실용적이지 않다
블럭 단위 맵핑
페이지가 아닌 장치의 블럭 당 하나의 포인터를 두는 방법으로 맵핑 정보의 크기를 줄일 수 있다
블럭 단위 Block-Level FTL 은 가상 메모리 시스템에서 더 큰 페이지 크기를 갖도록 하는 것과 유사하다
하지만, 로그 기반 FTL 은 특히 "작은 크기 쓰기"가 있을 때 (물리적 블럭보다 작은 크기의 요청인 경우) 성능 상 그렇게 잘 동작하지 않는다, FTL 은 오래된 블럭에서 많은 살아 있는 데이터를 읽은 후에 새로운 블럭에 복사해야 하는데, 작은 크기인 경우 쓰기 중복 비율을 매우 높게 만들기 때문에 결과적으로 성능이 매우 떨어지게 된다
하이브리드 맵핑
하이브리드 맵핑 Hybrid Mapping 기법은 로그 블럭 Log Block 이라고 부르는 몇 개의 삭제된 블럭들을 확보해 놓고
그 블럭이 모든 쓰기를 처리하도록 하여 쓰기가 유연하면서 맵핑 비용이 적게 드는 기법이다, 로그 블럭은 페이지 단위 맵핑을 사용한다
하이브리드 FTL 은 논리적으로 로그 테이블과 데이터 테이블을 메모리에 관리한다
특정 논리적 블럭을 탐색할 때 작은 크기의 페이지 단위 맵핑으로 동작하는 로그 테이블을 먼저 검색하고
찾을 수 없다면 블럭 단위 맵핑으로 관리된 데이터 테이블을 탐색하여 위치를 찾은 후 요청 받은 데이터에 접근한다
하이브리드 맵핑 전략의 핵심은 로그 블럭의 수를 작게 유지하는 것이다
로그 블럭의 수를 작게 유지하기 위해 FTL 은 주기적으로 로그 블럭을 검사해서 하나의 블럭 포인터에 연결된 블럭으로 전환한다
블럭의 내용에 따라 아래의 세 개의 방법 중 하나로 전환이 된다
전환 병합 Switch Merge - 이전과 같은 순서대로 기록하여 로그 블럭을 정리하는 방법
이상적인 경우에는 모든 페이지 당 포인터가 하나의 블럭 포인터로 교체가 된다, 하이브리드 FTL 의 이상적인 경우이다
부분 병합 Partial Merge - 로그 블럭의 일부 페이지만 교체하는 방법
물리적 블럭의 다른 페이지들을 한 자리로 모아서 하나의 블럭 포인터로 가리킨다
완전 병합 Full Merge - 여러 블럭들에서 페이지들을 한 곳에 모은 후 오래된 것을 삭제하는 방법
완전 병합을 자주하게 되면 성능에 치명적인 영향을 끼칠 수 있다
캐시를 활용한 페이지 단위 맵핑
페이지 단위 FTL 의 메모리 부담을 줄이기 위해, 실제 사용 중인 FTL 의 부분만 메모리 캐시로 올려 놓는 방법이 있다
메모리 사용량을 크기 증가시키지 않으면서 매우 좋은 성능을 얻을 수 있지만, 맵핑 정보가 갱신이되고 플래시에 반영되지 않은 경우
또 다른 쓰기가 발생할 수 있다, 하지만 대부분의 워크로드는 지역성이 있을 것이기 때문에 캐싱을 활용한 방법이 효율적일 수 있다
44.10 마모도 평준화
마모도 평준화 Wear Leveling 을 위해 FTL 은 장치의 모든 블럭들이 균등하게 마모되도록 삭제/ 프로그램 작업을 퍼뜨려야 한다
로그 기반 방식은 초반에 쓰기 요청을 퍼뜨리는데 유용하며 가비지 회수 작업 역시 도움이 된다
하지만 좀처럼 갱신되지 않는 블럭의 데이터가 있으면 해당 블럭의 가비지는 회수되지 않고 다른 블럭만큼 쓰기 부담을 감당하지 않는다
이런 문제를 해결하기 위해 FTL 은 주기적으로 그런 블럭들의 모든 살아 있는 데이터를 읽어서 다른 위치에 쓰는 방식으로 해당 블럭이 다른 쓰기 요청을 처리할 수 있도록 만들어야 한다, 이 방법 또한 추가적인 I/O 가 발생하지만, 이를 해결하기 위한 다양한 알고리즘들이 있다 (관심이 있으면 읽어보라고 함)
44.11 SSD 의 성능과 가격
성능
보편적인 디스크 드라이브는 수백개 정도의 초당 임의 I/O 를 처리할 수 있는 반면 SSD 는 그보다 더 잘할 수 있다
SSD 는 수십에서 수백 MB/s 의 임의 I/O 성능을 갖는 반면 "고성능" 하드 디스크는 수 MB/s 가 최고 성능이었다
순차 I/O 의 성능은 그보다 더 작은 차이를 나타낸다 - 순차 성능만 필요한 경우라면 HDD 도 나쁘지 않다
SSD 의 임의 읽기 성능은 SSD 의 임의 쓰기 성능만큼 좋지는 않다
가격
SSD 가 하드 디스크에 비해 월등한 성능을 보이지만 하드 디스크를 완전히 교체하지 못 한 이유는
가격, 구체적으로는 단위 용량 당 비용 때문이다, SSD 와 하드 디스크의 가격 차이는 10배 이상의 차이가 존재한다
SSD 와 HDD 를 하이브리드 접근 방법으로, 인기 있는 "활용 빈도가 높은" 데이터는 적은 수의 SSD 에 저장하여 고성능을 얻고
나머지 가끔 쓰이는 "활용 빈도가 낮은" 데이터를 하드 디스크 드라이브에 저장하여 비용을 절감하는 방식도 가능하다
여담 - 핵심 SSD 용어들
플래시 칩 Flash Chip 은 많은 뱅크들로 이루어졌다, 각 뱅크는 삭제 블록 Erase Block, 때로는 그냥 블록 Block 이라고 불림) 으로 이루어져 있고 각 블록은 다시 몇 개의 페이지 Page 로 나뉘어 있다
* 하나의 블록의 크기는 128KB에서 2MB 사이 정도이고 많은 페이지들을 포함하고 있다
페이지는 상대적으로 크기가 작고, 1KB에서 8KB 사이의 크기를 갖는다
* 플래시에서 읽으려면 주소와 길이로 된 읽기 명령을 내려야 한다
사용자는 이 명령으로 하나 또는 그 이상의 페이지를 읽을 수 있다
* 플래시에 쓰는 것은 좀 더 복잡하다. 먼저 사용자는 쓰려는 블록 전체를 삭제해야 한다
(해당 블록에 있던 모든 정보는 모두 사라진다), 블록 내의 페이지는 정확히 한 번만 쓸 수 있고, 쓰기 명령은 완료가 된다
* 새롭게 정의된 trim 명령은 장치에 더 이상 필요 없는 블록 또는 블록들의 범위를 알려주는데 유용하다
* 플래시 신뢰성은 대체적으로 마모도 Wear out 에 의해 결정이 된다. 만약 어떤 블록이 삭제되고 프로그램되는 빈도가 잦으면 더 이상 쓸 수 없는 상태가 된다
* 플래시 기반 SSD 는 일반적인 블록 기반 읽기/쓰기 디스크처럼 동작한다, 플래시 변환 계층 Flash Translation Layer, FTL 을 활용하여 사용자의 읽기와 쓰기 요청을 플래시 칩에 적합한 읽기, 삭제, 프로그램 명령으로 변환한다
* 대부분의 FTL은 로그 기반 Log-Structured 이다, 이 구조는 삭제/프로그램 주기를 줄이는 방식으로 쓰기의 비용을 줄인다, 메모리 내에 변환 계층이 논리적 쓰기가 어느 물리적 매체상의 위치에 기록되었는지를 추적하고 있다
* 로그 기반 FTL의 핵심적 문제 중 하나는 가비지 회수 Garbage Collection 비용이다, 이는 쓰기 증폭 Write Amplification 량을 늘리는 결과를 가져온다.또 다른 문제는 맵핑 테이블의 크기이다, 이 크기는 맵핑 종류에 따라 매우 커질 수 있다
하이브리드 맵핑 Hybrid mapping 이나 FTL의 자주 활용되는 변환 정보를 캐싱 Caching 하는 것이 가능한 해법이 될 수 있다
* 마지막 문제는 마모도 평준화 Wear Leveling 이다, 모든 블록들의 삭제/프로그램 비율을 비슷한 수준으로 맞추기 위해 읽기 위주의 블록의 데이터를 읽어서 다른 블록으로 가끔씩 이주시켜줘야 한다