트렌드브리프
GitHub API rate limit, 여러 워커가 공유 캐시로 한도 아끼기

GitHub API rate limit, 여러 워커가 공유 캐시로 한도 아끼기

게시 · 약 7분 분량

AI 요약

GitHub 인증 REST API는 2026년 7월 기준 토큰당 시간당 5,000회다. 워커 여러 개가 한 토큰을 쓰면 이 한도를 나눠 쓰므로, ETag를 담은 공유 캐시로 조건부 요청을 보내면 304 응답은 한도에서 빠진다.

목차
  1. 왜 워커를 늘려도 한도는 그대로일까
  2. 조건부 요청은 어떻게 한도를 아끼나
  3. 공유 캐시로 워커들이 ETag를 나눠 쓰기
  4. 공유 캐시를 쓸 때 놓치기 쉬운 것
  5. 개념을 실제로 구현한 도구: octopool

GitHub 공식 문서(REST API rate limits)에 따르면 2026년 7월 기준 인증된 REST API 요청 한도는 개인 액세스 토큰당 시간당 5,000회다. 이 한도는 프로세스가 아니라 토큰(사용자) 단위라, 워커를 여러 개 띄워 같은 토큰으로 호출하면 5,000회를 함께 나눠 쓴다. 해법은 ETag를 저장하는 공유 캐시를 두고 조건부 요청을 보내는 것이다. 리소스가 그대로면 GitHub이 304 Not Modified를 돌려주고, 이 304는 한도에서 빠진다. 이 글은 인증된 GitHub REST API를 여러 워커가 한 토큰으로 호출하는 상황에 한정하며, GraphQL API나 미인증 호출은 다루지 않는다.

왜 워커를 늘려도 한도는 그대로일까

한도가 프로세스가 아니라 토큰 단위이기 때문이다. GitHub 공식 문서는 미인증 요청을 시간당 60회, 개인 액세스 토큰으로 인증한 요청을 시간당 5,000회로 제한한다. 워커를 열 개로 늘려도 같은 토큰을 쓰면 열 프로세스가 이 5,000회를 함께 소진한다. 응답 헤더 x-ratelimit-remaining으로 남은 횟수를 확인하고, 이 값이 0이 되면 GitHub은 403이나 429를 돌려준다.

인증 방식마다 한도가 다르다. GitHub 공식 문서가 밝힌 값은 다음과 같다.

인증 방식시간당 요청 한도
미인증60회
개인 액세스 토큰5,000회
GitHub App 설치 토큰5,000회(최소)
Enterprise Cloud 조직 설치15,000회

그래서 처리량을 올리려고 워커 수만 늘리는 것은 한도 앞에서 의미가 없다. 늘려야 할 것은 한도에서 빠지는 요청의 비율이다.

조건부 요청은 어떻게 한도를 아끼나

변경이 없을 때 GitHub이 304를 돌려주고, 그 304가 한도 계산에서 빠지기 때문이다. GitHub 공식 문서(REST API 모범 사례)는 조건부 요청이 304 응답을 받고 Authorization 헤더로 올바르게 인증된 경우 기본 rate limit에 포함되지 않는다고 명시한다.

동작은 두 단계다. 먼저 응답 헤더의 ETag(리소스의 현재 버전을 가리키는 식별자) 값을 저장한다. 다음 요청 때 그 값을 If-None-Match 헤더에 실어 보낸다. 내용에 변화가 없으면 GitHub은 본문 없이 304만 돌려준다. ETag 대신 Last-Modified 값과 If-Modified-Since 헤더를 써도 결과는 같다. 단 조건부 요청은 GET 같은 안전한 메서드에만 걸린다.

# 1) 첫 요청에서 ETag를 받는다
curl -sI -H "Authorization: Bearer $TOKEN" \
 https://api.github.com/repos/octocat/Hello-World | grep -i etag
# etag: W/"abc123..."

# 2) 받은 ETag를 If-None-Match로 보낸다
curl -sD - -o /dev/null \
 -H "Authorization: Bearer $TOKEN" \
 -H 'If-None-Match: W/"abc123..."' \
 https://api.github.com/repos/octocat/Hello-World \
 | grep -iE "HTTP|x-ratelimit-remaining"
# 변경이 없으면 HTTP/2 304, x-ratelimit-remaining 은 줄지 않는다

