본문으로 건너뛰기

exists_many 해석

변경 검증 가이드 (다음 fetch 후):

git log eaa2bfee..HEAD -- lmcache/v1/storage_backend/raw_block/core.py

위에 커밋이 잡히면 exists_many 가 (a) 여전히 인메모리 dict 조회만 하는지, (b) lock=True 가 refcount 증가 패턴을 그대로 쓰는지 재확인. lock 의미가 bool/타임아웃 등으로 바뀌면 "왜 refcount 인가" 섹션 통째로 다시 써야 함.


위치: lmcache/v1/storage_backend/raw_block/core.py:523-547

한 줄 요약

"이 키들이 raw-block 디스크에 있느냐?" 를 인메모리 인덱스로 빠르게 확인 + 옵션으로 "있는 동안 evict 못 하게 잠금" 도 같이 거는 함수.

디스크 IO 는 한 번도 안 함 — 순수 in-memory 조회.

입출력

exists_many(["key_A", "key_B", "key_C"], lock=True)
[True, False, True]
  • 입력: 인코딩된 키 리스트
  • 출력: 같은 길이의 bool 리스트(hit bitmap) — 순서 보존

코드 한 줄씩

results: list[bool] = []
with self._lock: # ① 인덱스 보호 (출입증)
for encoded_key in encoded_keys:
found = encoded_key in self._index # ② dict 조회 O(1)
results.append(found)
if found and lock: # ③ hit + lock 옵션
self._lock_refcnt[encoded_key] = (
self._lock_refcnt.get(encoded_key, 0) + 1 # ④ refcount++
)
return results

with self._lock

파이썬 threading.Lock — 다른 스레드가 동시에 _index 를 수정 중일 수 있어 보호. (threading_lock.md 참고.)

encoded_key in self._index

  • self._index: dict[str, _Entry] — 키 → 슬롯 위치/크기
  • dict 멤버십 평균 O(1)
  • 디스크 안 건드림 — 메타 인덱스는 부팅 시 한 번 로드돼서 메모리 상주

③ ④ L2 lock refcount 증가

호출자가 lock=True 명시했을 때만 — L2 lock = "이 슬롯 evict 하지 마" 의 reservation. 디스크 lock 아닌 카운터.

두 종류의 lock 구분

종류코드의미
파이썬 threading lockwith self._lock:스레드 간 동기화 (자료구조 보호)
L2 lock (refcount)_lock_refcnt[key] += 1슬롯 데이터 evict 보호 (논리적 잠금)

왜 lock 옵션이 필요 — race 방지

전형적인 read 흐름:

t0: vLLM 이 "key_A 있어?" → exists_many(["key_A"]) → [True]
t1: vLLM 이 "그럼 읽어줘" → load_many_into(["key_A"], buf)
t2: load 완료

t0 ~ t1 사이에 누가 delete("key_A") 하면? → 슬롯이 free list 로 가고 다른 put 이 덮어쓸 수 있음 → t1 에서 엉뚱한 데이터 read.

lock=True 로 호출하면:

t0: exists_many(["key_A"], lock=True) → [True], _lock_refcnt["key_A"] = 1
t1: load_many_into(["key_A"], buf) → 안전하게 로드
t2: unlock_many(["key_A"]) → _lock_refcnt["key_A"] = 0

delete(force=False) 는 lock 된 슬롯 보존 (refcount > 0 이면 evict 거부). core_delete_many.md 참고.

왜 refcount 인가 (단순 bool 이 아니라)

여러 호출자가 같은 키 동시 read 가능:

호출자 X: exists_many(["key_A"], lock=True) → refcnt = 1
호출자 Y: exists_many(["key_A"], lock=True) → refcnt = 2
호출자 X: load 끝, unlock → refcnt = 1
호출자 Y: load 끝, unlock → refcnt = 0 (이제 evict 가능)

동시 read 지원 + 모두 끝났을 때만 evict 허용 = refcount 패턴.

한 문장

exists_many = 인메모리 hit/miss 확인 + (옵션) hit 슬롯에 reservation 카운터 증가시켜 lookup → load 사이의 evict 로부터 보호. MP lookup-and-lock 계약의 핵심 구현.