Skip to content

ADR-18: GitHub API 캐시 테이블

🇺🇸 English Version

날짜작성자리포지토리
2025-12-24@specvitalinfra, web

컨텍스트

GitHub API의 엄격한 Rate Limit: 인증된 사용자 기준 시간당 5,000 요청. 대시보드 리포지토리 선택 기능에서 UX 및 안정성 이슈 발생.

이슈영향
Rate Limit 소진한도 도달 시 대시보드 사용 불가
반복 API 호출대시보드 방문마다 리포지토리 목록 조회
지연 시간 변동GitHub API 응답 시간 100-500ms
중복 요청 낭비여러 브라우저 탭에서 동일 API 호출

UX 문제점

  • 느린 초기 로드 (리포지토리 목록 대기 200-500ms)
  • 리포지토리가 많은 사용자의 빠른 Rate Limit 소진
  • 데이터 신선도 가시성 부재

ADR-09: GitHub App 통합 전략에서 확립한 5,000/hr Rate Limit 제약 조건 확장.

결정

하이브리드 정규화 및 사용자 제어 새로고침 전략의 데이터베이스 캐시 채택.

스키마

sql
-- 공유 조직 메타데이터 (전역 중복 제거)
CREATE TABLE github_organizations (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  github_org_id BIGINT NOT NULL UNIQUE,
  login VARCHAR(255) NOT NULL,
  avatar_url TEXT,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- 사용자-조직 N:N 관계
CREATE TABLE user_github_org_memberships (
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  github_org_id UUID NOT NULL REFERENCES github_organizations(id) ON DELETE CASCADE,
  CONSTRAINT uq_user_org UNIQUE (user_id, github_org_id)
);

-- 사용자별 통합 리포지토리 캐시
CREATE TABLE user_github_repositories (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  github_repo_id BIGINT NOT NULL,
  full_name VARCHAR(511) NOT NULL,
  source_type VARCHAR(50) NOT NULL, -- 'personal' | 'organization'
  source_org_id UUID REFERENCES github_organizations(id),
  CONSTRAINT uq_user_repo UNIQUE (user_id, github_repo_id)
);

접근 패턴

사용자 요청 → 캐시 확인 → 히트? → 캐시 데이터 반환 (즉시)
                    ↓ 미스
              Singleflight 그룹

              GitHub API 호출 → 저장 → 반환

새로고침 전략

  • 캐시 우선: GitHub API 호출 전 DB 확인
  • 사용자 제어: ?refresh=true로 무효화 강제
  • TTL 없음: 명시적 새로고침까지 캐시 유지
  • Singleflight: 중복 동시 요청 방지

검토한 옵션

옵션 A: 캐시 없음 (직접 API)

장점단점
항상 최신 데이터빠른 Rate Limit 소진
단순함요청당 200-500ms 지연
소진 시 연쇄 장애

기각: 용납할 수 없는 UX 및 Rate Limit 위험.

옵션 B: TTL 기반 캐시

자동 시간 기반 만료 캐시 (예: 15분 TTL).

장점단점
자동 신선도 보장TTL 튜닝 복잡성
예측 가능한 부실 범위TTL 경계에서 캐시 스탬피드
업계 표준 패턴백그라운드 새로고침 작업 필요

기각: TTL 경계가 스탬피드 위험 생성; 임의 값이 실제 변경 빈도와 불일치.

옵션 C: 사용자 제어 새로고침 (채택)

사용자가 명시적으로 새로고침할 때까지 캐시 유지.

장점단점
즉시 로드 (0ms 히트)데이터 부실 가능
사용자 통제권새로고침 필요성 인지 필요
최소 API 소비새 리포지토리 새로고침까지 미표시
TTL 복잡성 없음사용자 증가에 따른 저장 공간

채택: 주요 사용 사례(기존 리포지토리 선택)에 최적 UX 제공.

옵션 D: Redis 캐시

외부 Redis를 리포지토리 데이터에 활용.

장점단점
서브밀리초 읽기추가 인프라
내장 TTL재시작 시 데이터 손실
인스턴스 간 공유운영 복잡성

기각: 과잉 엔지니어링; PostgreSQL로 충분.

결과

긍정적:

  • 캐시에서 리포지토리 목록 즉시 표시
  • 명시적 새로고침 또는 첫 방문 시에만 API 호출
  • 데이터 신선도에 대한 사용자 통제
  • 하이브리드 정규화로 사용자 간 조직 데이터 공유
  • Singleflight로 중복 진행 중 요청 방지
  • GitHub 장애 중에도 캐시가 요청 처리

부정적:

  • 새 리포지토리 새로고침까지 미표시 (새로고침 버튼으로 완화)
  • 사용자당 저장 비용 (사용자 삭제 시 정리)
  • 새로고침 패턴 학습 필요 (온보딩 툴팁)

스키마 설계 근거

하이브리드 정규화

테이블근거
전역 github_organizations조직은 공유 엔티티; 동일 조직 1000명 사용자가 메타데이터 1000번 복제 방지
사용자별 user_github_repositories가시성이 사용자별로 다름; 권한 차이
연결 테이블"이 사용자가 어떤 조직에 속해 있는지" 쿼리를 위한 깔끔한 N:N

UNIQUE (user_id, github_repo_id)

  • 중복 캐시 항목 방지
  • Upsert 패턴 활성화 (INSERT ... ON CONFLICT)
  • 참조 무결성 유지

source_type 컬럼

  • UI 그룹핑: "개인" vs "조직" 섹션
  • 조직 전용 뷰 쿼리 필터링
  • 향후 권한 로직 차별화

TTL 컬럼 없음

  • 사용자 제어 새로고침으로 자동 만료 불필요
  • TTL 확인 없는 단순 쿼리
  • 적용을 위한 백그라운드 작업 불필요

참조

Open-source test coverage insights