본문으로 건너뛰기

Before/After #3226 — base overflow 처리 코드 비교

목적: PR #3226 (Daejun, incremental base+delta checkpoint) 적용 전후에 base payload overflow 처리가 어떻게 (혹은 거의 그대로) 유지되는지를 코드 라인 단위로 증명.

결론: #3226은 base overflow의 동작 자체를 바꾸지 않음. S2는 머지 후에도 그대로.


핵심 비교 표

항목기존 (현재 mainline)#3226 적용 후변화
base 직렬화json.dumps(snapshot, separators=...)동일 (json.dumps(snapshot, separators=...))❌ 없음
overflow 체크 위치_write_checkpoint 진입부동일 함수, 동일 패턴❌ 없음
overflow 체크 코드if len(payload) > payload_cap:동일 라인❌ 없음
overflow 시 동작logger.warning(...); return False동일 — silent skip❌ 없음
capacity 계산 식self._meta_container_bytes - self.block_alignself._meta_layout.base_payload_capacity (= base_copy_bytes - block_align)명칭만 바뀜, 값 동일
mirror 내 공간 분배mirror = header + base payloadmirror = header + base payload + delta tail⚠️ base 가용 공간 감소 가능
부분 복구 가능성불가 (base 못 쓰면 인덱스 전부 옛것/없음)가능 (이전 base + 살아있는 delta tail)✅ 영향 완화
overflow 천장 자체~64MB (기본 default 기준)~64MB (동일)❌ 없음
임계 엔트리 수~321,000 (entry≈209B 기준)~321,000 (동일)❌ 없음

변경 없음 행이 핵심. 천장과 silent fail은 그대로.


코드 라인 단위 증거

1. _write_checkpoint overflow 체크 — 변경 없음

before (mainline, lmcache/v1/storage_backend/raw_block/core.py:1164-1171):

def _write_checkpoint(self, payload: bytes, dirty_total_snapshot: int) -> bool:
"""Write one checkpoint copy and advance persisted metadata counters."""
payload_cap = self._meta_payload_capacity()
if len(payload) > payload_cap:
logger.warning(
"RawBlockCore metadata payload too large (%d > %d), "
"skipping checkpoint",
len(payload),
payload_cap,
)
return False
...

