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 | 지연 | fanout | batch/순차 | 결과 |
|---|---|---|---|---|
| 100 | 50µs | 22.7ms | 27.0ms | batch가 +19% 느림 |
| 100 | 200µs | 43.2ms | 58.0ms | batch가 +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 SQE | 2N 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% 빠르고
iostatQD 상승 확인 시 유효
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), 순서 무관