두 번째 요청이 304를 받으면 남은 한도가 그대로다. 폴링처럼 같은 리소스를 반복 조회하는 작업이라면 대부분의 호출이 304로 떨어지고, 실제 한도 소진은 변경이 일어난 순간에만 발생한다.

공유 캐시로 워커들이 ETag를 나눠 쓰기

핵심은 ETag와 응답 본문을 워커마다 따로 들고 있지 말고 Redis 같은 공유 저장소 한 곳에 모으는 것이다. 그러면 한 워커가 받아 둔 ETag를 다른 워커가 그대로 재사용해 조건부 요청을 보낸다. 워커 열 개가 같은 리소스를 조회해도 실제 데이터 갱신은 한 번만 한도를 쓴다.

요청 한 번의 처리 순서는 이렇게 짠다.

  1. 요청 전에 공유 캐시에서 해당 URL의 항목(ETag와 본문)을 찾는다.
  2. 항목이 있으면 그 ETag를 If-None-Match에 실어 GitHub에 요청한다.
  3. 304가 오면 캐시의 본문을 그대로 쓴다. 한도 소진은 없다.
  4. 200이 오면 새 본문과 새 ETag로 캐시를 갱신한다. 이때만 한도를 1회 쓴다.

캐시 키 설계가 관건이다. ETag는 토큰별로 유효하므로 캐시 키에 리소스 URL뿐 아니라 토큰이나 설치 ID를 함께 넣어 토큰별로 항목을 분리하는 게 안전하다. 이 구조는 REST 리소스 캐싱의 일반적인 설계와 결이 같다. REST의 안전한 메서드와 리소스 표현 개념이 낯설다면 REST API의 뜻과 설계 원칙을 정리한 글을 먼저 보면 조건부 요청이 왜 GET에만 걸리는지 이해가 빠르다.

공유 캐시를 쓸 때 놓치기 쉬운 것

개념을 실제로 구현한 도구: octopool

지금까지의 조건부 요청과 공유 캐시 구조를 조직 단위로 구현한 오픈소스가 octopool이다. octopool 프로젝트에 따르면 이 도구는 MIT 라이선스의 TypeScript 프로젝트로, '조직 공유 GitHub 읽기 릴레이·캐시'를 단일 Cloudflare Worker 하나로 구현한다. 유지보수 팀과 봇이 gh pr view, gh pr checks, gh run list, gh issue list 같은 똑같은 read 형태를 반복하면서 primary rate limit을 빠르게 소진하는 문제를, 그 트래픽을 개별 머신에서 Cloudflare로 옮겨 푼다. 앞서 본 "한 토큰의 5,000회를 여러 프로세스가 나눠 쓴다"는 상황을 팀 전체 규모로 확장한 셈이다.

octopool 프로젝트가 밝힌 한도 절약 방식은 세 갈래다.

접근 통제도 읽기 캐시라는 성격에 맞췄다. 검증된 조직 멤버만 caller 토큰을 발급받고, 대상은 공개 repo뿐이라 비공개 저장소는 각자 로컬 gh로 폴백한다. CLI는 gh 드롭인 shim이라 안전한 read만 octopool을 먼저 거치고, 뮤테이션이나 시크릿을 다루는 요청은 로컬 gh로 그대로 통과시킨다. 배포에는 Cloudflare Workers 유료 플랜(Durable Objects와 D1)과 검증된 org, 그리고 최소 한 개의 PAT나 App이 필요하다.

결국 처리량의 병목은 워커 수가 아니라 한도에서 빠지는 요청의 비율이다. GitHub도 반복 폴링 대신 웹훅으로 이벤트를 받으라고 권한다. 공유 캐시로 조건부 요청을 극대화하고, 그래도 자주 도는 조회는 웹훅으로 대체하는 것이 실무에서 쓰는 조합이다.

가운데 릴레이 블록이 위쪽 캐시 카드 층과 아래쪽 연료 게이지가 다른 네 개 토큰 층을 담고, 여유 최다 토큰이 파랗게 강조되며, 캐시 히트는 체크 표시와 함께 호출자로 되돌아가고 미스는 그 토큰으로 GitHub까지 나가는 선화
octopool의 그림: 신뢰 캐시 히트는 GitHub를 건드리지 않고, 미스는 여유가 가장 많은 풀 신원으로 내보낸다.
더 많은 글 보기 RSS 구독