after (#3226 적용, diff:716-720):

def _write_full_base(self, payload: bytes, dirty_total_snapshot: int) -> bool:
"""...
Returns:
True when the checkpoint committed; False when the payload
exceeds the per-mirror capacity.
"""
payload_cap = self._meta_payload_capacity()
if len(payload) > payload_cap:
logger.warning(
...
)
return False
...

→ 함수명만 _write_checkpoint_write_full_base로 약간 바뀌고, overflow 체크 5줄은 완전히 동일.


2. _meta_payload_capacity 구현 — 값은 동일

before (diff:462-464):

def _meta_payload_capacity(self) -> int:
"""Return usable bytes in one metadata checkpoint payload area."""
return self._meta_container_bytes - self.block_align

after (diff:464-465):

def _meta_payload_capacity(self) -> int:
return self._meta_layout.base_payload_capacity

_meta_layout.base_payload_capacity 정의 (diff:242-244):

@property
def base_payload_capacity(self) -> int:
return self.base_copy_bytes - self.block_align

표현만 layout 추상화로 옮김. 계산식은 동일 (copy_bytes - block_align). 기본값 산수도 동일: meta_total_bytes=128MB / 2 = 64MB - block_align ≈ 64MB.


3. base 직렬화 — 변경 없음

before (mainline, core.py:1213-1217):

snapshot, dirty_total_snapshot = self._snapshot_state()
payload = json.dumps(snapshot, separators=(",", ":"), ensure_ascii=True).encode(
"utf-8"
)
return self._write_checkpoint(payload, dirty_total_snapshot)

after (#3226 적용, diff:804-808):

snapshot, dirty_total_snapshot = self._snapshot_state()
payload = json.dumps(
snapshot, separators=(",", ":"), ensure_ascii=True
).encode("utf-8")
return self._write_checkpoint(payload, dirty_total_snapshot)

줄바꿈만 다르고 의미상 100% 동일. base는 여전히 전체 인덱스 JSON 직렬화.


4. delta tail이 mirror 공간 잠식 — base 가용 공간 약간 감소

before: mirror = [header (1 block)] [base payload (round_up 정렬)]

after (diff:228-235, 새 _MetaLayout 클래스 docstring):

"""...
The metadata region is split into ``len(base_copy_offsets)`` equal-size
mirrors. Within each mirror the layout is::

[ header (one block) ][ payload (round_up to block_align) ][ delta tail ]

The delta tail starts implicitly at the block-aligned end of the base
payload and extends to the mirror's end..."""

그리고 새 write 코드 (diff:751-755):

tail_start = target + self.block_align + payload_total_len
if tail_start + self.block_align <= target + self._meta_layout.base_copy_bytes:
zero_block = bytes(self.block_align)
raw.pwrite_from_buffer(...)

base + delta tail이 같은 mirror 공유. base가 클수록 delta 공간 줄고, delta tail이 있으면 base 가용도 (이론상) 감소. base가 mirror를 가득 채우려 하면 delta 공간 0이 됨.


5. compaction trigger — 천장 자체는 손대지 않음

after (#3226 신규, diff:836-854 _need_full_for_delta):

def _need_full_for_delta(self, payload: bytes) -> bool:
if self._meta_seq == 0:
return True
capacity = self._delta_tail_capacity_locked()
if capacity <= 0:
return True
record_bytes = self._delta_record_total_bytes(len(payload))
if record_bytes > capacity:
return True
if self._delta_tail_off + record_bytes > capacity:
return True
if self._delta_seq + 1 > self.meta_full_checkpoint_max_deltas:
return True
if (
time.monotonic() - self._last_full_ts
> self.meta_full_checkpoint_interval_sec
):
return True
return False

→ delta tail이 차면 full base로 compaction. 하지만 그 full base가 cap을 초과하면 여전히 #1번 코드(_write_full_base)에서 silent skip. Compaction 자체가 cap 문제를 해결하지 않음.


6. delta 실패 → full fallback (silent fail로 귀결)

after (#3226 신규, diff:798-808):

payload, op_count, dirty_total_snapshot = self._snapshot_dirty_log()
if payload is not None and not self._need_full_for_delta(payload):
if self._write_delta(payload, op_count, dirty_total_snapshot):
return True
# Fall through to full compaction if delta append failed.

snapshot, dirty_total_snapshot = self._snapshot_state()
payload = json.dumps(snapshot, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
return self._write_checkpoint(payload, dirty_total_snapshot)

→ delta가 실패하면 full base 시도. full base도 overflow면 silent skip → 결국 동일한 S2 시나리오로 귀결.


영향 완화는 있음 (전량 유실 → 부분 복구)

before (mainline): full base가 첫 시도부터 cap 초과 → meta_seq=0 유지 → 재시작 시 _index={}전량 유실.

after (#3226 적용): 만약 인덱스가 작을 때 한 번 base가 써졌다면(meta_seq>0), 이후 cap을 초과해도 delta는 누적 가능 (delta tail이 가득 차기 전까지). 재시작 시:

  • 이전 base 로드 → 그 시점의 _index 복구
  • delta tail replay → 이후 추가/삭제 반영

첫 base 이후의 변경은 부분 복구 가능. 단, 다음 full base는 여전히 실패하므로 long-term에는 동일 문제.


결론

기존#3226 후
base가 capacity 초과 시 silent skip✓ (동일)
capacity 한계 자체~64MB~64MB (동일)
임계 엔트리 수~321K~321K (동일)
HC SSD 스케일 trigger✓ (동일)
재시작 시 전량 유실부분 복구 가능 (delta tail 살아 있으면)

S2(base overflow)는 #3226 적용 후에도 그대로 살아있음. #3226은 우리 fix와 직교 (orthogonal) — 머지 후 위에 얹어서 해결 가능.


다음 단계 — Std SSD / HC SSD 분리

#3226 머지 후 S2 fix는 두 단계로 분리해서 진행. 자세한 분석: private/work/s2_checkpoint_overflow/s2_verification.md §9.

단계 1 — Std SSD 커버 (modest fix, 머지 가능성 높음)

meta_total_bytes 128MB → 512MB (4x)
임계 디바이스 (Qwen3-480B TP=8): 2.4TB → 9.5TB
  • Std SSD 1.92/3.84/7.68TB 전부 커버, HC SSD 미해결
  • meta_version bump 필요
  • _meta_layout / _write_full_base 주변만 건드리므로 #3226과 conflict 적음

단계 2 — HC SSD 커버 (단계 1 머지 후)

두 옵션 중 선택:

옵션 2a — 한 번 더 bump (512MB → 2GB, 16x 총)

  • 30TB HC SSD까지 커버 (layerwise 제외)
  • 같은 코드 경로, 값만 변경

옵션 2b — 바이너리 base 포맷 (RFC)

  • entry당 ~209B(JSON) → ~30B → 약 7배 헤드룸
  • layerwise 모드도 처리 (단기 fix로는 불가)
  • _snapshot_state / _apply_loaded_state 전면 수정

→ 단계 2는 단계 1 머지 결과 보고 결정.