본문으로 건너뛰기

Raw Block — Recovery Bringup Header Validation 병렬화 (NY + DG)

6/5 협의(한대규↔김나연)에서 갈라진 "bringup 시 slot header 직렬 pread 병목" 작업의 현재 코드 상태 정리. 두 브랜치가 같은 함수(_validate_loaded_entries)를 I/O 엔진별로 분담해 상보적으로 커버한다. 총괄 GitHub 이슈 초안의 근거 노트.

0. 문제 (공통 배경)

RawBlockCore 가 재시작(restart) 시 최신 checkpoint 를 로드해 in-memory index 를 복원한 뒤, 인덱스의 각 엔트리마다 slot header 를 읽어 identity/payload_len 이 metadata 와 일치하는지 검증한다 (_validate_loaded_entries). 기존 구현은:

for encoded_key, entry in items:
slot_hdr = self._read_slot_header(int(entry.offset)) # 슬롯당 동기 pread 1회
...

→ 엔트리 N개면 header 크기(4KiB)짜리 작은 동기 read N회를 직렬로 호출. 엔트리가 많아질수록(대용량 디바이스, 수십만 슬롯) bringup latency 가 선형 증가. 장치 병렬성 (NVMe queue depth)을 전혀 못 쓴다. → 6/5 한대규님이 "entry 많아졌을 때 병목" 으로 지목.

1. 두 브랜치 = 같은 함수의 엔진별 분담

02874b7(DG) 가 _validate_loaded_entries엔진별 dispatch 로 리팩터링하면서 이 함수가 두 작업의 합류 지점이 됐다:

if self.io_engine == "posix" and self._recovery_read_threads > 1 and items:
to_drop = self._validate_loaded_entries_posix_parallel(items) # DG: threadpool
elif self.io_engine == "io_uring":
to_drop = self._validate_loaded_entries_iouring_serial(items) # DG: TODO → NY 가 채움
else:
to_drop = self._validate_loaded_entries_serial(items) # fallback
영역담당브랜치메커니즘
함수 리팩터링 + dispatch + serial/fallbackDGdg-raw-block-multithreads-recovery_validate_loaded_entry() 단위 추출, engine 별 분기
POSIX 경로DGThreadPoolExecutor(8 reader), 엔트리를 range 로 쪼개 병렬 pread
io_uring 경로NYperf/validate-batched-readbatched_read 단일 제출 + wait_iouring 로 N개 header 동시 read
공유 bringup 벤치마크DGdg-…(fdaec7f)raw_block_recovery_bringup_bench.py

2. DG 커밋 분석

2.1 02874b7 — parallelize POSIX recovery header validation

core.py +120/-21, test +109, design/rst/README 문서 갱신.

핵심 변경:

  • 상수 DEFAULT_RECOVERY_READ_THREADS = 8 추가. 인스턴스에 self._recovery_read_threads 보관. (코멘트: 보수적 reader 수로 device 병렬성 노출, 높은 스레드 수에 의존 X. "io_uring recovery should use batched reads" 라고 명시 — NY 작업을 의식한 설계.)
  • _validate_loaded_entries 를 monolithic loop → engine dispatch 로 재작성.
  • 헬퍼 신설:
    • _validate_loaded_entry(item) -> str | None — 단일 엔트리 검증(stale 면 key 반환). 기존 inline 로직을 그대로 함수화 (identity/payload_len 비교, 예외 시 drop).
    • _validate_loaded_entries_serialmap 기반 직렬.
    • _validate_loaded_entries_posix_parallelmax_workers=min(8, N), _build_recovery_item_ranges 로 엔트리를 연속 range 로 분할 후 ThreadPoolExecutor.map 으로 range 별 병렬 처리.
    • _validate_loaded_entries_iouring_serial지금은 serial 로 위임 + TODO 코멘트 ("Add io_uring batch recovery here. Reuse _build_recovery_item_ranges()…"). → 정확히 NY 의 perf/validate-batched-read 가 구현하는 부분.
    • _build_recovery_item_ranges(item_count, num_ranges)(start, end) 리스트.
    • _validate_loaded_entry_range_posix(work_item) — range 내 순회하며 stale key 수집.

