Skip to content

ADR-08: External Repository ID 기반 데이터 무결성

🇺🇸 English Version

날짜작성자리포지토리
2024-12-22@KubrickCodeall

Context

문제

현재 리포지토리 식별 방식: UNIQUE (host, owner, name) 제약조건 사용. 여러 시나리오에서 실패:

시나리오: 삭제 후 재생성

1. 리포 A: alice/my-repo (external_repo_id: 100) → 분석 완료
2. 리포 A 삭제
3. 리포 B 생성: alice/my-repo (external_repo_id: 200)
4. Scheduler가 alice/my-repo 재분석 요청
5. Clone 성공 (리포 B)
6. 분석 결과가 리포 A의 row에 저장
   → 데이터 오염!

추가 시나리오

시나리오문제
Rename (alice/old → alice/new)새 이름으로 요청 시 히스토리 단절
Transfer (alice/repo → bob/repo)owner 변경 시 히스토리 단절
삭제 후 재생성다른 리포 데이터가 기존 히스토리에 오염

목표

  1. 데이터 무결성: 잘못된 리포지토리의 분석 결과가 저장되는 것 방지
  2. 히스토리 연속성: rename/transfer 시 분석 히스토리 유지
  3. API 효율성: VCS API 호출 최소화 (rate limit 고려)

Decision

이중 검증 메커니즘 채택: external_repo_id로 식별 + git fetch SHA로 무결성 검증

핵심 원칙

재분석 시 API 호출 없이 git fetch <last_commit_sha>로 무결성 검증

메커니즘 조합

메커니즘용도API 필요
external_repo_idRename/Transfer 시 히스토리 연결Yes (신규 분석)
git fetch <sha> 검증같은 리포인지 확인No

git fetch SHA 검증

bash
# 마지막으로 분석한 commit이 현재 리포에 존재하는지 확인
git fetch --depth 1 origin <last_commit_sha>

# 결과
# - 성공: 같은 리포 (해당 commit 존재)
# - 실패: 다른 리포 (삭제 후 재생성) 또는 force push

에러 메시지 (존재하지 않는 경우):

fatal: remote error: upload-pack: not our ref <sha>

Options Considered

Option A: 항상 VCS API 호출 (기각)

설명: 모든 분석 시 API 호출로 리포지토리 ID 확인 및 검증.

장점:

  • 단순한 구현
  • 항상 정확

단점:

  • Rate limit 소진 (GitHub 5000/hr)
  • 지연 시간 증가
  • 빈번한 재분석에 확장성 부족

Option B: git fetch SHA만 사용 (기각)

설명: external_repo_id 없이 git fetch 검증만 사용.

장점:

  • API 호출 0회
  • 단순

단점:

  • Rename/transfer 감지 불가 (히스토리 단절)
  • Force push와 삭제 후 재생성 구분 불가

Option C: 이중 메커니즘 (선택)

설명: external_repo_id 저장과 git fetch SHA 검증 조합.

장점:

  • 필요 시에만 API 호출 (신규 분석, 검증 실패)
  • external_repo_id로 rename/transfer 감지
  • Force push vs 삭제 후 재생성 구분
  • 확장 가능 (대부분의 재분석은 API 호출 불필요)

단점:

  • 구현 복잡도 증가
  • 스키마 변경 필요

Implementation

케이스 분류

케이스조건결과
ADB에 없음, external_repo_id도 없음새 codebase 생성
BDB에 있음, git fetch 성공기존 codebase 재분석
DDB에 없음, external_repo_id는 존재owner/name 업데이트
EDB에 있음, git fetch 실패, ID 다름stale 마킹 + 새 생성
FDB에 있음, git fetch 실패, ID 같음Force push, 재분석

흐름

