S2 종합 분석 — raw_block checkpoint payload overflow
검증일: 2026-05-27 / 종합 정리: 2026-05-28 / #3449 재평가: 2026-06-04 대상:
lmcache/v1/storage_backend/raw_block/core.py관련 task:private/tasks/v3_standalone_items.mdS2 재현 스크립트:private/work/s2_checkpoint_overflow/repro_checkpoint_overflow.py
0. 한 줄 요약
raw_block은 디바이스의 "목차(인덱스)"를 메타 영역에 JSON으로 백업하는데, 이 JSON에 ~64MB라는 보이지 않는 천장이 있고, 대용량 SSD를 가득 채워 엔트리가 ~32만 개를 넘으면 백업이 **조용히 실패(silent fail)**해서 재시작 시 캐시 전체를 잃는다 — 그리고 이건 정확히 대규모 disk-tier(HC SSD 포함) 사용 시나리오에서 드러난다.
2026-06-04 재평가 — #3449 (zlib 압축) 반영: Daejun의 후속 PR #3449가 checkpoint payload를 zlib 압축(실측
4.6x, capacity +5.5x)해 동일 ceiling 안에 ~5x 더 많은 엔트리를 수용. 이로 인해 Std SSD(1.927.68TB) 케이스는 사실상 해소됨. HC SSD(15/30TB)는 여전히 ceiling에 닿음 — §3 재계산 참고. S2 트랙: "단계 1 PR 불요, HC SSD 전용 단계 2 저우선"으로 축소. §9 갱신.
1. 버그 메커니즘
디바이스 레이아웃 (core.py:975-990)
┌─────────────────────────┬──────────────────────────────────────┐
│ ① 메타데이터 영역 │ ② 데이터 영역 │
│ (offset 0 ~ 128MB) │ (128MB ~ 디바이스 끝) │
│ 인덱스(key→offset 맵) │ 고정 크기 slot_bytes 슬롯 배열 │
└─────────────────────────┴──────────────────────────────────────┘
- 데이터 영역: 실제 KV 캐시. 키 1개 = 슬롯 1개.
- 메타 영역: "어떤 키가 몇 번 슬롯에 있는지" 인덱스. 기본 128MB.
런타임 인덱스는 RAM에 (core.py:241)
self._index: dict[str, _Entry] = {} # 키 → _Entry(offset, size, meta)
휘발성. 재시작하면 증발 → 데이터 영역의 KV가 멀쩡해도 슬롯↔키 매핑을 잃음.
Checkpoint = 인덱스를 디바이스에 박제 (core.py:1213, 1102)
snapshot, _ = self._snapshot_state() # _index → dict
payload = json.dumps(snapshot, ...).encode() # → JSON 바이트
return self._write_checkpoint(payload, ...) # → 메타 영역에 기록
엔트리 1개 JSON ≈ 209B (dense):
"model@0@<64자 해시>": {"offset":...,"size":...,"shape":[...],
"dtype":"bfloat16","fmt":"...","cached_positions":null}
천장: payload_cap (core.py:1032, 231)
메타 영역은 2벌 미러링(_meta_copy_count=2)이라 한 벌당 용량:
payload_cap = (meta_total_bytes // 2 정렬) - block_align
기본값(128MB, 4096) → ~64MB
버그: 천장 넘으면 silent skip (core.py:1164-1171)
if len(payload) > payload_cap:
logger.warning("metadata payload too large, skipping checkpoint")
return False # ← 예외 없음, WARNING 한 줄, 그냥 안 씀
복구 경로는 checkpoint가 유일 (확인 완료)
_validate_loaded_entries(core.py:1407)는 checkpoint에서 로드된_index를 검증만 함. 슬롯을 스캔해 인덱스를 재구축하는 코드 없음.- 슬롯 헤더(
_encode_header, core.py:940)에는slot_identity(8바이트 해시) +payload_len만 있고 원본 키 문자열이 없음 → 헤더만으로 키 복원 불가. - ∴ checkpoint JSON이 키→offset 매핑의 유일한 영구 저장소. fallback 없음.
실패 시나리오
인덱스 누적 → JSON > 64MB → checkpoint 매번 silent skip
→ 메타 영역은 옛 상태(또는 meta_seq=0 빈 상태)
→ 운영자 모름 (WARNING 한 줄, 알림/예외 없음)
→ 재시작/크래시 → RAM의 _index 증발
→ 메타 영역에서 복구 시도 → 비었거나 옛것 → 데이터 영역 KV 전부 미아
→ 캐시 전량 유실 (물리적으론 디스크에 있으나 못 찾음)
2. 초기 분석 오류 정정
초기 검토(2026-05-26)에서 "1TB 디바이스에서 지금 당장 실패"라 주장 → 틀림. 원인: slot_bytes를 1MB로 오가정.
-
slot_bytes는
full_chunk_bytes에서 파생 (rust_raw_block_backend.py:259,local_cpu_backend.py:792) -
non-layerwise:
full_chunk_bytes = kv_size × num_layers × chunk_tokens × hidden_dim × dtype(local_cpu_backend.py:816) -
실측 slot_bytes:
모델 slot_bytes Llama-3-8B (TP=1) 32 MB Llama-3-70B (TP=8) 10 MB Qwen2-72B (TP=4) 20 MB
→ 1MB가 아니라 1032MB. 초기 계산이 1030배 과장.
3. 정확한 overflow 임계점
- 임계:
payload_cap / entry_size = 64MB / 209B ≈ 321,000 entries - = 약 82M 토큰의 distinct KV (chunk_size=256 기준)
- entry 크기: dense(cached_positions=None) ~209B / sparse(256 positions) ~1.2KB
use_layerwise기본 False (config.py:99). layerwise=True면 slot ≈ 1MB
| 설정 | slot_bytes | overflow (가득 찰 때) |
|---|---|---|
| non-layerwise (기본) | 10 MB | 3.1 TB |
| 20 MB | 6.1 TB | |
| 32 MB | 9.8 TB | |
| layerwise=True | ~1 MB | 314 GB |
| sparse (positions) | 10 MB | 0.5 TB |
디바이스 크기 × 모델별 (가득 찰 때 엔트리 수)
| 디바이스 | 모델(slot) | max 엔트리 | JSON | overflow |
|---|---|---|---|---|
| 1.92TB Std | Llama-70B(10MB) | 201K | 40MB | no |
| 3.84TB Std | Llama-70B(10MB) | 403K | 80MB | YES |
| 7.68TB Std | Llama-70B(10MB) | 805K | 161MB | YES |
| 7.68TB Std | Llama-8B(32MB) | 252K | 50MB | no |
| 15TB HC | Llama-8B(32MB) | 503K | 100MB | YES |
| 30TB HC | Llama-8B(32MB) | 983K | 196MB | YES |
핵심 발견: HC SSD 전용 문제가 아님. 3.84/7.68TB는 TCO 문서의 "Std SSD"(Option B). 큰 모델(70B, TP로 slot 작아짐)에선 표준 엔터프라이즈 SSD에서도 가득 차면 overflow. 규칙: slot이 작을수록(큰 모델+높은 TP, 또는 layerwise) 같은 용량에 엔트리 ↑ → 더 쉽게 넘음.
#3449 (zlib 압축) 반영 후 재계산 (2026-06-04)
#3449 실측: 동일 payload_cap에서 압축 후 ~5.5x 더 많은 엔트리 수용 (20K entries 기준).
실효 임계 엔트리: 321K → 1.61.75M (보수 추정 ~5x 적용 시 ~1.6M).
Qwen3-480B TP=8 (slot 7.75MB, §6b 기준) 재계산:
| 디바이스 | max 엔트리 | #3449 전 overflow | #3449 후 overflow |
|---|---|---|---|
| 1.92TB Std | 260K | no | no |
| 3.84TB Std | 520K | YES | no (압축 후 임계 ~1.6M 밑) |
| 7.68TB Std | 1.04M | YES | no |
| 15TB HC | 2.08M | YES | YES (임계 초과) |
| 30TB HC | 4.16M | YES | YES |
→ Std SSD는 #3449로 사실상 해소. HC SSD(15/30TB)는 여전히 살아있음.
Llama-70B TP=8 (slot 10MB) 기준도 유사 — 3.84TB(403K)는 해소, 15TB+(1.5M+)는 잔존.
4. 재현 (검증 완료)
private/work/s2_checkpoint_overflow/repro_checkpoint_overflow.py — _FakeRawBlockDevice(메모리 bytearray)로
실제 디바이스 없이 동일 메커니즘 재현. payload_cap을 작게(4KB) 축소.
payload_cap = 4096 bytes
저장 성공: 60/60 entries, _index 크기 = 60 ← 데이터는 정상 기록
checkpoint payload 크기 = 10808 bytes (cap 4096)
→ overflow? True
_checkpoint_once(force=True) 반환값 = False ← silent skip
meta_seq = 0 ← 한 번도 안 써짐
--- 재시작 (같은 디바이스 재오픈) ---
복구된 _index 크기 = 0 ← 60개 전부 유실
로그 (WARNING만, 예외 없음):
RawBlockCore metadata payload too large (10808 > 4096), skipping checkpoint (core.py:1165)
핵심: 버그는 디바이스 물리 크기가 아니라 엔트리 개수로 터짐 → 8KB 가짜 디바이스로 15TB SSD에서 일어날 일을 동일 재현 가능.
5. 실제 사용성 평가 (실재 이슈인가?)
실재 이슈가 맞다는 근거
- 복구 경로가 checkpoint 하나뿐 (fallback 없음, 슬롯 스캔으로도 불가).
- 타겟 하드웨어(대용량 SSD)가 설계대로 쓰면 반드시 임계점 도달.
- disk 티어의 가치 제안("재시작에도 캐시 생존")을 정면으로 무너뜨림.
- silent — 재시작 전까지 아무도 모름.
덜 급할 수 있다는 정직한 반론
- raw_block이 신생 기능 (§7 참조) — 현재 HC-SSD 스케일 프로덕션 사용자 사실상 없음.
load_checkpoint_on_init=False로 끄면(영속성 불요 배포) 무관.meta_total_bytes를 키워두면 회피 가능한 튜닝 노브.
종합
코드 트집이 아닌 진짜 버그지만 성격은 잠복형(latent) 스케일 버그. 기능이 의도한 규모로 성공할 때 비로소 문다. 가장 정확한 프레이밍:
"대용량 SSD를 LMCache disk tier로 쓰세요"라고 고객에게 말하기 전에 고쳐야 할 버그.
6. 실제 AI 워크로드 발생 가능성
인덱스는 현재 디바이스 거주 키만 담음 → 두 조건 필요:
- 디바이스가 가득 차면 엔트리 > 32만? (디바이스 크기 + slot_bytes 의존, §3 표)
- 워크로드가 디바이스를 그만큼 채우나? (distinct 작업셋 크기 의존)
82M distinct 토큰이 현실적인 시나리오:
| 워크로드 | 82M 토큰 환산 | 현실성 |
|---|---|---|
| 대규모 RAG | ~120,000 페이지 코퍼스 | 엔터프라이즈 지식베이스면 가능 |
| 다중 사용자 챗 | ~8,200 distinct 활성 대화(~10K토큰) | 고트래픽 서비스면 도달 |
| 에이전트/툴 | 긴 컨텍스트 + 도구 출력 누적 | 컨텍스트 길수록 빨리 |
핵심 통찰: disk 티어는 채우려고 provision하는 것. LRU는 steady state에서 항상 가득 참 → 인덱스 크기 ≈ 디바이스가 찬 정도. disk 티어를 정당화하는 워크로드(대형 RAG, 고트래픽)가 곧 distinct 작업셋이 큰 워크로드. 자기선택적 — 이 하드웨어 까는 사람이 이 버그 밟을 사람.
안 일어나는 경우(균형): 소규모(1TB 이하 + 8B), 고재사용(distinct 작음), 개발/단일 GPU, 영속성 불요 배포.
발생 조건 한 문장:
(큰 모델 또는 layerwise로 slot 작고) + (3TB+ 디바이스) + (대형 RAG/고트래픽으로 distinct 작업셋이 커서 가득 채우는) 배포 → steady state에서 반드시 도달.
6b. Qwen3-Coder-480B 사례 (HC-SSD 비용 분석과의 연결)
HC-SSD 비용 분석(private/docs/lmcache_disk_tco_analysis_ko.md) 당시 사용한 모델이
Qwen3-Coder-480B. 이 모델이 정확히 S2를 트리거하는 프로파일임.
주의: 아래는 추정 아키텍처(num_layers=62, num_kv_heads=8(GQA), head_dim=128) 기준. 정확히는 실제
config.json의num_hidden_layers/num_key_value_heads/head_dim확인 필요. 단 "크고 GQA 사용"인 한 결론(slot 작아 overflow 취약)은 견고.
slot_bytes가 작음 (overflow에 불리)
480B MoE라도 KV 캐시는 attention만 관여(expert 수 무관). GQA로 KV head 8개 → 8-GPU 노드(TP=8)면 GPU당 1 head:
| TP | per-GPU KV head | slot_bytes |
|---|---|---|
| TP=8 (단일 노드, 현실 배포) | 1 | 7.75 MB |
| TP=4 | 2 | 15.5 MB |
| TP=2 | 4 | 31 MB |
480B는 최소 8-GPU 필요 → 현실 TP=8 → slot 7.75MB (작음 = 엔트리 많음 = overflow 취약).
overflow 임계 디바이스 크기
TP=8 (slot 7.75MB): 2.4TB 가득 차면 overflow 시작
TP=4 (slot 15.5MB): 4.7TB 가득 차면 overflow 시작
비용 분석 디바이스 옵션 대입 (TP=8, 가득 찰 때):
| 디바이스 | TCO 분류 | max 엔트리 | JSON | overflow |
|---|---|---|---|---|
| 1.92TB | (소형) | 260K | 52MB | no (간당간당) |
| 3.84TB | Std SSD | 520K | 104MB | YES |
| 7.68TB | Std SSD | 1.04M | 207MB | YES |
| 15TB | HC SSD | 2.08M | 414MB | YES |
| 30TB | HC SSD | 4.16M | 828MB | YES |
→ TCO 분석의 Std SSD(3.84/7.68TB)·HC SSD(15/30TB) 옵션 전부가 이 모델로 가득 차면 overflow. 안전한 건 1.92TB 정도뿐.
"실제로 가득 차나" 조건도 충족
Coder 모델 + 긴 컨텍스트(256K)는 §6의 "distinct 작업셋 큰 워크로드" 프로파일 정확히 해당:
- 대형 코드베이스/긴 파일/repo 수준 컨텍스트 → distinct 콘텐츠 많음
- 새 코드/파일 계속 유입, 반복 적음 → 캐시가 빠르게 distinct로 참
- 82M distinct 토큰(=TP=8 기준 2.4TB)을 며칠~몇 주 운영이면 도달
- §6의 "안 일어나는 경우(고재사용·소규모)"가 이 모델엔 해당 안 됨
비용 분석에의 함의
TCO 분석의 전제는 "캐시가 누적·유지되어 GPU 비용을 절감"인데, S2가 이 전제를 무너뜨림:
"이 SSD로 Qwen3-480B 캐싱 → GPU 비용 X 절감"이라는 결론이, 재시작 시 캐시 전량 유실 → 절감 효과 리셋 리스크를 깔고 있음. S2 미수정 시 TCO 분석의 ROI가 낙관적으로 과대평가됨.
7. "LMCache는 널리 쓰이는데 왜 안 터졌나"
LMCache(프로젝트)와 raw_block(S2가 있는 백엔드)을 구분해야 함.
LMCache — 성숙·널리 배포
- 2년차 (2024-05-29 시작, 1,612 커밋)
- 통합: vLLM, vLLM Production Stack, llm-d, NVIDIA Dynamo, KServe
- 스토리지 파트너: Redis, Weka, PliOps (공개 블로그)
raw_block — 신생·미배포 (repo 증거)
- 최초 등장 2026-02-04 (#2482) — 2년 프로젝트에서 ~4개월.
- user-facing 백엔드 카탈로그에 없음.
docs/source/kv_cache/storage_backends/에 raw_block 부재 (문서화된 건 3fs/cpu_ram/dax/gds/redis/s3/weka 등). MP L2 어댑터 문서(mp/l2_storage.rst)에만 등장. - MP L2 adapter 통합(#3119) 머지 2026-05-04 (3주 전). 삼성 팀이 활발히 개발 중.
결론
프로덕션 배포들은 raw_block을 안 씀 — 성숙한 다른 백엔드(Redis/Weka/local_storage/S3) 사용. 이들은 파일시스템의 inode/디렉토리가 인덱스를 대신 관리해 S2가 구조적으로 없음. raw_block은 파일시스템을 일부러 우회(NVMe 직접 I/O, O_DIRECT)하는 게 존재 이유라 인덱스를 스스로 관리해야 하고, 그래서 checkpoint가 필요하고, 그래서 S2가 생김.
"아무도 안 밟음 = 버그 없음"이 아니라 "이 기능이 아직 그 규모로 안 깔림". 삼성 입장: "남이 안 밟은 버그"가 아니라 "우리가 곧 밟을 길 위의 버그".
8. MP / non-MP 양쪽 영향
S2는 공유 RawBlockCore에 있어 MP/non-MP 공통. (디자인 문서: "avoids maintaining
separate raw-block implementations for MP and non-MP mode")
non-MP(RustRawBlockBackend) 기본값도 MP와 동일:
| 설정 | non-MP 기본 | 출처 |
|---|---|---|
meta_total_bytes | 128MB → payload_cap 64MB | rust_raw_block_backend.py:234 |
meta_enable_periodic | True | :279 |
load_checkpoint_on_init | True | :282 |
overflow 코드(_snapshot_state/_write_checkpoint)는 namespace 무관 — _index를 JSON
직렬화할 뿐. MP "object" 키든 non-MP "legacy" 키든 동일.
모드별 차이 (eviction 주체)
| eviction 주체 | 인덱스 누적 | |
|---|---|---|
| MP | 외부 SM이 L2 eviction 관장 | SM 예산만큼 (디바이스보다 작을 수도) |
| non-MP | 자체 eviction 없음 (= S1) | max_slots까지 차고 그 뒤 신규 드롭 |
non-MP는 S1과 맞물려 인덱스가 max_slots까지 자연 누적 → 대용량 디바이스에서 가득 차면 S2 도달. non-MP가 더 오래된 원조 경로(2026-02-04)라 S1·S2가 함께 사는 가장 "날 것" 경로.
→ "non-MP면 안전"은 아님.
9. 수정 방향 — Std SSD / HC SSD 분리 전략
2026-05-29 갱신: Std SSD(1.92
7.68TB)와 HC SSD(1530TB+)는 사용자 프로파일과 fix 정당성이 다름. 단계 분리가 효율적.2026-06-04 재평가 — #3449 반영으로 단계 전략 변경: #3449(zlib 압축)가 Std SSD 케이스를 사실상 해소. 단계 1 PR은 더 이상 불요. HC SSD(15/30TB)만 잔존 → 저우선으로 변경. 단계 2만 후보로 유지.
단계별 커버리지 요약 (#3449 반영 갱신)
| 단계 | 방식 | Std SSD 커버 | HC SSD 커버 | 우선순위 |
|---|---|---|---|---|
| #3449 (압축, Daejun) | zlib 압축 ~5x | ✅ 해소 | ❌ 15/30TB 잔존 | 머지 대기 |
| 불요 (폐기) | ||||
| 2a (HC bump) | 128MB → 2GB | ✅ | ✅ ~30TB | 저우선 |
| 2b (binary RFC) | 바이너리 포맷 | ✅ | ✅ | 저우선 (장기) |
단계 1 — 폐기 (2026-06-04)
#3449 zlib 압축으로 Std SSD 케이스 해소 → 단계 1(128MB → 512MB) PR은 더 이상 필요 없음.
단계 2 — HC SSD 커버 (저우선, #3449 머지 후 검토)
두 옵션 중 선택 (또는 병행). #3449가 머지·안정화된 후 Samsung HC SSD 데이터로 재검토.
옵션 2a — meta_total bump (128MB → 2GB, 총 16x)
임계 엔트리: 321K → 5.14M
임계 디바이스 (Qwen3-480B TP=8): 2.4TB → 38TB
- 15/30TB HC SSD까지 커버 (layerwise 제외)
- 비용: 30TB에서 0.007% 잠식 — 사실상 무료
- 정당성: Samsung HC SSD TCO 분석 데이터 + #3449 후 잔존 케이스 증거 필요
- 머지 난이도: 중간 (압축과 함께 쓰면 더 효과)
옵션 2b — 바이너리 base 포맷 RFC (장기 본질 해결)
entry당 ~209B(JSON) → ~30B(고정 길이 바이너리)
같은 천장으로 약 7배 더 많은 엔트리 + layerwise 모드도 커버
- 장점: 천장 자체를 키우지 않고도 헤드룸 확보, 직렬화 빠름, layerwise 처리
- 단점: 포맷 설계 +
_snapshot_state/_apply_loaded_state전면 수정 - 머지 난이도: 높음 (RFC 토론 3-6개월)
추천: #3449 머지 후 Samsung HC SSD 실측 보강 → 2a 또는 2b 결정.
반드시 — 회귀 테스트 (단계 1과 묶음)
private/work/s2_checkpoint_overflow/repro_checkpoint_overflow.py / repro_s2_midscale.py를
tests/v1/storage_backend/test_rust_raw_block_backend.py의
_FakeRawBlockDevice/_make_raw_block_core 헬퍼로 포팅. 수정 후 통과하도록.
추천 PR 경로
단계 1 (지금): meta_total_bytes 128MB → 512MB + meta_version bump + 회귀 테스트
→ 1개 PR. "보수적 modest fix, Std SSD 메인스트림 사용자 영향". 머지 가능성 높음.
단계 2 (단계 1 머지 후): 옵션 2a 또는 2b. Samsung HC SSD 데이터 보강 후 결정.
PR 명분의 핵심 근거: 실측 + 재현 + 임계점 표 (이미 확보).
10. 2026-06-04 재평가 — #3449 (zlib 압축) 반영
PR #3449 정독일: 2026-06-04 / 상태: OPEN, DongDongJu APPROVED (2026-06-01), ApostaC doc 코멘트만 남음 → 머지 임박
#3449 변경 요약
- checkpoint payload를 zlib level 1로 압축 (
meta_checkpoint_compression="zlib", default) - on-device 포맷: magic tag(
b"LMCZ1\x00") 프레임. 구버전(uncompressed) payload는{(0x7B)로 시작 → 자동 구분, 하위 호환 유지 meta_total_bytes/payload_capacity/_write_checkpointsilent skip 로직 자체는 무변경 — ceiling 동일meta_version미변경 (magic tag로 처리)- 실측 (real device, 20K entries): payload 4332KB → 952KB (4.6x 압축), capacity ~100 → ~550 entries 가득 찰 때 (5.5x)
- 압축/해제는 백그라운드 checkpoint 스레드 / 재시작 시 1회 → KV 데이터 경로 무영향
임계점 재계산 (Qwen3-480B TP=8, entry~209B, 4.6x 압축)
| 디바이스 | 원본 JSON | 압축 후 (~÷4.6) | cap(64MB) | overflow? | 기존 |
|---|---|---|---|---|---|
| 1.92TB | 52MB | ~11MB | 64MB | ✅ 안전 | 안전 (불변) |
| 3.84TB | 104MB | ~23MB | 64MB | ✅ 안전 | 기존 overflow → 해소 |
| 7.68TB | 207MB | ~45MB | 64MB | ✅ 안전 (여유 19MB) | 기존 overflow → 해소 |
| 15TB HC | 414MB | ~90MB | 64MB | ❌ overflow | 여전히 overflow |
| 30TB HC | 828MB | ~180MB | 64MB | ❌ overflow | 여전히 overflow |
Llama-70B (10MB 슬롯, 동일 패턴):
- 15TB: 300MB → ~65MB → borderline overflow (1MB 초과)
- 30TB: 600MB → ~130MB → overflow
S2 트랙 재평가 결론
→ (b) 축소: Std SSD(3.84/7.68TB) overflow는 #3449 머지로 해소. HC SSD(15/30TB)만 살아있음.
단계 분리 갱신:
| 단계 | 기존 | 갱신 | 비고 |
|---|---|---|---|
| 단계 1 (128MB→512MB) | Std SSD 커버 | #3449로 불요 | #3449 머지 후 폐기 |
| 단계 2a (2GB bump) | HC SSD 30TB까지 | 우선 후보 | Samsung HC SSD 데이터 보강 권장 |
| 단계 2b (binary RFC) | layerwise까지 | 장기 후보 | 3-6개월 |
- Daejun 협의: 질문 대상이 #3226 → #3449로 갱신. 질문 초점도 "overflow 다루나요?" → "HC SSD(15/30TB)에서 여전히 닿는 overhead 어떻게 볼지"로 전환.
- S2 트랙은 폐기가 아닌 저우선 유지 — HC SSD에서 살아있으나 메인스트림 우선도 낮아짐.
- S2 단계 1 PR 준비 자료(s2_todo.md P3-2)는 HC SSD용 단계 2a 준비로 전환 또는 보류.