블로그(former-force)에서 Notion API를 실행, rate_limited / HTTP 429가 난 이유를, 지금 레포에 있는 캐시·동시성·웹훅 기준으로 정리한 문서다.

관련: Astro 블로그 · Astro 블로그 · Notion CMS 방향성 · 제작 과정 · 결정 로그 (종료) · 레포 cpt4567/former-force


1. 왜 리미트가 뜨나

Notion Public API는 연결(connection)당 평균 초당 약 3요청이다. 넘으면 code: rate_limited, HTTP 429, 헤더 Retry-After(초). 워크스페이스 플랜에 묶인 한도도 따로 있고, 그때도 같은 형태로 막힌다. 529(service_overload)도 잠깐 쉬었다가 같은 방식으로 재시도한다.

공식: Request limits

블로그 쪽이 특히 잘 터지는 지점은 본문 블록을 페이지마다 blocks.children.list로 재귀 fetch할 때다. Published가 200장 가까이 되면, 목록 쿼리만으로도 페이지네이션이 여러 번이고, 본문까지 붙이면 요청 수가 급격히 불어난다. NOTION_CACHE=0으로 디스크 캐시를 끄거나 .cache/notion을 지운 채 astro dev / astro build를 연속으로 돌리면 한도가 바로 보인다.

레포는 이 에러를 감싸서 이렇게 다시 던진다 (src/lib/notion.ts):

Notion API rate limited while …. Wait a minute, or reuse .cache/notion (unset NOTION_CACHE=0).


2. 데이터 흐름 (한 장 요약)

경로 하는 일 Notion 호출량
목록 (getPublishedPostSummaries) Published 쿼리만 · bodyHtml 비움 작음 (dataSources.query 페이지네이션)
전체 (getPublishedPosts) 목록 + 각 페이지 블록→HTML 큼 (페이지×블록 children)
단건 (getPublishedPostBySlug) 풀 캐시 히트 우선 · 없으면 단건 본문 중간
웹훅 POST /api/notion/webhook 캐시 삭제 + 재배포 트리거 이벤트당 최소 · 본문 재fetch는 빌드 때

토큰(NOTION_TOKEN)은 서버/빌드 env만. PUBLIC_*로 올리지 않는다.


3. 캐싱 최적화 (구현됨)

3.1 디스크 + 메모리

항목 값
디렉터리 .cache/notion/ (gitignore)
스키마 버전 CACHE_SCHEMA = 3 (HTML 형태 바뀌면 버프 → 옛 파일 무효)
파일 예 posts-full.json · posts-summaries.json · tag-options.json
dev TTL 30분
build TTL 무한 (빌드 한 번 안에서 재사용)
무효화 NOTION_CACHE=0 또는 false · 폴더 삭제 · 웹훅 clearNotionCache()
저장 내용 포스트/태그 페이로드만 · 시크릿 없음

흐름은 단순하다. 같은 키로 이미 뜨는 요청이 있으면 inflight로 합치고, 메모리 → 디스크 순으로 보고, 둘 다 없거나 TTL이 지났을 때만 Notion을 친다.

3.2 동시성 캡

NOTION_MAX_CONCURRENT = 3 으로 Notion 호출을 큐잉한다 (withNotionLimit). 공식 평균 초당 3요청과 맞춰, 블록 재귀가 한꺼번에 폭주하지 않게 막는다.