Code Review: priv/sy/feat/raw-block-blkdiscard — BLKDISCARD on init
브랜치: bitbucket/priv/sy/feat/raw-block-blkdiscard
develop 기준 커밋 3개:
5c2f3199— [RawBlock] Add BLKDISCARD support to Rust RawBlockDevicef30249f9— [RawBlock] Add blkdiscard_on_init support to Python adapter and core4b176cfa— [RawBlock] Handle BLKDISCARD range limits in Rust
작성자: Sangyoon Kwon syk0905.kwon@samsung.com 일자: 2026-06-01
배경: SSD TRIM이란?
SSD는 쓰기/읽기 단위(page, ~4KB)와 지우기 단위(block, 256KB1MB)가 다릅니다.
기존 데이터가 있는 page를 직접 덮어쓸 수 없고, 반드시 block 전체를 erase한 뒤 써야 합니다.
이를 피하기 위해 SSD는 새 데이터를 빈 page에 쓰고 기존 page를 invalid로 표시합니다.
LMCache처럼 데이터를 자주 썼다 지우면 invalid page가 쌓이고, SSD 컨트롤러가 GC(Garbage Collection)를 돌려 valid page를 다른 block으로 옮긴 뒤(copy) 빈 block을 확보합니다. 이 GC가 백그라운드에서 돌 때 write 성능이 떨어지는 상태를 "dirty SSD"라고 합니다.
TRIM(BLKDISCARD) 은 OS가 SSD에 "이 범위는 더 이상 안 쓴다"고 알려주는 힌트입니다. SSD 컨트롤러는 해당 page들을 즉시 invalid로 표시하고, GC가 copy 없이 바로 erase할 수 있게 됩니다.
- 수명(wearout): 불필요한 write/erase를 줄여 P/E cycle(SSD 수명 단위)을 아낍니다.
- write 성능: TRIM 후 SSD가 clean 상태(빈 page 충분)가 되므로, 이후 write 시 GC 없이 바로 빈 page에 쓸 수 있어 write latency가 낮아집니다.
- 물리적 zeroing 없이: 직접 0으로 덮어쓰는 대신 invalid 표시만 하므로 실제 write 횟수 자체가 줄어듭니다.
변경 요약
RustRawBlockBackend / RawBlockCore / RawBlockL2Adapter에 blkdiscard_on_init
옵션을 추가. 초기화 시 슬롯에 쓰기 시작하기 전에 BLKDISCARD ioctl을 발행해
블록 디바이스 전체 범위를 TRIM 처리하는 기능.
load_checkpoint_on_init=False와 상호 배타적으로 강제되며,
clean raw-block device에서 빈 인메모리 인덱스로 새로 시작하는 배포를 위한 기능.
PR 설명 요약
RawBlockDevice.discard(offset, length)Rust 메서드 추가 — BLKDISCARD ioctl 래퍼- sysfs의
discard_max_bytes를 읽어 큰 요청을 chunk 단위로 분할 발행 (드라이버 거부 방지) blkdiscard_on_init플래그를 세 레이어(RawBlockCore→RustRawBlockBackend→RawBlockL2AdapterConfig)에 전파blkdiscard_on_init=True + load_checkpoint_on_init=True조합은ValueError로 거부- BLKDISCARD는 Linux 전용이며 디바이스 의존적. 디바이스가 지원하지 않거나 chunk 하나라도 거부하면 warning 로그 후 startup을 계속하는 best-effort 동작
- 디자인 문서 및 사용자용 RST 업데이트, 테스트 2건 추가
변경된 파일
| 파일 | 변경 내용 |
|---|---|
rust/raw_block/src/lib.rs | RawBlockDevice.discard() 신규, detect_blkdiscard_max_bytes() sysfs 탐색, path 필드 추가 |
lmcache/v1/storage_backend/raw_block/core.py | RawBlockCoreConfig.blkdiscard_on_init 필드, _discard_full_device() 신규, 초기화 순서에 discard 추가, 충돌 검증 |
lmcache/v1/distributed/l2_adapters/raw_block_l2_adapter.py | RawBlockL2AdapterConfig 파라미터/직렬화/docstring 확장 |
lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py | _build_core_config에 blkdiscard_on_init 전달 |
docs/design/v1/distributed/l2_adapters/raw_block.md | 새 옵션 및 제약 문서화 |
tests/v1/distributed/test_raw_block_l2_adapter.py | 테스트 2건 추가 |
동작 흐름
RawBlockCore.__init__
└─ _ensure_capacity_and_layout() (device 열기, 레이아웃 계산)
└─ load_checkpoint_on_init=False 경로
└─ blkdiscard_on_init=True
└─ _discard_full_device()
└─ _rawdev().discard(0, _effective_capacity_bytes)
└─ Rust: detect_blkdiscard_max_bytes() → chunk loop → BLKDISCARD ioctl
└─ meta thread 시작
분류 요약
| 카테고리 | 건수 | 가장 심각한 항목 |
|---|---|---|
| Info — 제약 검증 위치 | 1 | blkdiscard_on_init + load_checkpoint_on_init 충돌이 Core에서만 검증됨 |
| Design — discard 범위 | 1 | 메타 영역 포함 전체 discard가 의도인지 docstring 미명시 |
| Design — 파라미터 순서 변경 | 1 | meta_verify_on_load ↔ load_checkpoint_on_init 위치 swap — 잠재적 breaking change |
| Robustness — sysfs 탐색 | 1 | symlink 체인이 /sys 밖으로 나가는 엣지 케이스 미처리 |
Info — OSError 단일 처리 | 1 | discard 실패 시 warn+continue 동작이 PR 설명에는 있으나 코드 docstring에 미명시 |
| Test — 커버리지 | 2 | chunk 분할 경로 미테스트, discard 실패 시 정상 진행 미테스트 |
상세 리뷰
[info] blkdiscard_on_init + load_checkpoint_on_init 충돌 검증 위치
위치: core.py:227-230
blkdiscard_on_init=True + load_checkpoint_on_init=True 충돌은 RawBlockCore.__init__에서만 검증됩니다. RawBlockL2AdapterConfig에는 이 검사가 없지만, 이는 반드시 문제는 아닙니다. Config 레이어의 다른 단일 값 검사(slot_bytes <= 0 등)와 달리 이 제약은 두 값의 조합에 관한 것이므로 Core에서 검증하는 것도 자연스럽습니다.
단, Config 레이어에도 검증을 추가하면 RawBlockL2Adapter 생성 전에 빠르게 실패할 수 있다는 장점은 있습니다. 일관성을 중시한다면 RawBlockL2AdapterConfig.__init__에도 동일 검사를 추가하는 것을 고려할 수 있습니다.
[warning] discard 범위가 메타 영역을 포함함 — 의도 미명시
위치: core.py:809-840 (_discard_full_device)
_discard_full_device()는 _effective_capacity_bytes를 사용하므로,
메타데이터 영역([0, meta_total_bytes))을 포함한 전체 디바이스를 discard합니다.
load_checkpoint_on_init=False이므로 메타 영역 discard는 문제 없지만,
이것이 의도된 동작인지 데이터 영역만 discard하는 게 의도인지 docstring에 명시되어 있지 않습니다.
권장 수정: docstring에 "full device range including the metadata region" 명시,
또는 _data_base_offset부터 discard하는 것이 의도라면 범위를 수정.
[warning] meta_verify_on_load ↔ load_checkpoint_on_init 파라미터 순서 swap
위치: raw_block_l2_adapter.py:85-87, core.py:126-128
RawBlockL2AdapterConfig와 RawBlockCoreConfig 모두에서 meta_verify_on_load와
load_checkpoint_on_init의 순서가 뒤바뀌었습니다 (기존: load_checkpoint_on_init 먼저).
두 파라미터 모두 기본값이 있으므로 키워드 호출자에게는 영향이 없지만,
위치 인자로 호출하는 코드(테스트나 내부 사용)가 있다면 조용히 깨집니다.
이 순서 변경이 blkdiscard_on_init 추가와 묶여 있어 리뷰에서 놓치기 쉽습니다.
순서 변경이 의도적이라면 PR 설명에 명시하고, 의도적이지 않다면 되돌리는 것을 권장합니다.
[warning] detect_blkdiscard_max_bytes — symlink가 /sys 밖으로 나가는 케이스
위치: lib.rs:178-194
let mut sysfs_path = fs::canonicalize(Path::new("/sys/class/block").join(block_name)).ok()?;
let sys_root = Path::new("/sys");
while sysfs_path.starts_with(sys_root) {
...
if !sysfs_path.pop() { break; }
}
/sys/class/block/<name>을 canonicalize하면 sysfs 심볼릭 링크를 따라
실제 경로로 해석됩니다. 통상적으로 /sys/devices/...가 되지만,
특이한 커널 구성이나 테스트 환경에서는 다를 수 있습니다.
현재 코드는 canonicalize가 실패하면 ?로 None을 반환하므로 안전하게 폴백되며,
루프 가드 sysfs_path.starts_with(sys_root)도 잘못된 경로에서 즉시 탈출합니다.
다만 /sys/class/block 심볼릭 링크 자체가 없는 컨테이너 환경이나 /sys가 마운트되지
않은 경우 canonicalize가 Err를 반환하여 None 폴백→ DEFAULT_BLKDISCARD_CHUNK_BYTES(1GiB)
사용으로 이어집니다. 이 폴백 동작을 주석에 명시하면 좋습니다.
[info] _discard_full_device의 OSError catch-all — docstring 보완 여지
위치: core.py:829-839
PR 설명에는 "디바이스가 discard를 지원하지 않거나 한 chunk를 거부하면 warning 로그 후 startup을 계속한다"고 명시되어 있습니다. 그러나 코드 docstring에는 best-effort 동작의 이유—load_checkpoint_on_init=False이므로 어차피 이전 데이터는 인덱싱되지 않아 partial discard도 안전하다—가 빠져 있습니다. docstring에 한 줄 추가하면 코드만 보는 독자에게도 의도가 전달됩니다.
[info] 테스트 커버리지 부족
위치: test_raw_block_l2_adapter.py:382-429
추가된 테스트 2건:
blkdiscard_on_init호출 시_discard_full_device가 실제로 불리는지 확인 (monkeypatch)load_checkpoint_on_init=True와 함께 쓰면ValueError발생 확인
누락된 케이스:
-
chunk 분할 경로:
detect_blkdiscard_max_bytes가 작은 값을 반환할 때 루프가 올바른 횟수만큼 실행되는지 (discard()Rust 단위 테스트 또는 mock). -
discard 실패 시 초기화 계속:
_discard_full_device가OSError를 발생시킬 때RawBlockCore가 정상적으로 초기화 완료되는지 (현재 warn+continue이므로 반드시 테스트 필요). -
rust_raw_block_backend.py경로: 플러그인 레이어에서blkdiscard_on_init이_build_core_config를 통해 올바르게 전달되는지 테스트 없음.
테스트 커버리지 평가
| 테스트 | 커버 시나리오 |
|---|---|
test_raw_block_l2_adapter_blkdiscard_on_init_calls_discard | blkdiscard_on_init=True 시 _discard_full_device 호출 확인 (monkeypatch) |
test_raw_block_l2_adapter_discard_rejected_with_load_checkpoint | load_checkpoint_on_init=True + blkdiscard_on_init=True → ValueError |
누락: chunk 분할, discard 실패 후 정상 계속, 플러그인 레이어 전달 경로.