io_uring batched padded write 후속 PR 리뷰
요약
perf/iouring-batched-padded-write는 PR #3636에서 남아 있던 regular io_uring
padded O_DIRECT write의 per-entry write_uring() fallback을 없애고,
batched_write()가 payload_lens를 받아 padded write도 batch submit으로 처리하게
하는 후속 작업이다.
초기 리뷰에서 확인한 핵심은 payload_lens API 방향은 맞지만, regular io_uring write
계열의 padding semantics가 path별로 파편화될 수 있다는 점이었다. 최종 구현 기록은
별도 문서인 io_uring_padded_write_semantics_implementation.md에 정리했다.
배경: PR #3636에서 무엇이 batch였나
PR #3636에서 NVMe passthrough만 batch였던 것은 아니다. 기준 동작은 다음과 같다.
| 경로 | PR #3636 동작 | 설명 |
|---|---|---|
| POSIX | batch 아님 | pwrite_from_buffer()를 entry별로 호출 |
| regular io_uring, non-padded | batch | payload_len == total_len이면 batched_write() 사용 |
| regular io_uring, padded O_DIRECT | batch 아님 | payload_len < total_len이면 per-entry write_uring() fallback |
NVMe passthrough (io_uring_cmd) | batch | _write_uring_cmd_buffers()가 chunk를 만들고 batched_write() 호출 |
PR #3636의 빈틈은 regular io_uring 전체가 batch가 아니었다는 점이 아니라, regular io_uring에서 padded O_DIRECT write만 batch path를 못 탔다는 점이다.
당시 batched_write() API는 다음 형태였다.
batched_write(offsets, buffers, total_lens)
이 API는 source buffer에서 total_len만큼 읽는다고 가정했다. 하지만 padded write는
payload_len과 total_len이 다르다.
payload_len = 실제 payload byte 수
total_len = O_DIRECT alignment 때문에 올림된 물리 I/O byte 수
padding = [payload_len, total_len)
O_DIRECT padding은 I/O 길이를 block size에 맞춘다는 뜻이지, 커널이 padding bytes를
자동으로 zero-fill한다는 뜻이 아니다. total_len으로 submit하면 커널은 source
pointer에서 total_len bytes를 읽는다. 따라서 userspace가 padding 영역을 명확히
준비해야 한다.
리뷰 관점의 핵심 판단
-
payload_lens추가는 필요하다.- 기존
batched_write(offsets, buffers, total_lens)는 padded write에서 source 유효 길이와 물리 I/O 길이를 구분하지 못했다. payload_lens가 있어야payload_len < total_len인 write를 out-of-bounds read 없이 batch로 표현할 수 있다.
- 기존
-
regular io_uring serial/batched path의 padding 의미를 통일해야 한다.
- serial
write_uring()은payload_len/total_len을 받을 수 있었지만,cap >= total_len이면 caller tail을 그대로 쓰는 구조였다. - batched path만 별도 zero-fill 규칙을 갖게 만들면 io_uring write semantics가 더 난잡해진다.
- serial
-
zero-copy/fixed-buffer 이점은 최대한 유지해야 한다.
payload_len < total_len이라고 항상 bounce하면 안전하지만 성능상 둔하다.- tail이 이미 zero라면 direct/fixed-buffer path를 유지하고, tail이 non-zero이거나 source가 짧거나 O_DIRECT alignment가 맞지 않을 때만 bounce하는 절충안이 낫다.
변경 전후 흐름
PR #3636 regular io_uring padded write
RawBlockCore.put_many()
-> _prepare_write_payload()
payload_len < total_len
-> _write_buffers()
can_batch = False
-> for each entry:
raw_dev.write_uring(offset, buf, payload_len, total_len)
여러 entry가 있어도 per-entry submit path로 빠졌다.
후속 PR 최종 흐름
RawBlockCore.put_many()
-> _prepare_write_payload()
payload_len, total_len 준비
-> _write_buffers()
-> raw_dev.batched_write(offsets, buffers, total_lens, payload_lens)
-> Rust common helper:
tail zero면 direct/fixed-buffer
tail non-zero/source short/unaligned이면 bounce + zero-fill
-> batch submit
리뷰에서 요구한 보완 방향
batched_write()stale-tail regression을 추가한다.- source buffer가
total_len만큼 크고 tail이 non-zero인 경우를 검증한다. - readback tail은 zero여야 한다.
- source buffer가
- serial
write_uring()에도 같은 stale-tail regression을 추가한다.- serial/batched regular io_uring write가 같은 semantics를 가져야 한다.
payload_lensvalidation regression을 추가한다.- length mismatch
payload_len > total_lencap < payload_len
io_uring_cmdpath는 변경하지 않는다.- NVMe max transfer size chunking과 command alignment 제약이 있으므로 이번 변경의 대상이 아니다.
후속 구현 문서
실제 구현 내용, regression 테스트, 검증 결과는 io_uring_padded_write_semantics_implementation.md에 정리했다.
요약하면 최종 구현은 다음 정책을 따른다.
regular io_uring write는 padding tail을 zero로 기록한다.
tail이 이미 zero면 direct/fixed-buffer path를 유지한다.
tail이 non-zero/source short/unaligned이면 bounce + zero-fill한다.
serial write_uring()과 batched_write()는 같은 helper를 공유한다.