Git은 이렇게 움직인다! 내부 구조와 객체 저장의 모든 것
여러분은 Git이라는 버전 관리 시스템을 사용하면서 한 번쯤은 ‘Git은 도대체 내부에서 어떻게 작동할까?’라는 궁금증을 가져본 적이 있으실 겁니다. Git은 단순히 파일을 저장하는 것 이상으로 굉장히 독특한 방식으로 데이터를 관리하는데요. 이번 글에서는 Git의 내부 구조와 객체 저장 방식을 아주 자세하고 쉽게 풀어 설명해 드리겠습니다.
1. Git, 단순한 버전 관리가 아닌 혁신적인 데이터 저장소
먼저 Git의 근본적인 특징부터 살펴볼게요. 대부분 버전 관리 시스템은 변경사항을 ‘파일 단위’로 추적합니다. 그러나 Git은 ‘스냅샷(snapshot)’ 방식을 채택했죠. 이 말은 단지 바뀐 파일만 저장하는 게 아니라, 특정 시점의 전체 프로젝트 상태를 저장한다는 뜻입니다. 이 스냅샷이 Git 내부에서는 여러 가지 객체 형태로 관리됩니다.
Git 내부에서는 크게 네 가지 객체 타입이 존재합니다: 블롭(Blob), 트리(Tree), 커밋(Commit), 태그(Tag)이 그것이죠. 각각은 서로 긴밀하게 연결되어 있어, 프로젝트의 모든 변화를 추적하고 복원할 수 있게 하는데 핵심적인 역할을 합니다.
2. Git 객체의 종류와 역할 이해하기
블롭(Blob)
블롭은 Git에서 가장 기본이 되는 객체입니다. ‘Blob’은 ‘Binary Large Object’의 줄임말로, 파일의 실제 내용 그 자체를 의미합니다. 파일 이름이나 경로 정보 없이, 오직 파일의 내용만 저장하는 원시 데이터죠. 그래서 동일한 내용의 파일은 어떤 경로나 이름을 가져도 하나의 블롭으로 저장됩니다. 이 때문에 효율적인 중복 제거가 가능합니다.
트리(Tree)
트리는 파일 및 디렉터리의 구조를 담는 객체입니다. 폴더 안에 어떤 파일과 디렉터리가 있는지를 기록한다고 생각하시면 됩니다. 트리는 각 블롭이나 다른 트리를 포인터 형태로 참조해서 전체 프로젝트의 파일 구조를 나타내죠. 즉, 트리가 모여 프로젝트 전체 폴더 트리 구조를 형성합니다.
커밋(Commit)
커밋 객체는 프로젝트의 특정 시점 상태를 가리킵니다. 커밋은 하나의 트리를 가리키며, 이 트리는 해당 시점 파일들의 전체 구조와 내용을 담고 있죠. 또 커밋에는 작성자, 커밋 메시지, 부모 커밋 정보가 포함되어 있어 변경 이력을 연결하고 분석할 수 있게 합니다.
태그(Tag)
태그는 주로 특별한 커밋에 이름을 붙일 때 사용합니다. 예를 들어 릴리즈 버전 같은 중요한 순간에 태그를 붙여 쉽게 참조할 수 있게 하죠.
3. Git 객체가 저장되는 방식 — 해시 기반 저장소
Git 객체들은 단순한 파일 시스템에 저장되는 게 아니라, 특별한 해시 기반 저장소에 보관됩니다. 각각 객체는 SHA-1 해시 값을 가지는데, 이 값이 바로 객체의 고유 식별자 역할을 합니다. 예를 들어, 어떤 한 파일의 내용이 해시로 변환되어 ‘a7d8f6…’와 같은 형태를 가지면, 이 해시가 바로 그 객체를 참조하는 키가 됩니다.
이런 해시를 기반으로 저장하니, 왜 중복 파일이 저장되지 않는지 알 수 있겠죠? 내용이 같다? 그러면 해시도 같으니까 새로운 객체 저장 없이 기존 객체를 참조하는 거니까요. 또 해시는 무결성을 보장하는 데도 탁월합니다. 파일이 조금이라도 변하면 해시 값이 완전히 바뀌기 때문에, 파일이 변했는지 여부를 쉽게 감지할 수 있습니다.
Git의 저장소 내부에는 .git/objects 폴더가 있는데, 이 안에 객체들이 해시 이름으로 폴더와 파일 형태로 저장됩니다. 이때 해시값의 앞 두 글자는 폴더명으로, 나머지 글자들은 그 안에 파일명으로 쓰입니다. 예를 들어, 해시가 a7d8f6…라면 .git/objects/a7/d8f6… 이런 식으로 경로가 만들어지는 거죠.
4. 스냅샷이 아니라 델타 기반의 저장소로 오해하기 쉬운 점
많은 분들이 Git이 변경된 내용만 저장하는 델타 기반이라고 생각하기 쉽습니다. 사실 SVN이나 CVS 같은 전통적인 시스템이 델타 방식을 쓰죠. 하지만 Git은 단순히 델타가 아니라 ‘스냅샷’ 방식입니다. 하지만 Git도 내부 최적화를 위해 아주 효율적인 방법을 사용합니다. 예를 들어, packfile이라는 압축된 저장소 형태는 여러 객체를 묶고 중복된 데이터를 줄여 저장해 속도와 저장 공간 효율을 크게 높입니다.
Packfile은 내부적으로 델타 압축을 활용해 비슷한 객체들 간의 차이점만을 저장하기도 하지만, 기본 저장 원리는 언제나 전체 스냅샷을 위해 만들어진 객체들입니다.
5. Git 내부 구조를 알면 버전 관리를 더 잘할 수 있다
Git을 사용할 때 무심코 명령어를 적용하는 것과, 내부 구조를 이해하고 활용하는 것은 차이가 큽니다. 왜냐하면 내부 구조를 알면 명령어가 실제로 무슨 일을 하는지, 어떤 상황에 어떤 옵션을 써야 하는지 감이 잡히니까요.
예를 들어, git stash가 내부적으로 임시로 커밋을 만들어 저장하는 개념이거나, git reflog로 과거 참조를 찾는 이유가 커밋 객체들의 연결 방식을 활용하는 것이라는 점을 이해하면 복잡한 상황에서 문제를 해결하는 데 큰 도움이 됩니다.
또, 프로젝트의 히스토리가 꼬였다 싶을 때 git fsck처럼 내부 객체 무결성을 검사하는 도구도 더 친근하게 다가올 수 있습니다.
6. 요약하며: Git 내부 구조와 객체 저장 방식은 어떠한 장점이 있을까?
전체 프로젝트의 스냅샷을 저장하기 때문에 과거 어느 시점이든 간편하게 복원 가능
동일한 내용은 블롭 객체를 재활용하여 저장 공간 절약
해시 기반 무결성 검증으로 데이터 훼손 걱정 최소화
트리와 커밋 객체를 통해 파일 구조와 변경 내역을 명확히 관리
packfile 통한 압축과 델타 저장으로 효율성 극대화
Git은 그 자체가 하나의 작은 데이터베이스이자 시간 여행 장치와 같습니다. 파일 하나하나를 중복 없이, 또 체계적으로 저장하며, 언제든 원하는 순간으로 돌아갈 수 있게 해주죠. 이번 글을 통해 Git 내부의 객체 저장 방식을 이해하시면, 단순히 툴로서 사용하던 Git이 내 손 안의 강력한 무기가 될 것입니다.