분석 계획 (TODO)
작성일: 2026-05-19 목적: LMCache 의 SSD I/O 스택을 FDP / HC-SSD / 기존 io_uring 활용 관점에서 분석. 관련: lmcache_overview.md
0. 분석 렌즈 (모든 모듈에 동일 적용)
각 모듈을 읽을 때 항상 아래 4가지 관점으로 노트를 남긴다.
| # | 렌즈 | 묻는 질문 |
|---|---|---|
| L1 | Contract | 어떤 인터페이스/추상 클래스를 따르는가? 입력/출력/invariant 는? 어떤 호출 순서가 보장되는가? |
| L2 | I/O 경로 | read/write 가 어디서 발생하는가? 데이터 흐름 (host buffer ↔ device ↔ disk)? 동기/비동기? 배치 단위? |
| L3 | FDP / HC-SSD 삽입 지점 | stream hint, RUH(Reclaim Unit Handle), placement ID 를 끼울 수 있는 지점은? HC-SSD offload(예: compression/CRC) 를 위탁할 수 있는 hook 은? |
| L4 | io_uring 활용 방식 | (해당되는 경우) io_uring_* 호출 위치, queue depth, SQ/CQ 배치 정책, op 종류, registered buffer/file 사용 여부 |
주의: io_uring 은 이미 구현되어 있음. "어떻게 추가할지" 가 아니라 "어떻게 쓰고 있는지" 를 분석한다.
1. 산출물 규칙
- 모듈별 분석 노트:
private/docs/notes/<topic>.md- 파일 상단에 L1~L4 섹션을 그대로 두고 채움.
- 인용은
path/to/file.py:line포맷.
- 최종 종합:
lmcache_overview.md끝에 "§6. SSD I/O 스택 종합" 섹션으로 append. - 코드 수정은 이 단계에서 하지 않음. 분석/노트만.
2. TODO 리스트
진입 순서는 lmcache_overview.md §5.3 의 권장 순서를 따른다.
TODO 1 — L2 어댑터 전체 그림 ✅
- 왜 이걸 먼저 보는가: LMCache 의 SSD I/O 진입점은 "L2 어댑터" 라는 추상 레이어다. 우리가 새 백엔드를 끼우든, 기존 raw_block 을 분석하든, 결국 다 이 레이어 위/아래에서 일어난다. 먼저 무엇이 어떤 종류로 분류되어 있는지 알아야 그 다음 분석에서 "이건 어떤 부류, 어디까지가 framework 책임" 이 자연스럽게 보인다.
- 읽기:
docs/design/v1/distributed/l2_adapters/overall.md - 노트:
private/docs/notes/l2_adapters_overall.md - 목표 산출:
- L2 어댑터가 무엇이고 storage_backend 와 어떤 관계인지 한 문단
- 어댑터 종류 분류 (fs / native / plugin / nixl / mooncake / s3 / resp / mock / raw_block / dax)
- L1, L2 채워두기 (L3, L4 는 모듈별 분석 후 채움)
TODO 2 — 어댑터 계약과 등록 흐름 ✅
- 왜 이걸 보는가: 새 백엔드를 추가할 때 우리가 반드시 지켜야 할 invariant 가 여기서 결정된다. 추상 메서드 시그니처(submit / pop / query, eventfd 3개), thread-safety 요구, "submit 은 non-blocking" 같은 계약을 모르면 plugin 을 짜도 framework 와 충돌한다. factory/registry 흐름을 알아야 "config JSON 한 줄로 어떻게 우리 어댑터가 꽂히는지" 의 끝까지가 보인다.
- 읽기:
lmcache/v1/distributed/l2_adapters/base.py,factory.py,config.py - 노트:
private/docs/notes/l2_adapters_contract.md - 목표 산출 (L1 중심):
L2Adapter추상 메서드 시그니처와 의미- factory 가 config 로부터 어댑터를 고르는 분기 로직
- serde wrapper 가 어디서 끼어드는지 (
serde_wrapper.py)
TODO 3 — adapter ↔ plugin 연결 메커니즘 ✅
- 왜 이걸 보는가: 최종 목표가 "LMCache core 를 건드리지 않고 우리 SSD 백엔드를 plugin 으로 등록" 이다. 그러려면 plugin 이 어떤 식으로 로드/연결되는지 — 동적 import, config class 자동 탐색, native vs python plugin 의 차이, framework 가 깔아주는 것 vs plugin 이 직접 만들어야 하는 것 (event loop, eventfd, executor) — 의 경계를 정확히 그어야 skeleton 을 만들 수 있다.
- 읽기:
docs/design/v1/distributed/l2_adapters/plugin.md+plugin_l2_adapter.py+native_plugin_l2_adapter.py - 노트:
private/docs/notes/plugin_pipeline.md - 목표 산출 (L1 + L2):
- L2Adapter 가 plugin 백엔드를 호출하는 경로 (call graph)
- plugin (native vs python) 의 차이
- 설정 키 매핑: config 등록부터 adapter 인스턴스 생성까지
TODO 4 — raw_block 라인 종단 분석 (★ 최우선) ✅
- 왜 이걸 보는가: raw_block 은 현존하는 LMCache 의 SSD 백엔드 중 우리가 만들 것에 가장 가까운 레퍼런스다. Python adapter ↔ Rust 코어 ↔ io_uring ↔ NVMe 까지 이미 다 연결되어 있어서 "어디까지 잘 짜놓았고, 어디에 placement_id / stream / HC-SSD offload 같은 hook 을 끼울 수 있는지" 의 후보 지점이 모두 이 라인 안에 있다. 또 io_uring 이 이미 구현되어 있으니까 우리가 추가할 게 아니라, 어떻게 쓰고 있는지 (queue depth, op, registered buffer, batch 정책) 를 정밀하게 읽어 두는 게 곧 SSD 입장에서 "LMCache 가 우리 디바이스를 얼마나 잘 쓰는지" 의 baseline 이다.
- 읽기 (Python 라인):
docs/design/v1/distributed/l2_adapters/raw_block.mdlmcache/v1/distributed/l2_adapters/raw_block_l2_adapter.pylmcache/v1/storage_backend/plugins/rust_raw_block_backend.pylmcache/v1/storage_backend/raw_block/core.py,key_codec.py
- 읽기 (Rust 라인 ★):
rust/raw_block/src/lib.rs(2030 LOC) — POSIX/io_uring 엔진, AlignedBuf, fixed buffer 등록, PyO3 바인딩rust/raw_block/README.md— 빌드 방법, 설정 파라미터, 제약rust/raw_block/Cargo.toml—io-uring/pyo3/libc버전- 이 라인이 우리가 FDP placement hint / NVMe passthru 를 끼워 넣을 실제 자리 — Python 분석만으로는 불충분
- 노트:
private/docs/notes/raw_block_line.md - 목표 산출 (L1~L4 모두 채움):
- L1: adapter → plugin → core → Rust 인터페이스 종단 다이어그램
- L2: put / get / lookup / evict 시의 데이터 흐름 (CPU buffer, slab, slot, device)
- L3: FDP/HC-SSD 삽입 후보 지점 표 (어디에 placement_id, stream, offload hook 을 넣을 수 있는가) — 호스트(Python) 측 후보 + Rust↔kernel 경계 후보 둘 다
- L4: io_uring 사용 분석 (전적으로 Rust 라인)
- 어디서 ring 을 만들고 submit/complete 하는가
- queue depth (
DEFAULT_IOURING_QUEUE_DEPTH) 와 배치 정책 - registered buffer / fixed file 사용 여부 — 인프라는 있으나 호출 안 됨
- 어떤 op (READ/WRITE/READV/WRITEV/FSYNC …) 를 쓰는가
- setup flags (
setup_iopoll/setup_sqpoll/setup_single_issuer등) — 현재 전부 default - per-TP device sharding (
PerTPDevicePaths) 가 ring 분배에 미치는 영향
TODO 4.5 — raw_block 분석 후속 검증 (Open Question 처리)
- 왜 이걸 보는가:
TODO 4 가 5개의 Open Question 을 남겼다. 특히
register_fixed_buffers가 Rust 에 인프라만 있고 LMCache 에서 호출 안 되는 것으로 보이는데, 이게 사실이면 io_uring fixed buffer 의 zero-copy 경로 전체가 죽어 있는 것 이라 P1 개선 후보가 된다. 나머지 Open Question 들도 HC-SSD 구간에서의 실제 병목 후보 (인덱스 lock hold, free_slots O(n) 등) 라 실측 전 코드상 위치 / 호출자 확인은 미리 끝내 두는 게 낫다. - 검증 1 —
register_fixed_buffers호출자:lmcache/v1전체에서register_fixed_buffersgrep- 안 불리면 그 이유 (lifetime? alignment 불일치? L1 memory pool 과 호환 안 됨?) 를 코드 추적
- 산출:
raw_block_line.md의 L4 / Open Questions 섹션 갱신 + "P1 후보 확정" 여부 결론
- 검증 2 — L1 memory pool ↔ FDP PLID 풀 분리 가능성:
lmcache/v1/memory_management.py의 메모리 풀 구조 확인- PLID 별 분리 풀이 framework 와 충돌하는지 / 슬롯 단위 인자만 다르게 흘려도 되는지 판단
- 검증 3 —
_snapshot_statelock hold 측정 (가능하면):- 인덱스 1만/10만/100만 entry 시 JSON 직렬화 시간 추정 (microbench)
- 실측은 추후 단계, 지금은 추정만 —
_snapshot_state가 lock 잡고 dict 통째로 순회 + JSON serialize 하는 구조 (core.py:1102-1145) 의 worst case 예측
- 검증 4 —
_free_slots: list[int]멤버십 체크 임계점:- free slot 개수 N 에서
slot in self._free_slots가 문제가 되는 시점 추정 - O(n) 가 실제 핫패스인지 (
delete_many빈도) 코드상 재확인
- free slot 개수 N 에서
- 검증 5 —
meta_total_bytes256 MiB 충분성:- 인덱스 entry 1개당 JSON 직렬화 크기 추정 (key 길이 × N) → 256 MiB / mirror 2 / header 빼고 남는 영역 비교
- 슬롯 100만 개 시나리오에서 OOM 위험 있는지
- 노트 갱신 위치: 모두
raw_block_line.md의 해당 섹션 inline 업데이트 (단독 노트 X) - 목표 산출:
- Open Question 5개 중 코드 추적만으로 답할 수 있는 것 은 답 닫음
- 실측 필요한 것 은 "Phase 0 측정 항목" 으로 라벨링해서 TODO 6 종합 단계로 넘김
TODO 5 — eviction / quota 사이드 트랙
- 왜 이걸 보는가: SSD 입장에서 "LMCache 가 언제, 어떤 데이터를 지우는가" 는 WAF/수명에 직결된다. 특히 FDP 의 RUH 회수 정책과 LMCache 의 slot eviction 정책이 정렬되어 있지 않으면 placement hint 효과가 망가진다 — eviction 흐름을 정확히 알아야 "어떤 데이터를 같은 PLID 로 묶을지" 의 정책 설계가 가능하다.
- 읽기:
docs/design/v1/distributed/l2_adapters/l2_eviction.mddocs/design/v1/distributed/l2_adapters/l2_per_user_quota.md
- 노트:
private/docs/notes/l2_eviction_quota.md - 목표 산출:
- eviction 트리거 조건과 raw_block slot 회수 흐름
- FDP 관점에서 RUH 회수 정책과 일치/불일치 지점
TODO 6 — 종합
- 왜 이걸 하는가: TODO 1~5 의 노트들이 분산되어 있어서, 다음 단계 (코드 수정/plugin skeleton/벤치마크) 로 넘어갈 때 한 곳에서 결정 근거를 볼 수 있어야 한다. 특히 "FDP/HC-SSD 삽입 후보 지점 정리표" 와 "io_uring 사용 요약" 두 개는 회사 내부 보고용으로도 그대로 쓸 수 있는 산출물이라 분리해서 만든다.
-
lmcache_overview.md에 §6 "SSD I/O 스택 종합" 섹션 추가 - 포함 내용:
- adapter → plugin → core 계층 그림 (raw_block 기준)
- io_uring 사용 요약 (queue depth, op 종류, 배치 정책)
- FDP / HC-SSD 삽입 후보 지점 정리표
- TODO 4.5 에서 "Phase 0 측정 항목" 으로 라벨링된 것 모음
- 다음 단계 (코드 수정 단계로 넘어갈 때의 우선순위)
2.5 보충 노트 (TODO 흐름 외 별도 보충)
진행 중 필요해서 작성한 보충 자료 — TODO 1~6 의 정식 산출물은 아니지만 분석 맥락에 필요해서 별도 파일로 둠.
| 파일 | 내용 | 작성 계기 |
|---|---|---|
notes/mp_vs_non_mp.md | MP / Non-MP 모드 정의, 프로세스/통신 구조, 인터페이스 비교 | TODO 4 후속, "MP / Non-MP 차이 별도 정리" 필요해서 |
notes/raw-block-perf-findings.md | raw_block 성능 관점 사전 메모 (Rust 7개 / Python 7개 / L2 adapter 2개 개선 포인트) | TODO 4 사전 입력 |
추가 보충이 생기면 이 표에 한 줄씩만 추가. 단독 TODO 로 승격할 가치가 생기면 §2 의 정식 TODO 로 옮긴다.
3. 진행 규칙
- 각 TODO 는 읽기 → 노트 작성 → 체크박스 마킹 순서.
- 노트는 길게 쓰지 않는다. L1
L4 각 15 bullet 이면 충분. - 디자인 문서와 코드가 다르면 코드를 우선 하고 차이를 노트에 기록.
- 의문점은 노트 하단 "Open Questions" 에 모았다가 일괄 질문.