O_DIRECT io_uring에서 32페이지 리드어헤드가 장치 요청을 19.6만 건에서 1.6만 건으로 줄인 이유
- Turso의 TPC-H Q6 스캔에서 32페이지 리드어헤드를 켜자 SQE 수는 195,207건에서 218,212건으로 늘었지만 장치 요청은 약 196,000건에서 약 16,300건으로 감소함
- 리드어헤드를 끄면 io_uring이 페이지 하나를 읽고 완료를 기다린 뒤 다음 요청을 내지만, 켜면 페이지 100부터 131까지 32개 읽기를 동시에 제출해 블록 계층이 인접 요청을 합칠 수 있게 됨
- 리드어헤드 사용 시 bio 218,493건 중 202,539건이 병합됐고
%rrqm은 91-93%, 장치의 평균 읽기 요청 크기rareq-sz는 56.53KiB였음. 미사용 시 병합 bio는 195,516건 중 140건, 평균 크기는 4.37KiB였음 - SQ polling을 켠 io_uring Q6는 중앙값 기준 벽시계 시간 8.22초, 시스템 시간 8.46초였고 커널 폴링 스레드
io_sq_thread가 CPU 사이클의 65%를 차지함. 폴링을 끄면 벽시계 시간은 8.62초로 늘었지만 시스템 시간은 1.27초로 감소함 - SQ polling을 끈 Q6에서 io_uring은 8.55초, syscall
pread(2)백엔드는 3.02초였음. perf 측정에서 io_uring은 명령어 수가 0.2B만 더 많았지만 캐시 미스는 10.666M으로 syscall의 5.889M보다 4.8M 많았음
Hacker News opinions
연속 리드어헤드라면 왜 preadv를 안 쓰는지 궁금함. Marginalia 인덱스에서는 한 번의 preadv 호출로 전체 읽기가 가능한 경우 preadv가 io_uring보다 항상 빨랐음.
preadv에는 페이지 캐시에 이미 있는 데이터만 읽으라고 지시하는 기능이 없음. 그리고 io_uring과 preadv는 대립 관계가 아니라, io_uring에 preadv 연산을 넣어 여러 read OP 대신 쓸 수도 있음. 글은 O_DIRECT를 쓸 때 필요한 애플리케이션 리드어헤드를 설명한 것임.
mmap을 쓰고 uring에 그 메모리에서 읽으라고 했더니 다른 방법보다 빨랐음.
왜 syscall과 O_DIRECT io_uring만 비교하는지 모르겠음. Turso는 SQLite처럼 여러 프로세스가 같은 DB에 접근할 수 있으니, 일부 워크로드에서는 버퍼링이 크게 이득일 텐데 O_DIRECT 없이 io_uring을 쓰는 선택지도 있고 RWF_DONTCACHE 같은 중간 선택지도 있음.
이런 벤치마크는 DB가 하드웨어 전체를 사실상 독점하는 경우에만 올바른 접근이라 봄. 임베디드 DB는 다른 작업과 CPU·메모리를 나눠 쓰는 일이 많고, 커널 페이지 캐시를 직접 관리로 대체하면 메모리 압박 때 시스템 전체 상태를 고려하기 어려움.
그럼 이런 경우 올바른 벤치마크와 프로파일링 방법은 뭔지 궁금함. Vinyl 캐시 병목을 찾으려고 측정해 봤는데, 원인을 제대로 분리하면서 재현 가능한 벤치마크를 만드는 일이 생각보다 어렵더라.