SLF/타입: 모두 self 내부 메서드, _-prefix 자기 멤버 접근만. 반환 타입 명시 (str | None, list[str], list[tuple[int,int]]). Any/bare generic 없음. ✅

관찰점 (이슈/PR 리뷰용):

  • _validate_loaded_entry 내부 except Exception: → drop. 기존 동작 보존이라 OK.
  • threadpool 은 read 만 병렬; index/to_drop 적용은 호출부에서 직렬(lock). 안전.
  • POSIX 만 병렬; io_uring 분기는 일부러 serial 유지(중복/충돌 회피 설계).

2.2 fdaec7f — recovery bringup benchmark

benchmarks/storage_backend_io/raw_block_recovery_bringup_bench.py (+318), README (+26).

  • 합성 fixture: 많은 indexed raw-block 엔트리로 checkpoint 를 만들고 slot header 만 기록(payload 본문은 안 씀). bringup = checkpoint load + header validation 측정용.
  • --prepare (디바이스 덮어쓰기, --i-understand-this-overwrites-device 가드) / --measure 분리. --threads 1 8 스윕, --repeats, median/mean + 1↔8 speedup 출력.
  • time_bringupraw_block_core.DEFAULT_RECOVERY_READ_THREADS 를 직접 바꿔 스레드 수별 RawBlockCore 생성 시간 측정.
  • 현재 한계: make_configio_engine="posix" 하드코딩 → POSIX 경로만 측정. io_uring(batched_read) 경로 측정 기능은 아직 없음 → NY 작업 합류 시 확장 여지.

3. NY 커밋 분석 — dc3f5d36 (perf/validate-batched-read)

core.py +55, test +148. dev 원본(monolithic loop) 위에서 작성된 상태라 DG 의 리팩터링과 같은 함수를 건드림 → 머지 시 충돌, 화해 필요(아래 §4).

  • 신설 _read_slot_headers_batched(offsets) -> list[Optional[tuple[int,int]]]:
    • O_DIRECT 정렬 만족하는 단일 연속 버퍼(n*hdr + align-1) 할당, 포인터 정렬 pad 계산.
    • batched_read(offsets, views, [hdr]*n) 단일 제출 + wait_iouring(batch_id).
    • 예외 시 전체 [None]*n 반환(보수적). _inflight_io_count 증감 + _last_io_ts 갱신.
    • 각 view 를 _decode_slot_header 로 디코드.
  • _validate_loaded_entries 진입부에서 io_engine=="io_uring" and not use_uring_cmd and len(items)>1 일 때 batched 경로, 아니면 기존 per-slot read. 이후 zip(items, hdrs) 로 동일 검증.
  • uring_cmd(/dev/ngXnY, passthrough) 경로는 제외_read_uring_cmd_buffers 가 batched_read 를 안 쓰는 별개 경로이기 때문(io_uring_post_pr3274 §참조).

4. 두 브랜치의 관계 / 머지 순서

  • 상보적: POSIX(스레드풀) + io_uring(batched_read) = bringup 검증 병렬화의 양쪽 엔진.
  • 충돌 지점: 둘 다 _validate_loaded_entries 를 수정. DG 의 dispatch 구조가 "프레임워크", NY 의 batched_read 가 DG 가 남긴 _validate_loaded_entries_iouring_serial TODO 를 채우는 구현으로 들어가는 게 자연스러운 화해 방향.
  • 권장 순서: DG dispatch 리팩터(02874b7) 먼저 머지 → NY 가 그 위에서 _validate_loaded_entries_iouring_serial_read_slot_headers_batched 로 교체. (NY 브랜치를 dispatch 구조에 rebase). 벤치(fdaec7f)는 io_uring threads 측정 확장 가능.
  • 6/5 협의의 3번(lazy/background verification)은 안 하기로 결정됨(scope 제외) — 이슈/노트에서 제외.

5. 중복 체크 (2026-06-18)

  • gh issue list --search "recovery header validation bringup"해당 이슈 없음.
  • 열린 PR 중 #3226/#3449 는 checkpoint 포맷/압축(D1/D2)으로 영역 다름. recovery header validation 병렬화 는 미등록 gap. → 총괄 이슈 신규 등록 타당.