본문으로 건너뛰기

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 SQE32바이트 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+ 권장.


도구 / 라이브러리

도구설명
liburingC 라이브러리. 가장 일반적.
io_uring-rsRust 바인딩
xNVMeFDP-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_containsSQ에 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 실제 구현 분석