io_uring 심화 — 비동기 IO의 혁신적 인터페이스
io_uring은 리눅스 5.1에서 도입된 비동기 I/O 인터페이스로, 시스템 콜 오버헤드를 극적으로 줄입니다. 기존 epoll, AIO의 한계를 해결하며 고성능 서버의 새로운 표준이 되고 있습니다.
기존 I/O 모델의 한계
| 모델 | 한계 |
|---|---|
| Blocking I/O | 스레드 하나가 하나의 I/O에 묶임 |
| Non-blocking + epoll | 이벤트 알림은 비동기지만 실제 I/O는 동기적 시스템 콜 |
| POSIX AIO | API가 불편하고 내부적으로 스레드 풀 사용 |
| Linux AIO | Direct I/O만 지원, 버퍼 I/O 불가 |
공통 문제: 매번 시스템 콜을 호출해야 함 → 유저-커널 전환 비용
io_uring의 핵심 아이디어
두 개의 링 버퍼 를 유저-커널 공간에서 공유하여, 시스템 콜 없이 I/O를 요청하고 완료를 확인합니다.
유저 공간 공유 메모리 커널 공간
┌──────────┐ ┌────────────────┐ ┌──────────┐
│ │ │ SQ (제출 큐) │ │ │
│ 애플리케이션│────→│ 요청1, 요청2... │────→│ 커널이 │
│ │ │ │ │ 처리 │
│ │ ├────────────────┤ │ │
│ │←────│ CQ (완료 큐) │←────│ 결과를 │
│ │ │ 결과1, 결과2... │ │ 넣음 │
└──────────┘ └────────────────┘ └──────────┘
시스템 콜 없이 SQ에 요청을 넣고, CQ에서 결과를 읽음!
두 개의 링 버퍼
- SQ (Submission Queue): 애플리케이션이 I/O 요청을 넣는 큐
- CQ (Completion Queue): 커널이 완료된 결과를 넣는 큐
두 큐 모두 mmap으로 유저-커널 공유 → 데이터 복사 없음
동작 흐름
1. io_uring_setup(): 링 버퍼 초기화 (한 번만)
2. 요청 제출:
SQE(Submission Queue Entry)를 SQ에 추가
→ 시스템 콜 불필요 (메모리 쓰기만)
3. 커널에 알림 (선택적):
io_uring_enter() 또는 SQPOLL 모드에서는 자동
4. 완료 확인:
CQ에서 CQE(Completion Queue Entry) 읽기
→ 시스템 콜 불필요 (메모리 읽기만)
SQPOLL 모드
커널 스레드가 SQ를 지속적으로 폴링합니다. 시스템 콜이 완전히 제거 됩니다.
일반 모드: 앱 → SQ 쓰기 → io_uring_enter() → 커널 처리
SQPOLL 모드: 앱 → SQ 쓰기 → 커널 스레드가 자동 감지 → 처리
시스템 콜 제로!
코드 예시
#include <liburing.h>
struct io_uring ring;
// 초기화
io_uring_queue_init(256, &ring, 0); // 큐 깊이 256
// 읽기 요청 제출
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, buf_len, offset);
io_uring_sqe_set_data(sqe, user_data); // 사용자 데이터 연결
io_uring_submit(&ring);
// 완료 대기
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
int result = cqe->res; // 읽은 바이트 수
io_uring_cqe_seen(&ring, cqe);
// 정리
io_uring_queue_exit(&ring);
io_uring vs epoll
| 항목 | epoll | io_uring |
|---|---|---|
| I/O 제출 | 시스템 콜 (read/write) | 링 버퍼에 쓰기 |
| 완료 확인 | 시스템 콜 (epoll_wait) | 링 버퍼에서 읽기 |
| 시스템 콜 수 | 많음 | 최소 (SQPOLL이면 0) |
| 지원 I/O 유형 | 소켓 중심 | 파일, 소켓, 타이머 등 모두 |
| 배치 처리 | 제한적 | 여러 I/O를 한 번에 제출 |
활용 사례
- 고성능 스토리지: RocksDB, ScyllaDB 등이 io_uring 도입으로 I/O 처리량 향상
- 웹 서버: 차세대 웹 서버들이 io_uring 기반으로 전환 중
- 데이터베이스: PostgreSQL이 io_uring 지원 작업 진행
핵심 포인트
- io_uring이 빠른 이유: 공유 링 버퍼로 시스템 콜 오버헤드 제거, 배치 처리
- epoll과의 차이: epoll은 이벤트 알림만 비동기, 실제 I/O는 동기 시스템 콜 / io_uring은 I/O 제출도 비동기
- SQPOLL 모드: 커널 스레드 폴링으로 시스템 콜 완전 제거 → CPU를 소비하지만 최저 지연
정리
io_uring은 리눅스 비동기 I/O의 게임 체인저입니다. 공유 링 버퍼로 시스템 콜 오버헤드를 제거하고, 배치 처리와 SQPOLL로 극한의 성능을 제공합니다. 고성능 서버 개발자라면 반드시 알아야 할 기술입니다.