- 분야: DB
- 기술 스택: TypeScript · Cloudflare D1 · SQLite · AsyncLocalStorage
- 관련 기술: SSR 메타데이터 · 요청 범위 메모이제이션 · Promise 공유 · 콘텐츠 revision
문제와 원인
메타데이터와 본문이 같은 이론 경로를 각각 조회할 때마다 D1에서 revision을 읽었다. 캐시 미적중에서는 첫 조회의 revision·콘텐츠 읽기 2회에 두 번째 revision 읽기 1회가 추가됐다. 캐시 적중에서도 revision 확인 2회가 남았다.
수정 전 코드
readPublicTheoryPage()
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
const revision = await readContentCacheRevision();
return readPublicContentCache({
namespace: "public-theory-page",
key: pathname,
revision,
loader: () => readUncachedPublicTheoryPage(route),
});
수정 후 코드
AsyncLocalStorage의 요청별 Map에 경로를 키로 넣고, work()가 반환한 Promise를 즉시 저장했다. 메타데이터와 본문은 진행 중인 작업을 함께 기다린다. 실패한 Promise는 제거해 재시도를 허용하고, 다음 HTTP 요청은 새 Map으로 revision을 다시 확인한다.
readPublicTheoryPage()
revision 확인부터 콘텐츠 캐시 조회까지 같은 작업으로 감싼다.
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
return memoizeD1Request(`public-theory-page:${pathname}`, async () => {
const revision = await readContentCacheRevision();
return readPublicContentCache({
namespace: "public-theory-page", key: pathname, revision,
loader: () => readUncachedPublicTheoryPage(route),
});
});
memoizeD1Request()
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
export function memoizeD1Request<T>(key: string, work: () => Promise<T>) {
const memo = scope.getStore()?.requestMemo;
if (!memo) return work();
const existing = memo.get(key);
if (existing) return existing as Promise<T>;
const pending = work();
memo.set(key, pending);
void pending.catch(() => {
if (memo.get(key) === pending) memo.delete(key);
});
return pending;
}
검증 결과
수정 전후 원본 이론 조회 서비스·계측 모듈을 Node.js·SQLite에서 같은 기준 DB·경로로 실행했다. 요청마다 메타데이터·본문 순서로 2번 호출하고 D1 호환 어댑터 카운터와 X-DB-Ops 헤더를 대조했다. Cloudflare Wrangler의 로컬 D1에서도 같은 호출 감소를 확인했다.
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
| 조건 | 수정 전 D1 호출 | 수정 후 D1 호출 | 결과 |
|---|---|---|---|
| 콘텐츠 캐시 미적중 | 3회 | 2회 | 1회 감소·33.3% |
| 새 요청의 캐시 적중 | 2회 | 1회 | 1회 감소·50.0% |
| 다음 요청 전 제목·revision 변경 | 3회 | 2회 | 변경 제목 반영 |
메타데이터·본문의 전체 페이지 값은 전후 모두 일치했고, 초기 페이지의 직렬화 SHA-256도 같았다. 모든 비교 조건에서 계측 헤더와 어댑터 카운터가 일치했다.
Local Explorer의 d1_all·d1_batch 노드는 각각 D1 실행 호출 1회다. 수정 전 미적중 요청에는 d1_all·d1_batch·d1_all 세 노드가 있었다.
수정 후 같은 조건의 요청에는 d1_all·d1_batch 두 노드만 남았다.