구리지 않은 시맨틱 무효화

발행: (2026년 3월 3일 오후 11:45 GMT+9)
6 분 소요
원문: Dev.to

Source: Dev.to

캐싱 문제

웹 애플리케이션을 어느 정도 기간 동안 작업해 본 사람이라면 캐싱에 대한 이야기를 들어봤을 것입니다. 캐시를 추가하면 모든 것이 빨라지고, 누군가가 무언가를 업데이트하면 사용자는 오래된 데이터(가격, 재고 등)를 보게 됩니다. TTL이 도움이 되지만 항상 신선도와 부하 사이를 트레이드오프하게 됩니다.

보통의 해결책은 수동 무효화입니다: 제품을 업데이트하고 캐시 키를 무효화합니다. 이는 단일 엔드포인트에서는 잘 동작하지만, 그 제품에 리뷰가 있고, 리뷰에 댓글이 있으며, 제품이 매장에 속하고, 매장이 조직에 속하는 경우 관계가 급격히 복잡해집니다. 결국 많은 관계를 추적하고 모든 곳에서 키를 무효화해야 하는 상황에 처합니다.

ZooCache 소개

ZooCache는 Rust 코어를 갖춘 파이썬 캐싱 라이브러리로, 시맨틱 무효화에 초점을 맞춥니다—무엇이 변경됐는지에 기반해 무효화하고, 언제가 아니라.

의존성 등록

무언가를 캐시할 때, 해당 데이터가 의존하고 있는 태그(종속성)를 선언합니다:

from zoocache import cacheable, invalidate, add_deps, configure

configure()

@cacheable()
def get_product(pid):
    add_deps([f"product:{pid}"])
    return db.get_product(pid)

@cacheable()
def get_reviews(pid):
    add_deps([f"product:{pid}:reviews"])
    return db.get_reviews(pid)

@cacheable()
def get_store_products(sid):
    add_deps([f"store:{sid}:products"])
    return db.get_store_products(sid)

@cacheable()
def get_org_stores(oid):
    add_deps([f"org:{oid}:stores"])
    return db.get_org_stores(oid)

이 태그들은 계층 구조를 이룹니다. 예를 들어 org:1:stores:2:products:42PrefixTrie 안의 경로가 됩니다.

태그 무효화

데이터가 변경되면 해당 태그를 무효화합니다:

def update_product(pid, data):
    db.update_product(pid, data)
    invalidate(f"product:{pid}")

def update_store(sid, data):
    db.update_store(sid, data)
    invalidate(f"store:{sid}")

def update_org(oid, data):
    db.update_org(oid, data)
    invalidate(f"org:{oid}")

org:1을 무효화하면 그 아래에 있는 모든 것이 삭제됩니다—제품, 리뷰, 매장 제품 등. 이제 어떤 함수가 무엇을 캐시했는지 기억할 필요가 없습니다.

무효화 연산은 **O(D)**이며, 여기서 D는 태그 깊이이고, 캐시된 항목 수와는 무관합니다.

인스턴스 간 일관성

여러 인스턴스를 실행한다면 ZooCache는 **Hybrid Logical Clocks (HLC)**를 사용해 일관성을 유지합니다. 각 무효화는 시계 드리프트를 고려한 타임스탬프를 받으며, 시스템 시계가 동기화되지 않아도 나중에 발생한 무효화는 더 높은 타임스탬프를 갖게 됩니다.

수동 재동기화

각 캐시 엔트리는 버전 정보를 저장합니다. 노드가 다른 노드에서 데이터를 읽을 때 해당 버전을 확인하고, 더 최신이면 자동으로 따라잡습니다. 이를 통해 추가적인 조정 없이도 읽기 일관성을 유지합니다.

캐시 스탬피드 방지

캐시 엔트리가 만료되고 동시에 많은 요청이 데이터베이스를 향하면 스탬피드가 발생합니다. ZooCache는 SingleFlight 패턴으로 이를 완화합니다: 첫 번째 요청이 작업을 수행하고 나머지는 대기한 뒤, 모두 동일한 결과를 받습니다. 데이터베이스는 여러 번이 아니라 한 번만 쿼리를 받게 됩니다.

한눈에 보는 기능

  • 스토리지 백엔드: 인‑메모리, LMDB, 또는 Redis
  • 프레임워크 통합: FastAPI, Django, Litestar
  • 직렬화: LZ4 압축을 적용한 MsgPack
  • 관측성: 로그, Prometheus, OpenTelemetry
  • 모니터링용 CLI
  • Rust 코어와 파이썬 바인딩

벤치마크는 문서 사이트에서 확인할 수 있습니다.

설치

uv add zoocache

링크

편하게 사용해 보시고 의견을 공유해 주세요—이슈, 제안, 혹은 그 외 어떤 것이든 환영합니다. 더 나은 동작을 위해 여러분의 피드백을 언제나 기다리고 있습니다.

0 조회
Back to Blog

관련 글

더 보기 »