템플릿 둘러보기/깃허브 (GitHub)

깃허브 (GitHub)

사용자 계정, 조직, 저장소, 브랜치, 커밋, 풀 리퀘스트, 코드 리뷰, 이슈, 라벨, 마일스톤, 릴리스, 스타, 포크 및 Actions 워크플로우를 포함하는 깃허브 스타일의 코드 호스팅 플랫폼을 위한 종합적인 데이터베이스 스키마입니다.

PostgreSQL16개 테이블웹 앱
developer-toolsgitcollaborationopen-sourceci-cd
ERD Studio로 제작

이 스키마에 대하여

저장소는 사람에게 속할 수도 조직에 속할 수도 있고, 이 스키마는 두 포인터 — owner_id 와 org_id — 를 같은 행에 두고 하나만 채웁니다. 거의 모든 코드 호스팅이 도달하는 모양입니다. 사용자에서 조직으로 옮긴 저장소가 정체성과 스타와 이슈를 그대로 가져가야 하기 때문입니다.

이슈와 풀 리퀘스트는 저장소 단위 번호를 각각 들고 있는 별개 테이블입니다. 합칠 수 있어 보이지만, PR 에는 브랜치와 병합 상태와 리뷰가 있고 이슈에는 담당자와 마일스톤이 있습니다. 번호 순번을 둘이 공유하는 부분만은 스키마 바깥에서 지켜 줘야 합니다.

Git 자체는 모델링하지 않습니다. commits·branches·releases 는 호스팅이 보여 줘야 하는 것 — 누가, 언제, 몇 줄 — 만 기록하고 객체 그래프는 디스크의 저장소에 남습니다. 의도한 경계입니다. 모든 커밋의 트리를 담으려는 데이터베이스는 Git 이 이미 잘하는 일의 느린 사본이 됩니다.

눈여겨볼 관계

repositories → users / organizations
owner_id 와 org_id 중 하나. 소유권 이전이 새 저장소가 아니라 이 행의 갱신이라, 스타와 이슈가 따라갑니다.
repositories → repositories
forked_from_id 는 자기참조입니다. 포크는 자기 스타와 이슈를 가진 독립된 저장소이면서 어디서 왔는지를 기억합니다.
pull_requests → pr_reviews
리뷰가 각자 state 를 가진 행이라, 한 PR 이 어떤 리뷰어에게는 승인이고 다른 리뷰어에게는 변경 요청인 상태가 동시에 가능합니다.
issues → labels (issue_labels)
라벨이 전역이 아니라 저장소별입니다. 두 프로젝트가 서로 다른 색의 'bug' 라벨을 갖고, 한쪽이 다른 쪽 이름을 바꿀 수 없습니다.
actions_workflows → actions_runs
워크플로는 정의, 런은 자기 status·conclusion·커밋을 가진 한 번의 실행입니다. 워크플로 파일을 지운다고 무엇을 했는지의 기록까지 사라지면 안 됩니다.

설계 판단

이슈와 PR 은 별개 테이블

번호 순번과 댓글 흐름을 공유해서 합치고 싶어집니다. 그런데 PR 에는 head·base 브랜치, 초안 플래그, 병합 시각, 리뷰가 붙고, 이슈에는 그런 것이 없는 대신 마일스톤과 담당자가 있습니다. 한 테이블이면 모든 행의 절반이 null 이고 모든 질의가 종류를 걸러야 합니다. 나눈 대가는 공유 번호를 스키마 밖에서 발급해야 한다는 것, 그리고 댓글이 둘 중 하나를 가리켜야 한다는 것입니다.

저장소가 소유자 컬럼을 둘 든다

owner_id 와 org_id 중 정확히 하나만 채우는 방식은 가장 단정한 모델은 아닙니다 — 다형 소유자 테이블이 더 엄격합니다. 이렇게 두는 이유는 둘 사이의 소유권 이전이 흔하고 그때 저장소의 정체성이 바뀌면 안 되기 때문이며, 거의 모든 질의가 추상적 소유자가 아니라 둘 중 하나로 거르기 때문입니다.

커밋은 객체 그래프가 아니라 기록

commits 는 sha·message·additions·deletions·files_changed 를 담습니다. 목록 화면이 보여 주는 것들입니다. 트리나 블롭이나 부모는 담지 않습니다. 관계형 테이블에서 히스토리를 복원하는 것이 Git 에 묻는 것보다 느리고, 그 데이터는 저장소와 어긋날 수 있는 두 번째 사본이기 때문입니다.

브랜치는 행이고, 헤드는 비정규화한다

branches.commit_sha 는 Git 에서 꺼내 온 브랜치 헤드입니다. 저장소 페이지가 브랜치와 마지막 커밋을 ref 를 훑지 않고 나열할 수 있게 해 줍니다. 캐시라서 푸시보다 늦을 수 있고, 진실은 저장소에 있습니다.

이 템플릿의 테이블

users사용자
10 cols
organizations조직
9 cols
org_members조직 멤버
6 cols
repositories저장소
14 cols
branches브랜치
8 cols
commits커밋
11 cols
pull_requests풀 리퀘스트 (PR)
14 cols
pr_reviewsPR 리뷰
8 cols
issues이슈
12 cols
labels라벨
7 cols
issue_labels이슈 라벨 연결
5 cols
milestones마일스톤
9 cols
stars스타
5 cols
releases릴리스
10 cols
actions_workflowsActions 워크플로우
9 cols
actions_runsActions 실행 기록
10 cols

자주 묻는 것

이슈와 PR 을 왜 안 합치나요?
PR 에는 이슈에 없는 브랜치·병합 상태·리뷰가 있고, 이슈에는 PR 이 다르게 쓰는 마일스톤과 담당자가 있습니다. 합치면 컬럼 절반의 의미가 종류 플래그에 달린 넓은 테이블이 됩니다. 나눌 때 어려워지는 것은 공유 번호 순번 하나뿐입니다.
저장소별 이슈 번호는 어떻게 유일하게 유지하나요?
이 스키마가 해 주지 않습니다. number 가 repo_id 범위이므로 (repo_id, number) 유니크 제약이 필요하고, 다음 값을 이슈와 PR 이 함께 합의해서 발급해야 합니다 — 보통 저장소 행의 카운터를 같은 트랜잭션에서 올립니다.
이슈와 PR 의 댓글은 어디 있나요?
이 템플릿에는 없습니다. 넣으려면 댓글 테이블 하나가 둘을 가리키게 할지 — nullable 컬럼 둘, 저장소 소유자와 같은 모양 — 각각 따로 둘지 정해야 합니다. 어느 쪽이든 댓글은 똑같이 그려지므로 하나로 두는 편이 보통입니다.
branches 테이블을 빼도 되나요?
늘 Git 에 묻는다면 됩니다. 이 테이블은 브랜치와 헤드를 나열하는 일이 ref 조회 묶음이 아니라 질의가 되게 하려고 있습니다. 브랜치가 많은 저장소의 페이지에서 차이가 납니다. 푸시가 갱신하는 캐시로 보세요.
actions_runs 가 왜 커밋 id 대신 sha 를 저장하나요?
런이 커밋 행보다 오래 남을 수 있고, 무엇이 실행됐는지를 가리키는 것이 sha 이기 때문입니다. commits 를 가리키면 아직 색인되지 않은 커밋에 대한 실행을 기록할 수 없습니다.