④ O_DIRECT padded write — codex 방식 (fallback 명시화 + Rust 후속)
Context
PR #3636 리뷰의 ④: io_uring batch 경로에서 패딩이 필요한 O_DIRECT write가 진짜 batched로
처리되지 않는다. 근본 원인은 Rust batched_write(offsets, buffers, total_lens)가 payload_len을
받지 않고 각 버퍼에서 total_len을 읽으므로, payload가 미정렬이라 패딩되는 경우(payload_len < total_len)를
batched로 안전히 다룰 수 없다는 점.
현재 동작은 이미 안전하다: _write_buffers()는 can_batch = all(payload_len == total_len)일 때만
batched_write()를 호출하고, 패딩이 필요한 O_DIRECT write는 payload_len < total_len이라 per-entry
write_uring()으로 폴백된다. write_uring()은 Rust에 payload_len/total_len을 모두 넘겨 패딩을
bounce 버퍼로 안전 처리한다.
전략(codex 방식, 사용자 확정): can_batch 동작은 바꾸지 않는다. 이번 PR(#3636)에서는 이 폴백이
의도된 안전 동작임을 코드 주석과 테스트로 명시한다. 진짜 batched padded 지원(Rust API 변경)은
별도 후속 PR로 분리한다.
기준 노트: private/work/raw_block/io_uring/iouring-batch-follow-up-codex.md.
Part A — 이번 PR(#3636)에 추가
대상 파일:
lmcache/v1/storage_backend/raw_block/core.py—_write_buffers()주석만 (동작 변경 없음)tests/v1/storage_backend/test_raw_block_core.py— O_DIRECT padded fallback 테스트
A-1. 주석 추가 (_write_buffers can_batch 근처)
batched_write()는 현재total_lens만 받는다는 점.- O_DIRECT padded write는
payload_len < total_len이 되어 batched로 다룰 수 없으므로,payload_len/total_len을 분리 전달하는 per-entrywrite_uring()폴백을 의도적으로 쓴다는 점. - 동작 코드는 손대지 않음 (
can_batch = all(payload_len == total_len)유지).
A-2. 유닛 테스트 (TDD)
기존 헬퍼 재사용: _FakeRawDevice(이미 batched_write_calls/write_uring_count 보유),
_make_core_with_fake. 단 _make_core_with_fake에 use_odirect 오버라이드 인자 추가
(현재 io_engine/load_checkpoint/capacity만 override → use_odirect=False 기본 파라미터 추가해
overrides에 반영).
테스트 test_raw_block_core_io_uring_put_many_odirect_padded_falls_back:
_make_core_with_fake(path, fake, io_engine="io_uring", use_odirect=True).- N>1 키, block_align(4096) 비배수 payload(예: 1000·1500 bytes) →
_prepare_write_payload가total_len=round_up(payload_len) > payload_len생성(enable_zero_copy=False라 direct view None). put_many()호출.- 기대:
result.results == [True]*N(성공)fake.batched_write_calls == [](batched 미사용)fake.write_uring_count == 2*N(키마다 header+payload per-entry 폴백)- round-trip:
load_many_into로 원본 payload 복원 확인
- TDD 절차상 이 테스트는 현재 코드에서 이미 통과(폴백이 이미 동작) → 회귀/명시화 테스트 성격. 먼저 작성해 현 동작을 고정한 뒤 주석을 단다.
A-3. (선택) 리뷰어 응답
codex 노트 하단 초안 기반으로 ④ 스레드에 답글: "안전 폴백은 이미 존재 / 이 PR에 주석+테스트로 명시 / Rust batched_write API 확장은 후속 PR로" — 게시는 사용자 승인 후.
A-4. 반영/검증
- 테스트 클론(
/home/ny/LMCache, Rust 확장 OK)에서 detached checkout 후pytest -xvs tests/v1/storage_backend/test_raw_block_core.py -k odirect_padded통과 → 파일 전체 통과. - workspace(
/home/ny/workspace/LMCache)에서pre-commit run --files <두 파일>. - amend + force-push (단일 커밋 유지): 커밋 메시지에 "make O_DIRECT padded write_uring fallback explicit (comment + test)" 취지 추가, Signed-off-by 유지. push는 사용자 승인 후.
Part B — 후속 Rust PR (별도, 이번 PR 아님)
진짜 batched padded 지원. 대상: rust/raw_block/src/lib.rs + core.py call site + 테스트.
batched_write시그니처 확장:(offsets, buffers, total_lens)→(offsets, buffers, payload_lens, total_lens).- Rust validation: vector 길이 동일 /
payload_len <= total_len/ buffer cap >= payload_len / O_DIRECT에서 offset·total_len 정렬. - Rust 내부 패딩 처리(
write_uring미러링): aligned & cap>=total_len이면 직접 submit, 아니면 aligned bounce(AlignedBuf)에payload_len복사 +total_len-payload_lenzero-fill. - Python call site:
_write_buffers에서payload_lens도 batched_write에 전달하고 can_batch 게이트를 완화 → padded O_DIRECT도 폴백 없이 batched. - 테스트: Rust 단위 + RawBlockCore에서 padded O_DIRECT
put_many가 batched_write 사용 검증 + round-trip + 패딩 영역 안전성.
순서 제약(안전): 3(Rust bounce/패딩)이 먼저 들어간 뒤에야 4(Python 게이트 완화)를 한다.
역순이면 batched_write가 short 버퍼에서 total_len을 읽어 OOB read. codex 노트는 단계 순서로만
암묵적 안전 — 이 의존성을 명시 커밋/PR 본문에 적는다.
조정 필요(별 후속과 충돌 가능):
iouring-batch-coalesce-writev-plan.md(write coalescing Tier 1)도 같은batched_write에 벡터드(iovec 그룹/opcode::Writev) 확장을 추가한다. 본 Part B(payload_lens추가)와 목적이 다르나 시그니처가 겹치므로, 둘이 별 PR로 가더라도 통합 시그니처(batched_writev(offsets, buffer_groups, payload_lens, total_lens)등)를 함께 설계할 것.
검증 요약 (Part A)
| 단계 | 명령 | 기대 |
|---|---|---|
| A-2 후 | pytest -k odirect_padded | 통과 (batched 0, write_uring 2N, round-trip OK) |
| A-2 후 | pytest tests/v1/storage_backend/test_raw_block_core.py | 전부 통과 |
| A-4 | pre-commit run --files ... | 통과 |
마무리
- 테스트 클론
git checkout p0-verify복원. private/작업 후:CHANGELOG.md[mod]기록 +tasks/index.md동기화(codex 노트 행에 PR 진행 반영).
Out of scope
can_batch동작 변경(= 이전에 검토한 Python Layer 1 완화안). codex 방식 채택으로 제외._read_buffers게이트, #3636 orphan-write 항목(별도 follow-up 후보).