본문으로 건너뛰기

raw_block put_many 쓰기 경로 최적화 (P0)

[!tldr] 업무 관점 takeaway put_many가 키마다 개별 I/O 제출하던 것을, io_uring 환경에서 2N개 버퍼를 한 번의 batched_write로 제출하는 _put_many_batch_io로 바꿨다. NVMe NCQ가 N건을 병렬 처리할 수 있게 된다. 시뮬레이션 기준 N=100·50µs에서 fanout 대비 −28%. 단, 시뮬레이션이 NCQ 병렬성을 모델 가정으로 깔아 실 NVMe 측정이 전제다. 현재 브랜치 perf/rawblock-put-many-batch-io에 구현 완료.

항목
브랜치perf/rawblock-put-many-batch-io
영역core.py (perf), plugins/rust_raw_block_backend.py (correctness fix)
분기 조건io_engine == "io_uring" AND len(keys) > 1
상태구현 완료, 실 NVMe 실측 미검증
선행 조건#3274 머지 (현재 APPROVED+MERGEABLE)

1. 문제 — dispatch만 묶어선 부족하다

put_many는 키마다 ① 락(슬롯 할당) → ② write_one(I/O) → ③ 락(commit)을 직렬 반복한다. write_one은 [[raw_block-io_uring-인프라]] §3의 이유로 can_batch=False → io_uring에서도 키당 write_uring 2회 + 완료 대기.

호출부(plugin)에서 N개 키를 하나의 task로 묶어도(L2 개선), put_many 내부가 직렬이면 device 큐는 여전히 2개짜리.

측정 결과: dispatch만 묶으면 오히려 느려진다.

N지연fanoutbatch/순차결과
10050µs22.7ms27.0msbatch가 +19% 느림
100200µs43.2ms58.0msbatch가 +34% 느림

dispatch 절약보다 thread pool 병렬성 손실이 크다. put_many 내부의 직렬 I/O 자체를 병렬로 바꿔야 한다.


2. 해결 — _put_many_batch_io (4단계)

def put_many(self, keys, objs):
if self.io_engine == "io_uring" and len(keys) > 1:
return self._put_many_batch_io(keys, objs)
# 기존 순차 루프 — 변경 없음
Phase A (락 1회) N개 키 dedup → N개 슬롯 할당 → inflight N개 등록
Phase B+C 헤더+페이로드 × N = 2N개 버퍼 리스트 구성
→ _write_buffers(2N) 1회 → batched_write(2N) → 1번의 io_uring_submit
Phase D (락 1회) 기록된 키 index에 일괄 commit, inflight 정리

기존 순차 대비:

기존_put_many_batch_io
슬롯 할당 락N회1회
_write_buffers 호출N회1회
io_uring 제출키마다 2 SQE2N SQE 한 번에
commit 락N회1회

실패는 all-or-nothing: _write_buffers 예외 시 모든 슬롯 반환, commit 없음.


3. 검증

시뮬레이션 결과

obj=64B (음수 = batch+io_uring이 fanout보다 빠름)
N 지연 fanout batch+io_uring vs fanout
100 50µs 22.67ms 16.24ms -28.4% ✓ (NVMe 현실 구간)
100 200µs 43.20ms 31.16ms -27.9% ✓
1000 50µs 321.6 ms 164.1 ms -49.0% ✓
1000 200µs 232.5 ms 309.6 ms +33.2% (Phase A 락 병목)

N=1000·200µs에서 역전: Phase A의 단일 락 구간에서 1000개 슬롯 순차 할당이 병목.

⚠️ 시뮬레이션 한계

fake device의 batched_write가 "N건을 한 번의 지연으로 완료" = 완벽 병렬 가정. NCQ 병렬성을 결론으로 전제하고 측정한 것이라 실 NVMe 검증 없이는 미확정.

실측 계획

  • 실 NVMe + io_uring 5.19+ 커널 필요
  • 판정: N=100에서 순차 대비 >10% 빠르고 iostat QD 상승 확인 시 유효

correctness 테스트 (3개 통과)

  • round-trip: N=10 write → load 일치
  • 중복 키 skip: 5중복+5신규 → 5신규만 저장
  • 쓰기 실패 롤백: 예외 시 모든 슬롯 반환, inflight 비움

4. 함께 수정한 correctness 버그 (L2, 별도 브랜치)

batched_submit_put_task(plugin 호출부)의 두 버그는 fix/rawblock-batched-put-toctou(L2 브랜치)에 분리:

dedup TOCTOU 경합: 키 중복 필터링을 락 밖에서 → 다른 스레드가 사이에 commit 가능. 락 안으로 이동.

dispatch 실패 시 자원 누수: ref count 올린 뒤 task 생성 실패 시 키가 in-flight 집합에 영구 잔류. try/except로 롤백.


참고

  • 전제 인프라: [[raw_block-io_uring-인프라]]
  • 관련 task: [[raw_block-개선-Task]] §P0
  • L2(correctness fix)와 독립 PR — 파일 분리(core vs plugin), 순서 무관