ADR-01: 스케줄 기반 재수집 아키텍처
| 날짜 | 작성자 | 리포지토리 |
|---|---|---|
| 2024-12-18 | @KubrickCode | collector |
배경
응답 시간 문제
ADR-05에서 초기 분석 요청을 위한 큐 기반 비동기 처리를 확립함. 이는 장시간 실행 작업 문제를 해결하지만, 지연이 발생함: 이전에 분석한 저장소를 요청할 때도 큐 처리를 기다려야 함.
저장소가 미리 분석되어 최신 상태로 유지되면, 사용자는 분석 대기 없이 즉각적인 응답을 받음.
사용자 경험 영향
| 시나리오 | 온디맨드만 | 사전 수집 적용 |
|---|---|---|
| 첫 방문 | 큐 대기 (예상됨) | 큐 대기 (예상됨) |
| 재방문 (신선) | 캐시에서 즉시 | 캐시에서 즉시 |
| 재방문 (오래됨) | 다시 큐 대기 | 즉시 (사전 갱신됨) |
| 인기 저장소 | 큐 대기 | 즉시 (사전 캐시 가능성 높음) |
핵심 통찰: 대부분의 사용자 요청은 이전에 분석한 저장소에 대한 것임. 사전 수집은 대다수 요청에서 큐 대기 시간을 제거함.
부가적 이점: 데이터 신선도
응답 시간 외에도, 사전 수집은 데이터 노후화도 해결함 (의존성 업데이트, 보안 패치, 코드 리팩토링).
핵심 요구사항
- 자동 업데이트: 이전에 수집한 저장소를 주기적으로 재분석
- 리소스 효율성: 비활성 저장소의 불필요한 재수집 방지
- 분산 환경 안전성: 다중 인스턴스 배포에서 중복 실행 방지
- 우아한 성능 저하: 연쇄 효과 없이 장애 처리
결정
적응형 감쇠 로직을 갖춘 스케줄러 기반 재수집 시스템 채택
핵심 원칙:
- 적응형 갱신 간격: 사용자 활동 기반 감쇠 알고리즘
- 분산 락: 단일 실행 보장을 위한 PostgreSQL 기반 락
- 서비스 분리: Scheduler가 Worker와 독립적으로 실행
- 서킷 브레이커: 연속 실패 시 자동 중단
검토한 옵션
옵션 A: 적응형 감쇠를 갖춘 스케줄러 (선택)
작동 방식:
- Cron 작업이 주기적으로 트리거
- Scheduler가 분산 락을 획득하여 중복 실행 방지
- 후보 조회: 설정된 기간 내 조회된 저장소
- 감쇠 알고리즘 적용: 최근 활동 → 더 빈번한 갱신
- 자격 있는 저장소를 작업 큐에 등록
감쇠 알고리즘 개념:
- 최근 조회된 저장소는 더 자주 갱신
- 유휴 시간이 증가하면 갱신 간격도 증가
- 임계값을 초과하면 유휴 상태로 간주하고 제외
장점:
- 실제 사용자 활동에 기반한 리소스 최적화
- 활성 저장소의 데이터 신선도 유지
- 방치된 저장소의 자동 갱신 중단
- 연속 실패 추적을 통한 장애 격리
단점:
- 간격 계산의 복잡한 로직
- 사용자 조회 타임스탬프 추적 필요
- 기준 임계값이 일부 사용 사례에서 너무 공격적일 수 있음
옵션 B: 고정 간격 갱신
- 활동과 무관하게 모든 저장소를 N시간마다 갱신
- 단순하지만 비활성 저장소에 리소스 낭비
옵션 C: 이벤트 기반 갱신
- 외부 이벤트(GitHub 웹훅)로 재수집 트리거
- 실시간이지만 웹훅 인프라 및 접근 권한 필요
구현 고려사항
서비스 아키텍처
| 컴포넌트 | 스케일링 전략 |
|---|---|
| Worker | 큐 깊이 기반 수평 확장 |
| Scheduler | 단일 활성 인스턴스 (락 보호) |
분리 근거:
- Worker 스케일링이 불필요한 스케줄러를 생성하지 않음
- Scheduler 변경이 Worker 재배포를 요구하지 않음
- Blue-green 배포가 분산 락으로 안전 유지
Private 저장소 처리
설계 결정: 스키마 필터링이 아닌 런타임 검증
저장소 가시성은 데이터베이스에 저장하지 않음. 언제든 변경될 수 있기 때문임 (public↔private). 대신 clone 시점에 런타임으로 검증함.
이 접근법의 이유:
| 관심사 | 해결책 |
|---|---|
| 토큰 관리 | Scheduler가 사용자 토큰 없이 동작 |
| 가시성 변경 | 오래된 가시성 플래그 유지 불필요 |
| 보안 | 동의 없이 백그라운드에서 private 코드 접근 안함 |
| 단순성 | 추가 스키마나 동기화 로직 불필요 |
동작 방식:
- Scheduler가 모든 자격 후보를 큐에 등록 (가시성 필터 없음)
- Worker가 비인증 clone 시도
- Private 저장소는 자연스럽게 실패
- 연속 실패 누적 → 결국 제외
참고: 저장된 사용자 토큰으로 private 저장소 재수집이 기술적으로는 가능함. 의도적으로 제외한 이유:
- 토큰 만료/취소 처리 복잡성
- 사용자가 저장소 접근 권한을 잃었을 수 있음 (조직 퇴사, 권한 취소)
- 프라이버시 우려: 명시적 사용자 행동 없이 백그라운드 접근
- 사용자의 GitHub 쿼터에 대한 Rate Limit 소비
에러 처리 전략
서킷 브레이커 패턴:
- Scheduler 수준: 연속 큐 등록 실패 시 배치 중단
- 저장소 수준: 연속 분석 실패 시 자동 갱신에서 제외
- 복구: 다음 주기에 새로 시작; 수동 재분석이 카운터 초기화
중복 방지
- 고유 윈도우가 설정된 기간 내 중복 등록 방지
- cron 지터 및 수동 등록 중복 처리
결과
긍정적
리소스 효율성:
- 활성 저장소는 빈번한 업데이트
- 비활성 저장소는 리소스 제로 소비
- 감쇠 알고리즘이 자연스럽게 배치 크기 제한
시스템 안정성:
- 분산 락이 단일 스케줄러 실행 보장
- 개별 저장소 실패가 다른 저장소에 영향 없음
- 일시적 장애는 큐 메커니즘으로 재시도
운영 단순성:
- 모니터링할 단일 스케줄러 인스턴스
- 실패 카운터를 통한 명확한 장애 신호
부정적
복잡도:
- 감쇠 알고리즘이 세심한 튜닝 필요
- 분산 락이 PostgreSQL 운영 의존성 추가
- 추적하고 이해할 다중 실패 카운터
제한사항:
- cron 간격에 의해 제한되는 최소 단위
- 하드 기준이 장기 비활성 후 복귀하는 사용자를 놓칠 수 있음
- 락 TTL이 최대 배치 처리 시간 제한
