NVMe FDP (Flexible Data Placement)
[!tldr] 업무 관점 takeaway 우리 미션의 핵심 HW 기술. Host가 SSD에 write 명령을 보낼 때 "이 데이터는 RUH #X에 놔라"는 힌트를 추가하는 NVMe 표준 (TP4146, 2022). 같은 RUH끼리 같은 RU(Reclaim Unit)에 모여 lifetime이 정렬 → [[Garbage-Collection|GC]] 복사 최소화 → [[WAF]] ~1.0. 우리는 [[LMCache-개요|LMCache]]의 KV Cache lifetime을 RUH에 매핑하는 [[기여-포인트-맵|Backend [1]·[9]]]를 만든다.
한 줄 정의
NVMe의 데이터 배치 힌트 표준. Host가 write 시 placement identifier를 함께 보내면 SSD가 같은 식별자끼리 같은 [[NAND-Flash-기초|NAND]] RU에 배치 → 수명 분리로 GC 비용 절감.
4가지 핵심 개념
| 개념 | 영문 | 설명 |
|---|---|---|
| Reclaim Unit (RU) | Reclaim Unit | SSD 내부 "데이터 그룹" 단위 = 기존 Superblock. GC가 RU 단위로 발생 |
| Reclaim Group (RG) | Reclaim Group | RU들의 모음. NAND Die 기반으로 묶임 |
| Reclaim Unit Handle (RUH) | RUH | Host가 사용하는 "라벨" / 포인터. "이 데이터는 RUH #2"라고 힌트 |
| Placement Handle (PH) | PH | Host write 명령 시의 인덱스. 관계: PH → RUH → RU |
추가: Endurance Group (EG) — 수명 함께 관리되는 NAND 블록 집합. 보통 SSD 전체.
기존 SSD vs FDP SSD
기존 (CNS — Conventional Namespace SSD)
Host → LBA만 제공
↓
SSD 내부: Append Point 1개
↓
Superblock 내부:
[A(짧)][B(김)][C(짧)][D(김)][E(짧)] (섞임)
짧은 게 지워지면 → 빈칸이 군데군데 → GC 시 긴 것까지 복사.
FDP SSD
Host → LBA + RUH 힌트 제공
↓
SSD 내부: RUH 수만큼 Append Point
↓
RU #0 (RUH #0): [A(짧)][C(짧)][E(짧)] (수명 짧음만)
RU #1 (RUH #1): [B(김)][D(김)] (수명 김만)
RU #0이 모두 지워지면 → valid copy 0 → 그냥 erase → WAF ~1.0.
자세한 Append Point 그림: [[NAND-Flash-기초]].
FDP의 결정적 특성
| 특성 | 함의 |
|---|---|
| 하위 호환 | FDP 모르는 앱도 동작. 아는 앱만 추가 혜택 |
| Read/TRIM 변경 없음 | Write 명령에만 placement handle 추가 |
| WAF 보장 가능 | 잘 매핑하면 ~1.0 달성 가능 ([[CacheLib-FDP-사례 |
Linux Kernel 지원
| 구성요소 | 상태 |
|---|---|
| Linux Kernel | 5.13+ I/O Passthru, 5.19+ 안정 |
| xNVMe | v0.7+ Full Support |
| QEMU | v8.0+ FDP Emulation (WAF 시뮬레이션 X, 기능 검증용) |
| nvme-cli | Upstream (FDP 명령/로그 페이지) |
| Fio | RU/RUH 지원, io_uring trim 개선 중 |
| SPDK | v23.05+ |
| CacheLib | Ongoing (Meta) |
FDP hint 전달 경로 2가지
경로 A — I/O Passthru (저지연)
App → io_uring_cmd → NVMe Driver → FDP SSD
파일시스템 우회, NVMe 디바이스 직접 접근
경로 B — File System / Block IO
App → VFS → FS → Block Layer → NVMe → FDP SSD
─ Application-driven: 앱이 힌트 부여 (LMCache에 적합)
─ Filesystem-driven: FS가 자동 분리 (예: f2fs)
LMCache 관점에서는 앱이 KV 수명을 가장 잘 안다 → Application-driven 경로 + io_uring_cmd 조합이 정답. ([[io_uring]], [[기여-포인트-맵|기여 포인트 [2][8]]])
FIO로 효과 측정
ioengine=io_uring_cmd
fdp=1
fdp_pli=<placement identifier list>
WAF 측정: [[WAF#측정-방법|WAF 페이지의 nvme fdp stats]] 참조.
QEMU 에뮬레이션 (실제 SSD 없이 실험 가능)
-device "nvme-subsys,id=nvme-subsys0,fdp=on,fdp.runs=96M,fdp.nrg=1,fdp.nruh=16"
-device "nvme,id=nvme0,serial=deadbeef,subsys=nvme-subsys0"
-drive "id=fdp-1,file=data.img,format=raw,if=none"
-device "nvme-ns,id=fdp-1,drive=fdp-1,nsid=1,fdp.ruhs=1-15"
fdp.runs— RU 크기fdp.nrg— Reclaim Group 수fdp.nruh— RUH 수
⚠️ 기능 검증만 가능, WAF는 시뮬레이션 안 됨 (실 SSD 필요).
LMCache RUH 매핑 전략 (설계 후보)
KV Cache 종류 → RUH 매핑
─────────────────────────────────────────
Prefix Cache (long-lived) → RUH #0 (long bucket)
중간 컨텍스트 (medium) → RUH #1
Single-query KV (short-lived) → RUH #2 (short bucket)
임시 버퍼 → RUH #3 (very short)
매핑 기준 후보:
- 접근 빈도 (LFU bucket)
- 생성 시간 (FIFO bucket)
- 세션 / 모델 단위
- 사용자 명시 (pin 여부 등)
[[LMCache-Local-Disk-Backend|LRU 정책]]이 이미 lifetime bucket을 만들어주고 있으므로, 그걸 그대로 RUH ID로 사용하는 게 자연스럽다.
의외의 연결
[!note] FDP RUH = lifetime-aware allocator 결국 FDP는 NAND 안에서 동작하는 lifetime-aware free space allocator다. [[KV-Cache]] 워크로드는 lifetime이 명백히 나뉘므로 ideal workload. "풀고 싶은 문제와 푸는 도구가 너무 잘 맞는 케이스".
[!note] FDP는 ZNS의 실패에서 배웠다 [[데이터-배치-기술-역사]] 참조. ZNS는 좋았지만 호환성 깨서 죽음. FDP는 "hint만 추가, 안 쓰면 평소대로" 라는 하위 호환을 지켜서 살아남음.
관련 페이지
- [[NAND-Flash-기초]] — RU/Append Point가 풀고자 하는 NAND 제약
- [[WAF]] — FDP가 줄이는 핵심 지표
- [[Garbage-Collection]] — FDP가 줄이는 메커니즘
- [[HC-SSD]] — FDP 효과가 더 큰 미디어
- [[CacheLib-FDP-사례]] — 실측 (WAF 3.5 → 1.0)
- [[데이터-배치-기술-역사]] — Streams/ZNS와의 비교
- [[io_uring]] — FDP hint 전달의 정석 경로
- [[기여-포인트-맵]] — [1][9][10][11] 우리 작업 영역