[분석 요청: owner/repo]

      ├─ 1. Clone

      ├─ 2. DB 조회 (owner, repo)
      │      │
      │      ├─ 없음 ──────────────────────────────┐
      │      │                                     │
      │      └─ 있음                               │
      │           │                               │
      │           ├─ 3. git fetch <last_sha>      │
      │           │      │                        │
      │           │      ├─ 성공                  │
      │           │      │    → 분석 진행         │
      │           │      │                        │
      │           │      └─ 실패                  │
      │           │           │                   │
      │           │           ▼                   │
      │           └───────────┴───────────────────┤
      │                                           │
      │                       ┌───────────────────┘
      │                       │
      │                       ▼
      │              4. VCS API 호출
      │                 → external_repo_id
      │                       │
      │                       ▼
      │              5. DB 조회 (external_repo_id)
      │                       │
      │              ┌────────┴────────┐
      │              │                 │
      │           있음               없음
      │              │                 │
      │              ▼                 ▼
      │        owner/name         새 codebase
      │          업데이트             생성
      │              │                 │
      │              └────────┬────────┘
      │                       │
      │                       ▼
      └──────────────→ 6. 분석 & 저장

스키마 변경

sql
-- 컬럼 추가
ALTER TABLE codebases ADD COLUMN external_repo_id VARCHAR(64);
ALTER TABLE codebases ADD COLUMN is_stale BOOLEAN DEFAULT false;

-- owner/name partial unique 인덱스 (stale 제외)
CREATE UNIQUE INDEX idx_codebases_owner_name
ON codebases(host, owner, name)
WHERE is_stale = false;

-- external_repo_id unique 인덱스
CREATE UNIQUE INDEX idx_codebases_external_repo_id
ON codebases(host, external_repo_id);

VARCHAR(64) 선택 이유:

플랫폼타입예시
GitHubBIGINT123456789
GitLabINTEGER12345678
BitbucketUUID{550e8400-e29b-41d4-a716-446655440000}

모든 타입을 문자열로 통일하여 저장.

Race Condition 처리

Clone-Rename Race:

T1: Worker가 alice/old-repo clone
T2: 사용자가 alice/old-repo → alice/new-repo로 rename
T3: Worker가 clone 완료 (old-repo 코드)
T4: Worker가 API 호출 → external_repo_id: 100
T5: DB에서 id=100 조회 → alice/new-repo로 변경됨
T6: Worker가 old-repo 코드를 new-repo에 저장
    → 데이터 오염!

해결책: Clone 시점의 owner/name과 API 조회 결과 비교

go
if existingCodebase.Owner != req.Owner || existingCodebase.Name != req.Name {
    return ErrRaceConditionDetected // 재시도 유도
}

동시 분석 요청:

  • (host, external_repo_id) unique 제약조건으로 중복 생성 방지
  • Application layer에서 케이스별 분리 처리 (UPSERT 사용 금지)

Stale 정책

항목
보존 기간30일
UI 표시"리포지토리가 더 이상 존재하지 않습니다"
자동 삭제30일 후

Consequences

Positive

데이터 무결성:

  • 삭제 후 재생성 시나리오 올바르게 처리
  • Force push와 식별 변경 구분
  • Rename/transfer 시 히스토리 보존

효율성:

  • 대부분의 재분석은 API 호출 불필요
  • Rate limit 부담 최소화
  • 수백만 리포지토리로 확장 가능

경쟁 우위:

  • Codecov/Coveralls와 달리 rename 시 자동 히스토리 연결
  • 수동 재설정 불필요

Negative

복잡도:

  • 6가지 케이스 분류 구현 필요
  • 스키마 마이그레이션 필요
  • Race condition 처리 필요

마이그레이션:

  • 기존 codebase에 external_repo_id 백필 필요
  • 단계적 배포 필요 (nullable → 백필 → NOT NULL)

플랫폼 의존성:

  • Bitbucket Cloud git fetch SHA 지원 불확실
  • GitLab self-hosted는 uploadpack.allowReachableSHA1InWant 설정 필요

플랫폼 지원

플랫폼git fetch SHA테스트 결과
GitHub지원직접 테스트 완료
GitLab지원직접 테스트 완료
Bitbucket Server지원 (v5.5+)문서 확인
Bitbucket Cloud불확실추후 테스트 필요

API 호출 빈도

케이스API 호출빈도
신규 분석1회낮음
재분석 (정상)0회높음
Scheduler (정상)0회높음
삭제 후 재생성1회매우 낮음
Force push1회매우 낮음
Rename/Transfer1회매우 낮음

대부분의 케이스에서 API 호출 불필요 → Rate limit 부담 최소화

References

Open-source test coverage insights