본문으로 건너뛰기

raw_block 내부구조 — 핵심 Q&A

[!tldr] 업무 관점 takeaway raw_block의 디바이스 레이아웃, 슬롯 구조, write/read path, 체크포인트 조건을 한눈에 정리. S2(checkpoint overflow), H1(O(n) 체크), H2(FIFO 슬롯) 개선 Task의 코드 근거가 여기에 있다. FDP placement hint를 삽입하려면 슬롯 구조와 key namespace부터 이해해야 한다.


Q1. 디바이스 레이아웃

raw_block 디바이스는 두 영역으로 나뉜다:

offset 0
├─ [메타데이터 영역] 0 ~ meta_total_bytes (기본 128MB)
│ └─ 2벌 미러링 (_meta_copy_count=2)
│ 각 컨테이너: header 1 block + payload (key→offset 맵, free slot 목록)

└─ [데이터 영역] meta_total_bytes ~ end
└─ 고정 크기 슬롯 배열
data_base_offset = meta_total_bytes (기본 128MB)
max_slots = (capacity - data_base_offset) // slot_bytes

기본값:

파라미터기본값
block_align4,096 B
header_bytes4,096 B
meta_total_bytes128 MB
data_base_offset128 MB

Q2. 슬롯 구조와 크기 계산

[ header_bytes | payload (full_chunk_bytes) | padding ]
← 4096B → ←────── round_up 후 slot_bytes ────────────→
  • header_bytes: LMCBLK01(8B) + slot_identity(8B) + payload_len(8B) = 24B 최소. 기본 4096B — O_DIRECT 정렬용.
  • full_chunk_bytes: L1 CPU 백엔드에서 측정한 실제 KV 텐서 1청크 크기.
  • block_align: O_DIRECT 요구 정렬 단위 (보통 4096B).
default_slot_bytes = round_up(header_bytes + full_chunk_bytes, block_align)

왜 round_up이 필요한가: O_DIRECT는 offset과 I/O 크기 모두 block_align 배수 필요. 정렬 안 된 slot_bytes는 다음 슬롯의 offset이 정렬 위반 → I/O 실패.

실측 slot_bytes (모델별):

모델slot_bytes
Llama-3-8B (TP=1)32 MB
Llama-3-70B (TP=8)10 MB
Qwen2-72B (TP=4)20 MB

Q3. Write Path 중복 방지 — 두 가지 체크

put_many에서 lock 내부 두 체크:

with self._lock:
if key.encoded in self._index: # 체크 1: 이미 I/O 완료 + 인덱싱됨
results[i] = True
continue
if key.encoded in self._inflight: # 체크 2: 현재 다른 스레드가 I/O 중
continue
# 슬롯 할당 + _inflight 등록
# lock 해제 후 _write_one() 실행 (lock 없이)

하나만으로는 안 되는 이유: lock 해제 후 _write_one()이 실행되는 사이에 다른 스레드가 진입하면 race 발생. 두 체크가 "완료됐거나 진행 중이면 skip"을 구현.


Q4. Read Path — Prefix만 반환하는 이유

get_metadata_prefix는 첫 번째 miss에서 즉시 중단:

for encoded_key in encoded_keys:
entry = self._index.get(encoded_key)
if entry is None:
break # ← 연속된 prefix까지만
metas.append(entry.meta)
if lock:
self._lock_refcnt[encoded_key] += 1

이유: LLM KV cache는 토큰 시퀀스에 1:1. [t0, t1, t2, t3] 요청 시 t1이 없으면 t2·t3는 t1의 attention 없이 쓸 수 없음 → 중간 gap 이후는 버림.

lock=True → unlock_many 패턴: 로드 중 eviction이 슬롯을 재사용하면 데이터 오염. lock 잡으면 delete_many(force=False) 경로에서 locked key 삭제 불가:

# core.py
locked = self._lock_refcnt.get(encoded_key, 0) > 0
if entry is not None and locked and not force:
deleted.append(False)
continue

Q5. Key Namespace — legacy vs object

namespaceslot_identity 계산사용처
legacyint(key.chunk_hash) & UINT64_MASK비MP 단일 프로세스 CacheEngineKey
objectblake2b(encoded_str, digest_size=8)MP 모드 ObjectKey

object namespace에서 chunk_hash만 쓰면 model_name/kv_rank/cache_salt가 다른 키끼리 충돌 가능 → 전체 encoded string을 해싱.

슬롯 헤더 포맷: LMCBLK01(8B) + slot_identity(8B) + payload_len(8B) = 24B.
복구 시 헤더 읽어 expected identity와 비교 → 슬롯 유효성 검증.


Q보너스. Checkpoint 트리거 조건

# core.py _checkpoint_once
dirty = self._meta_dirty_total > self._meta_persisted
idle_ok = self._inflight_io_count == 0 and (
time.monotonic() - self._last_io_ts
) >= (self.meta_idle_quiet_ms / 1000.0)

if not dirty: return False
if not force and not idle_ok: return False
파라미터역할
meta_checkpoint_interval_sec"언제 시도할지" — 이 주기마다 깨어나 시도
meta_idle_quiet_ms"얼마나 조용해야 쓸지" — in-flight I/O 0 + 이 시간 경과 후 허용

I/O 중에 인덱스 체크포인트하면 슬롯 내용과 불일치 → crash 후 복구 오류.
"주기가 됐고 + 더티가 있고 + I/O 없는 조용한 시점" 세 조건 동시 만족 시만 기록.


관련 페이지

  • [[raw_block-종단-분석]] — L1~L4 전계층, FDP 삽입 포인트
  • [[raw_block-성능-우선순위]] — 이 구조 위 개선 항목 종합 우선순위(T1~P3)
  • [[S2-checkpoint-overflow]] — Q보너스에서 이어지는 overflow 버그
  • [[raw_block-개선-Task]] — H1/H2(자료구조 개선), M1/M2(io_uring), S1/S2(안정성)
  • [[LMCache-동시성-비동기-기초]] — _lock/_inflight/idle gate의 동시성 모델
  • [[raw_block-put_many-lock-PR]] · [[raw_block-delete-toctou-PR]] — put_many·delete_many 락 구조를 실제로 손댄 PR