git가 내 푸시를 거부한 날
어느 날, git이 내 작업을 더 이상 저장하지 못하게 만들었다. 한 번도 본 적 없는 서버 오류와 함께 푸시가 실패했다 — 병합 충돌도, 권한 문제도 아니고, 그저 반대편에서의 단호한 거부였다. 나는 몇 주 동안 즐겁게 커밋해 왔는데, 이제 하루 중 가장 일상적인 명령어가 완료되지 않았다.
원인은 git의 버그가 아니었다. 그 몇 주 동안 내가 조용히 저장소에 거기 있을 이유가 전혀 없는 수천 개의 것들을 욱여넣었고, 결국 들어 올리기엔 너무 무거워진 것이었다.
나가지 않으려는 푸시
처음엔 우연이라고 짐작했다 — 네트워크 일시적 장애, 서버가 잠깐 안 좋았던 순간. 하지만 그것은 계속, 어김없이, 매번 일어났다. 저장소에 쌓인 덩치가 원인인 것 같았다: 푸시는 매번 똑같은 방식으로 계속 실패했고, 아무리 재시도해도 일시적이 아니라 구조적인 문제를 고칠 수는 없었다.
마침내 저장소가 실제로 무엇을 담고 있는지 들여다봤을 때, 그 답은 부끄러웠다. 코드 — 저장소가 존재하는 이유인 그것 — 는 그중 작은 소수에 불과했다. 압도적인 덩치는 데이터였다: 수천 개의 생성된 파일, 캐시, 컴파일된 산출물, 그 하나하나가 충실히 추적되고, 버전 관리되고, 모든 작업마다 함께 끌려다니고 있었다.
저장소가 조용히 창고가 되는 법
아무도 기가바이트의 데이터를 버전 관리에 넣겠다고 결정하지 않는다. 그것은 한 번에 한 걸음씩, 그럴듯해 보이는 단계를 거쳐 일어난다. 파일을 몇 개 생성하면 그것들이 프로젝트 폴더에 떨어진다. add 명령을 실행하면, 어쩌면 조금 부주의하게, 그것들이 딸려 온다. 작동한다. 아무것도 불평하지 않는다. 그래서 또 하고, 또 하고, 저장소는 서랍이 잡동사니를 쌓듯 데이터를 쌓아간다 — 보이지 않게, 닫히지 않는 날이 올 때까지.
git이 내 푸시를 거부했을 무렵, 나는 수천 개의 데이터 파일을 추적하고 있었다. 나는 그것들을 버전 관리에 넣겠다는 의식적인 선택을 한 적이 결코 없었다. 그저 그것들을 빼두겠다는 선택을 한 적이 한 번도 없었을 뿐이다.
데이터와 코드는 정반대를 원한다
둘을 섞는 것이 해로운 이유는, 그것들이 완전히 다른 이유로 버전 관리되기 때문이다. 코드는 작고, 의미가 있으며, 한 줄 한 줄 보고 싶은 방식으로 바뀐다 — git은 바로 그것을 위해 만들어졌다. 데이터는 크고, 불투명하며, 통째로 바뀌는 경향이 있다; 그것의 한 줄 한 줄 diff는 거의 아무것도 말해주지 않고, 과거의 모든 버전을 영원히 저장하는 것은 대부분 낭비다. git은 첫 번째 일에는 훌륭하다; 그 표준 히스토리는 두 번째 일에는 흔히 잘 맞지 않는다, 적어도 데이터가 커지거나 끊임없이 바뀌게 되면 말이다.
둘을 합치면 양쪽의 최악을 얻는다. 당신의 히스토리는 결코 의미 있게 읽을 수 없는 거대한 변경들로 부풀고, 모든 클론과 푸시가 그 전부를 끌고 다니며, 작고 소중한 것 — 실제 소스 — 은 대부분이 바닥짐인 저장소 속에 파묻혀버린다.
해결책: 건너뛴 경계를 그어라
수리는 개념적으로는 사소했지만 약간 지루했다: git에게 무엇을 추적하지 말아야 하는지 명시적으로 알려주는 것. 생성된 파일, 캐시, 컴파일된 산출물 — 그 전부가 제외되었고, 저장소는 처음부터 마땅히 그래야 했던 모습으로 돌아갔다: 내가 의도적으로 저술한 것들 — 코드, 설정 (config), 문서, 테스트 — 의 보금자리, 대량으로 생성된 출력물이 아니라. 데이터는 크고 재생성 가능한 것들이 있어야 할 곳으로 옮겨졌다 — 필요할 때 생산되거나, 큰 파일을 위해 만들어진 어딘가에 저장되되, 다시는 소스인 양 한 줄 한 줄 버전 관리되지 않도록.
푸시는 첫 시도에 통과했다. 그 해결책은 영리하지 않았다. 그것은 그저 내가 첫날 그었어야 했지만 한 번도 긋지 않았던 경계였을 뿐이다.
그 밑에 깔린 습관: 소스 대 파생물
오래 남는 교훈은 사실 git에 관한 것이 아니다. 그것은 이제 내가 프로젝트가 만들어내는 모든 파일에 대해 던지는 질문이다: 내가 이것을 저술했는가, 아니면 내 코드가 이것을 생성했는가? 이것은 소스 — 사람이 쓴, 대체 불가능한 것 — 인가, 아니면 파생물 — 소스를 다시 실행하면 재생성될 수 있는 출력 — 인가?
소스는 일반적으로 버전 관리에 속한다. 크거나 덩치 큰 파생 파일들은 보통 그렇지 않다 — 그것들은 재현성이나 워크플로가 진정으로 요구할 때만 커밋되어야 하며, 이상적으로는 언제든 소스로부터 재현 가능한 상태로 남아야 한다. 관리 불가능한 난장판으로 썩어가는 많은 저장소들은 바로 그 구분을 놓침으로써 그렇게 된다 — 파생물을 소중한 것인 양 취급하고, 언제든 재생성될 수 있는 것들을 결코 대체될 수 없는 것들 옆에 쌓이게 내버려둠으로써.
그날 git은 고장 나지 않았다. 그것은 그저 내가 소스와 출력을 각각의 분리된 자리에 두는 일을 그만뒀다는 것을, 그리고 결코 그렇게 만들어지지 않은 일을 한 도구에게 시키고 있었다는 것을 마침내 깨닫게 해주었을 뿐이다.
— 신호도, 수익도 없으며, 투자 자문이 아닙니다.