io_uring — Linux의 진짜 비동기 I/O
[!tldr] 업무 관점 takeaway [[LMCache-Local-Disk-Backend|LMCache]]의 스레드 풀 기반 가짜-async I/O를 진짜 async로 교체할 수단. 그리고 [[NVMe-FDP|FDP placement hint를 NVMe에 직접 전달]]하는 정석 경로 (io_uring_cmd). 두 기여 포인트를 한 번에 푼다 — 이게 우리 업무에 io_uring이 결정적인 이유. 단순한 성능 도구가 아니라 FDP 도입의 전제 조건.
한 줄 정의
Linux 커널의 비동기 I/O 인터페이스. 제출 큐(SQ)와 완료 큐(CQ)를 user/kernel이 공유 메모리로 주고받아 syscall 최소화.
User space Kernel space
┌─────────────┐ ┌─────────────┐
│ Submission │ ──────► │ NVMe driver │
│ Queue (SQ) │ │ I/O dispatch│
└─────────────┘ └──────┬──────┘
┌─────────────┐ │
│ Completion │ ◄──────────────┘
│ Queue (CQ) │
└─────────────┘
기존 방식과 차이
| 기존 (sync + thread pool) | io_uring | |
|---|---|---|
| Syscall 횟수 | I/O마다 1번씩 | batch (io_uring_enter 1번에 N개) |
| Thread context switch | 워커 스레드마다 발생 | 없음 |
| 실제 비동기 여부 | 흉내 (스레드 풀) | 진짜 |
| FDP hint 전달 | 불가능/제한적 | io_uring_cmd로 가능 |
LMCache 적용 시 효과
[[LMCache-Local-Disk-Backend|현재 LocalDiskBackend]]:
LocalDiskWorker (priority queue)
├── Thread 1 ─── write() ← sync syscall
├── Thread 2 ─── write()
├── Thread 3 ─── read()
└── Thread 4 ─── read()
io_uring으로 교체:
io_uring instance
├── SQ → write/read/cmd 명령들을 batch 제출
└── CQ → 완료 이벤트만 수신
(스레드 풀 자체 불필요)
기대 효과:
- 스레드 컨텍스트 스위치 ↓
- syscall 횟수 ↓
- 진짜 async I/O
- FDP placement hint 전달 경로 확보
io_uring_cmd — NVMe passthru
io_uring_cmd는 단순 read/write를 넘어서 임의 device command를 보낼 수 있다. NVMe의 경우:
- FDP write 명령에 placement identifier (RUH) 포함
- TRIM / Dataset Management
- 모든 NVMe admin command
→ FS/Block layer 거치지 않고 NVMe driver에 직결 ([[Storage-Stack|경로 B]]).
LMCache → io_uring_cmd → NVMe Driver → FDP SSD
↑
FS/Block layer 우회
RUH 힌트 직접 전달
big SQE / big CQE 구조
일반 io_uring의 SQE는 64바이트인데, NVMe 명령 자체가 64바이트라 공간이 부족하다.
그래서 io_uring_cmd는 128바이트 big SQE와 32바이트 big CQE를 사용한다 (커널 5.19+).
big SQE (128바이트):
┌────────────────────┐
│ io_uring 메타 │ 48바이트 (opcode, fd, flags …)
├────────────────────┤
│ NVMe 명령 embed │ 80바이트 ← Read/Write/FDP-Write 등 직접 삽입
└────────────────────┘
big CQE (32바이트):
┌────────────────────┐
│ 표준 CQE 결과 │ 16바이트 (result, flags)
├────────────────────┤
│ 확장 필드 │ 16바이트
└────────────────────┘
기존 io_uring: SQE 64B / CQE 16B
io_uring_cmd: SQE 128B / CQE 32B (2배)
디바이스 파일 차이
| 경로 | 디바이스 | 특징 |
|---|---|---|
| 일반 io_uring | /dev/nvme0n1 | 블록 디바이스, Block Layer 거침 |
| io_uring_cmd | /dev/ng0n1 | 문자 디바이스 (ng = NVMe Generic), Block Layer 우회 |
ng* prefix가 없으면 io_uring_cmd 사용 불가.
버퍼 정렬 요구사항
io_uring_cmd는 DMA 전송을 사용하므로 버퍼 주소가 페이지(4096B) 단위 정렬되어 있어야 한다.
정렬되지 않은 버퍼를 넘기면 하드웨어가 처리 거부.
max_data_transfer_size와 I/O 분할
NVMe 컨트롤러마다 단일 요청으로 전송 가능한 최대 크기가 다르다.
이 한계는 /sys/block/nvme0n1/queue/max_hw_sectors_kb 에서 읽을 수 있다.
이 값을 초과하는 I/O는 자동으로 여러 요청으로 분할해서 순차 제출해야 한다.
Fixed Buffer (미구현 — 다음 단계)
현재는 user space 버퍼 → 커널 복사가 여전히 발생한다.
io_uring의 fixed buffer 등록(io_uring_register)을 쓰면 이 복사를 없애 true zero-copy에 근접할 수 있다.
현재: App 버퍼 → (복사) → 커널 → DMA → SSD
목표: App 버퍼 ──────────────── DMA → SSD (zero-copy)
커널 버전 요구사항
| 기능 | 커널 |
|---|---|
| io_uring 기본 | 5.1+ |
| I/O Passthru (io_uring_cmd) | 5.13+ |
| FDP 안정화 | 5.19+ |
→ 우리 검증 환경은 5.19+ 권장.
도구 / 라이브러리
| 도구 | 설명 |
|---|---|
liburing | C 라이브러리. 가장 일반적. |
io_uring-rs | Rust 바인딩 |
xNVMe | FDP-aware 추상화 라이브러리 |
fio (ioengine=io_uring / io_uring_cmd) | 벤치마크/측정 |
SPDK (FDP v23.05+) | 커널 우회까지 가는 user-space NVMe |
LMCache 도입 시 고려할 인터페이스
[[LMCache-Async-Loading|Async Loading]]이 요구하는 메서드들과 매핑:
| LMCache 메서드 | io_uring 매핑 |
|---|---|
batched_async_contains | SQ에 stat/lookup 일괄 제출 |
put() (async write) | io_uring_prep_write + (FDP면) io_uring_cmd |
get() (blocking → async) | io_uring_prep_read |
prefetch() | io_uring_prep_read (high priority) |
의외의 연결
[!note] io_uring 도입 = [[기여-포인트-맵|기여 포인트 [2][8]]] 동시 달성 [2] LMCache async I/O 교체 + [8] Block layer 우회. 한 번의 변경으로 두 마리 토끼.
[!note] io_uring 도입 = FDP Backend의 전제 FDP placement hint를 안정적으로 전달하려면 io_uring_cmd 경로가 표준. 즉 io_uring 없이 FDP Backend를 만들면 우회로(예: 파일 단위 ioctl)를 써야 해서 지저분해진다. [1]·[2]는 함께 진행되는 게 자연스럽다.
[!note] LocalDiskWorker priority queue ↔ io_uring SQ priority LMCache가 우선순위 큐(PREFETCH > DELETE > PUT)를 운영하는데, io_uring도 SQE에 priority 필드가 있음 → 그대로 매핑 가능.
관련 페이지
- [[LMCache-Local-Disk-Backend]] — io_uring으로 갈아엎을 대상 코드
- [[LMCache-Async-Loading]] — io_uring과 만나는 상위 추상화
- [[NVMe-FDP]] — io_uring_cmd로 전달하는 hint
- [[O_DIRECT]] — io_uring과 자주 함께 쓰는 옵션 (page cache 우회)
- [[Storage-Stack]] — io_uring이 layer를 건너뛰는 방식
- [[기여-포인트-맵]] — [2][8] 포인트
- [[raw_block-io_uring-cmd]] — PR #3274 io_uring_cmd NVMe passthrough 실제 구현 분석