본문 바로가기
WIKI 기술 지식 베이스

콘텐츠 캐싱

원문 보기 위키 갱신

콘텐츠 캐싱 (Content Caching)

도구와 에이전트는 이미 본 파일을 자꾸 다시 읽어서 토큰과 시간을 낭비해요. 임베디드 Turso 데이터베이스 기반의 콘텐츠 주소 캐시는 바뀌지 않은 파일을 감지해 전체 내용 대신 diff를 돌려주고, 중복 I/O를 극적으로 줄여 줘요.

출처: 문서

본문

도구와 에이전트는 이미 본 적 있는 파일을 계속 다시 읽곤 하는데, 이러면 토큰과 시간이 낭비돼요. 임베디드 Turso 데이터베이스로 만든 콘텐츠 주소 캐시(content-addressed cache)는 바뀌지 않은 파일을 알아채고 전체 내용 대신 diff를 돌려줘서, 중복 I/O를 획기적으로 줄여 줘요.

이 가이드는 세션별 추적을 갖춘 파일 콘텐츠 캐시를 만드는 데 필요한 스키마와 쿼리를 다뤄요.

스키마 (Schema)

캐시는 네 개의 테이블을 사용해요: 파일 버전용 하나, 세션별 읽기 포인터용 하나, 통계용 두 개예요.

CREATE TABLE IF NOT EXISTS file_versions (
  path        TEXT NOT NULL,
  hash        TEXT NOT NULL,
  content     TEXT NOT NULL,
  lines       INTEGER NOT NULL,
  created_at  INTEGER NOT NULL,
  PRIMARY KEY (path, hash)
);

CREATE TABLE IF NOT EXISTS session_reads (
  session_id  TEXT NOT NULL,
  path        TEXT NOT NULL,
  hash        TEXT NOT NULL,
  read_at     INTEGER NOT NULL,
  PRIMARY KEY (session_id, path)
);

CREATE TABLE IF NOT EXISTS stats (
  key   TEXT PRIMARY KEY,
  value INTEGER NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS session_stats (
  session_id  TEXT NOT NULL,
  key         TEXT NOT NULL,
  value       INTEGER NOT NULL DEFAULT 0,
  PRIMARY KEY (session_id, key)
);

INSERT OR IGNORE INTO stats (key, value) VALUES ('tokens_saved', 0);

구성 요소가 어떻게 맞물리는지

  • **file_versions**는 콘텐츠 주소 저장소예요. 복합 기본키 (path, hash) 덕분에 같은 파일의 여러 버전이 동시에 저장될 수 있어요. 파일이 바뀌었다가 다시 돌아오는 경우(예: 브랜치 전환)에도 옛 버전이 이미 캐시에 있어요.
  • **session_reads**는 각 세션이 각 파일의 어떤 버전을 마지막으로 봤는지 추적해요. (session_id, path)가 키라서 세션마다 자기만의 읽기 상태를 독립적으로 추적해요.
  • **stats**와 **session_stats**는 전역 및 세션별 누적 토큰 절감량을 추적해요.

연결 (Connecting)

import { connect } from "@tursodatabase/database";

const db = await connect(".cache/content.db");
await db.exec(SCHEMA); // The CREATE TABLE statements above

파일 버전 저장하기

파일을 처음 읽었거나(또는 바뀌었을 때) 내용을 해시 키와 함께 저장해요. INSERT OR IGNORE 덕분에 같은 버전이 이미 있으면 아무 일도 일어나지 않아요:

INSERT OR IGNORE INTO file_versions (path, hash, content, lines, created_at)
VALUES (?, ?, ?, ?, ?);

해시는 파일 내용에서 계산해야 해요(예: 잘라낸 SHA-256). 이렇게 하면 저장소가 콘텐츠 주소 방식이 돼요 — 같은 경로라면 동일한 내용은 절대 두 번 저장되지 않아요.

세션별 읽기 추적하기

각 세션은 특정 파일을 마지막에 무엇을 봤는지 알아야 해요. 첫 읽기에서 세션의 읽기 포인터를 기록해요:

INSERT OR REPLACE INTO session_reads (session_id, path, hash, read_at)
VALUES (?, ?, ?, ?);

이후 읽기에서는 세션이 마지막으로 본 것을 확인해요:

SELECT hash FROM session_reads WHERE session_id = ? AND path = ?;

저장된 해시가 현재 파일 해시와 같다면 파일이 바뀌지 않은 거예요 — 전체 내용 대신 짧은 확인 응답을 돌려주면 돼요. 해시가 다르다면 옛 내용을 가져와 diff를 계산해요:

SELECT content FROM file_versions WHERE path = ? AND hash = ?;

그다음 세션의 읽기 포인터를 새 해시로 갱신해요:

UPDATE session_reads SET hash = ?, read_at = ? WHERE session_id = ? AND path = ?;

토큰 절감량 추적하기

전역 통계에는 원자적 카운터를, 세션별 추적에는 업서트를 사용해요:

-- Global counter
UPDATE stats SET value = value + ? WHERE key = 'tokens_saved';

-- Per-session counter (upsert)
INSERT INTO session_stats (session_id, key, value)
VALUES (?, 'tokens_saved', ?)
ON CONFLICT(session_id, key) DO UPDATE SET value = value + ?;

통계 조회하기

-- Number of distinct files cached
SELECT COUNT(DISTINCT path) as file_count FROM file_versions;

-- Global tokens saved
SELECT value FROM stats WHERE key = 'tokens_saved';

-- Tokens saved in the current session
SELECT value FROM session_stats WHERE session_id = ? AND key = 'tokens_saved';

정리 (Cleanup)

디스크에서 파일이 삭제되면 그 파일의 모든 버전과 읽기 포인터를 제거해요:

DELETE FROM file_versions WHERE path = ?;
DELETE FROM session_reads WHERE path = ?;

캐시 전체를 비우려면:

DELETE FROM file_versions;
DELETE FROM session_reads;
DELETE FROM session_stats;
UPDATE stats SET value = 0;

핵심 설계 포인트

  • 콘텐츠 주소 저장소 덕분에 브랜치 전환이 자연스럽게 처리돼요. main에서 피처 브랜치로 갔다가 다시 돌아와도, 원래 파일 버전이 여전히 캐시에 있어요.
  • 세션 격리 덕분에 각 소비자가 자기가 본 것을 독립적으로 추적해요. 세션 A가 이미 캐시했더라도 세션 B는 전체 파일 내용을 받아요.
  • 읽기 시점의 해시 기반 변경 감지 덕분에 정확성을 위해 폴링이나 파일 감시(watching)가 필요 없어요.
  • 임베디드 Turso 데이터베이스는 모든 것을 파일 하나에 담고 네트워크 오버헤드가 전혀 없어요.

예시 (Example)

cachebro는 이 패턴을 AI 코딩 에이전트용 드롭인 파일 읽기 캐시로 구현한 MCP 서버예요.

더 알아보기 (Learn more)