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 lock | with 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 계약의 핵심 구현.