돌고 돌아 퍼포스

큰 파일, 많은 파일, 바이너리 파일을 퍼포스만큼 잘 다루는 다른 도구는 없습니다.

돌고 돌아 퍼포스

깃(Git)이 세계에서 가장 많이 사용되는 형상관리도구라는 점을 부정할 생각은 없지만 다른 한편으로는 제가 여러 번에 걸쳐, 정확히는 다섯 번에 걸쳐 깃에 대한 불만을 말한 적이 있습니다. 물론 깃은 제가 불만을 가지고 있든 말든 여전히 세계에서 가장 널리 사용되며 어지간한 앱이 깃을 통해 배포되지 않는 경우를 찾기 더 어려울 지경입니다. 깃은 분산 환경에 적합한 모양으로 만들어졌지만 상황에 따라 원한다면 원격 서버 없이 완전히 로컬에서만 사용할 수도 있어 아주 간단한 버전관리만을 원하고 기계 여러 대를 사용할 계획이 없다면 아무런 환경 설정을 할 필요도 없이 로컬에 아무 디렉토리나 깃 환경으로 만들어 버전 관리를 할 수 있습니다.

게임 프로젝트에 참여하면서 몇몇 형상관리도구를 사용했는데 맨 처음에는 아예 형상관리도구를 사용하지 않던 시대도 잠깐 있었지만 이내 마이크로소프트 소스세이트라는 도구를 사용한 적이 있고 또 그 다음에는 SVN, 그 다음에는 퍼포스를 사용했는데 이들 중 가장 자주 사용한 형상관리도구는 퍼포스였습니다. 처음 퍼포스라는 도구를 사용한 것은 지난 10년의 밤에 소개한 규모가 큰 MMO 프로젝트에서였는데 처음 사용해 보긴 했지만 원격 파일시스템을 디팟, 로컬 파일시스템을 워크스페이스로 구분하고 항상 이들을 동기화 시킨다는 개념이 아주 단순해 쉽게 적응할 수 있었습니다.

또 퍼포스는 얼마나 많은 파일을 변경하든, 또 얼마나 큰 파일을 변경하든 항상 문제 없이 잘 동작했기 때문에 형상관리도구는 모두 적어도 이 정도로 동작한다는 선입견을 가지게 만들었습니다. 물론 퍼포스도 완벽하지는 않았는데 종종 알 수 없는 이유로 메타데이터 상에 나타나는 파일 정보와 실제 로컬 파일 사이에 차이가 생겨 올바른 버전을 받았는데도 빌드가 예상대로 실행되지 않을 때가 있었습니다. 이럴 때는 점심 먹으러 가기 전에 문제가 있을 것으로 예상되는 디팟 상의 특정 경로의 모든 파일을 강제로 다시 다운로드해 형상관리도구의 동작에 대한 지식이 없어도 쉽게 문제를 해결할 수도 있었습니다.

다른 개발 업계에서는 깃을 주로 사용했고 게임 업계에서는 SVN이나 퍼포스를 주로 사용한다는 사실을 알게 됐고 이런 차이가 생긴 가장 큰 이유는 게임 업계에서는 형상관리도구를 통해 바이너리 파일을 많이 다루기 때문이라는 점을 이해합니다. 특히 깃은 큰 파일, 많은 파일, 그리고 바이너리 파일을 잘 다루지 못했는데 애초에 바닐라 깃은 아주 작은 텍스트 파일만을 다룰 수 있었고 더 큰 파일, 그리고 바이너라 파일을 다루기 위해서는 LFS라는 일종의 익스텐션을 사용해야만 했습니다. 그런데 이런 환경은 적어도 윈도우에서는 아슬아슬하게 동작 했고 종종 오동작 해 형상관리도구 깃에 대한 지식이 부족한 여러 직군으로 구성된 개발팀에서 종종 스스로 빠져 나올 수 없는 문제를 일으켰습니다. 원인이 무엇이든 깃의 문제는 개발팀의 여러 구성원들이 스스로 형상관리도구로부터 겪는 문제 상황으로부터 빠져나오고 또 스스로 그런 문제상황에 빠지지 않도록 주의할 마땅한 방법이 없다는데 있었습니다.

가령 깃을 호스팅 해 주는 깃헙을 사용하면 네트워크 속도에 따라 메타데이터를 먼저 업데이트 한 다음 실제 파일을 업데이트 하다가 실패할 때가 자주 있었는데 그냥 상식적으로 뭔가 작업을 하다가 실패 했으면 실패한 작업을 스스로 취소하고 다시 시도할 절차를 제공하면 될 것 같았지만 깃과 깃헙은 절대 그렇게 행동하지 않습니다. 네트워크 속도에 따라 메타데이터를 업데이트 한 다음 실제 파일을 업데이트 하려다가 서버가 늦게 응답하거나 하는 온갖 원인에 의해 실제 파일을 업데이트 하는데 실패하면 깃은 무슨 일이 일어났는지 이해하지 못하고 메타데이터와 실제 파일이 달라졌으니 로컬의 모든 파일이 변경 되었다고 생각하고 앞에서 업데이트에 실패한 모든 파일을 스테이지에 올려놓은 다음 이들을 제가 직접 없애든지 아니면 커밋 하든지 할 것을 요구합니다. 그저 원격 파일을 받아 오려고 했을 뿐인데 한 번 실패한 다음 업데이트를 재시도 할 수 없는 상태로 만들어 버리는 행동은 여러 직군에 걸친 스탭들이 깃 풀을 무서워하게 만듭니다. 특히 이런 현상은 큰 파일, 아주 많은 파일, 그리고 바이너리 파일이 많이 포함된 커밋을 포함해 풀 받을 때 자주 일어났는데 이래서야 이 도구를 게임 개발에 사용할 수 있을지 의심스러웠습니다.

게임 개발에서는 소스코드와 데이터를 제외한 나머지 거의 모든 에셋이 바이너리 형식으로 저장됩니다. 이미지는 이미지 형식의 바이너리로, 3D 에셋 역시 전용 바이너리 형식으로 저장되며 효과음과 배경음악도 마찬가지입니다. 또 언리얼 환경에서 저장하는 거의 모든 에셋 역시 바이너리로 저장되며 언리얼 자체적으로 이전 리비전과 비교할 수는 있지만 언리얼 외부에서 이 서로 다른 리비전 파일들은 그저 속을 전혀 알 수 없는 바이너리 형식일 뿐이었습니다. 그래서 바이너리 파일을 잘 다루는 퍼포스를 주요 형상관리도구로 사용해 왔는데 퍼포스가 아무리 바이너리 파일을 잘 다루더라도 버전 관리는 일단 언리얼 엔진 환경으로 임포트 한 에셋에 대해서만 수행하고 그 바깥에 있는 저작도구로 저장한 원본 이미지와 3D 그래픽 등은 퍼포스를 통해 버전 관리를 잘 하지 않았습니다. 이들 마저 버전 관리 영역에 포함하면 제 아무리 퍼포스라도 이 거대한 바이너리 파일을 제대로 처리해내기 쉽지 않았을 겁니다.

이런 상황이다 보니 언리얼 환경에서도 언리얼 바깥에서 각자의 저작도구로 만든 에셋을 임포트 해 올 때 임포트 할 위치를 네트워크 위치로 설정한 다음 모두가 이 네트워크 위치를 기준으로 업데이트 된 에셋을 임포트 해 오는 기능이 포함되어 있는데 만약 언리얼 바깥의 에셋 마저도 버전 관리 대상이었다면 이런 기능이 필요하지 않았을 겁니다. 하지만 이 모든 파일을 버전 관리 영역으로 끌어들이기에는 게임 개발에 사용되는 바이너리 파일이 너무나도 많고 또 거대했기 때문에 저작도구로 만들어낸 원본은 별도 파일시스템에 저장하고 언리얼 엔진으로 임포트 한 에셋에 한해 버전 관리 하는 타협안을 찾은 셈이라고 할 수 있습니다.

또한 깃은 리파지토리 크기가 커지면 첫 클론을 하거나 많은 파일을 한 번에 풀 해 올 때, 또 아주 많은 커밋을 포함한 서로 거리가 먼 두 브랜치나 버전 사이를 오갈 때 자주 오동작해서 예상한 결과를 만들지 못했습니다. 가령 신규 스탭이 온보딩 하며 고작 몇 백 기가 짜리 리파지토리 클론을 시작했는데 몇 시간이 지나도 끝날 기색도 보이지 않자 지금 이 상태가 실행은 되고 있지만 단지 오래 걸리고 있을 뿐인지 아니면 뭔가 타임아웃 같은 상황이 일어나 이미 작업은 실패했지만 계속해서 뭔가 돌아가는 것처럼 보일 뿐이라 작업을 중단하고 재시작 해야 하는 것인지 판단하기 어려웠습니다. 윈도우에서 작업관리자를 열어 그래도 깃 프로세스가 뭔가 네트워크와 디스크를 사용하고 있는 것 같으니 실패는 아니라고 판단하고 그대로 놔 두기로 결정했고 그 다음다음날이 되자 클론을 마쳐 드디어 뭔가 실행해볼 수 있었습니다.

또 한번은 어떤 요구사항을 처리하기 위해 몇 달 전에 서비스한 버전으로 돌려 그 버전에서 영상을 찍어야 했습니다. 일단 깃 리파지토리 상에 그 버전이 있고 기술적으로 그 버전을 되돌릴 수 있음을 알고는 있었지만 지금까지 경험에 따라 과연 그 버전으로 되돌아가는데 시간이 얼마나 걸릴지, 또 깃이 과거 리비전으로 제대로 되돌아갈 수는 있을지 같은 주제로 이야기하다가 결국 이런 시도를 하는 대신 다른 방법을 모색하기로 결정 하기도 했습니다.

하지만 이런 상황 속에서도 어쨌든 깃은 고작 몇 백 기가 짜리 리파지토리를 적어도 최신 버전에 한해서는 아슬아슬하게 지원하고 있었고 각 팀마다 스탭들이 처한 문제해결을 도와주게 됐고 어쨌든 돌아는 가고 있었습니다. 또 개인적으로도 거의 1년에 걸쳐 깃에 대한 불평을 늘어놓았지만 나름 깃의 로컬과 리모트의 분리, 또 이렇게 분리된 환경에서 브랜치 운용 같은 개념이 나쁘지 않다고 생각해 개인 버전관리 (2023년 가을 버전)에 소개한 대로 깃을 사용하기 시작합니다. 처음에는 별 생각 없이 빗버킷을 사용했는데 무료로는 한 리파지토리에 4기가 제한이 있어 고민 끝에 훨씬 큰 용량을 사용할 수 있는 장난감 서버에 forgejo라는 프로그램을 설치해 개인 깃 서버를 만들어 사용했습니다. 흥미롭게도 깃은 로컬에서만 사용할 때도 깃 디렉토리 자체가 모든 이전 버전의 메타데이터와 모든 이전 버전의 실제 파일을 저장하고 있는 완전한 데이터베이스 환경이어서 개인 깃 서버로 이전할 때 할 일은 그저 .git 디렉토리 안에 있는 모든 파일을 서버로 복사 하기만 하면 됐고 이 경험은 굉장히 인상적이었습니다. 그리고 한동안 개인 깃 서버를 사용해 개인 파일을 관리하며 아슬아슬하지만 그럭저럭 원하는 기능을 사용하며 살았습니다.

하지만 여전히 깃은 커다란 파일, 많은 파일, 바이너리 파일을 잘 다루지 못했습니다. 가령 일하다가 가끔 사용하는 엑셀, 파워포인트, 비지오, 발사믹 목업 파일들은 처음 파일을 생성 하면 그리 크지 않지만 일 하다 보면 이런 파일은 순식간에 크기가 커집니다. 엑셀은 몇 만 라인에 걸친 데이터를 다루다 보면 순식간에 10메가를 넘겼고 파워포인트는 중요한 보고를 위해 다양한 디자인과 영상을 포함하다 보면 한 파일이 몇 기가에 달하는 일이 드물지 않았으며 비지오와 발사믹은 인터페이스 와이어프레임을 작성하기 위해 현재까지 개발된 게임 스크린샷을 포함하거나 다른 게임 스크린샷을 포함하다 보면 한 파일이 쉽게 수 십 메가에 달합니다. 여전히 깃은 이런 파일을 서버로 보내다가 자주 실패했고 직접 서버를 구축한 덕분에 스택오버플로우를 헤매며 비슷한 문제를 겪은 사람들의 실패 사례를 참고해 온갖 설정을 바꿔 봤지만 문제가 완화될 뿐 해결할 수는 없었습니다. 그렇게 로컬에만 존재하고 절대 서버에 푸시 할 수 없는 파일이 늘어나자 로컬 기준으로는 이들이 버전 관리 되고 있었지만 기계 여러 대에서 활용하지 못하는 마당에 차라리 그냥 드랍박스에 파일을 넣는 편이 더 낫겠다는 생각이 들었습니다.

회사에서는 이미 결정된 정책 상 어쩔 수 없이 미래에도 깃을 사용해 수많은 바이너리 파일을 아슬아슬하게 다루는 줄타기를 계속할 수밖에 없겠지만 개인 영역에서까지 그런 고통을 감내할 필요가 없다는 생각을 하기 시작합니다. 사용자는 한 명 뿐이니 빠르게 결정해 빠르게 깃을 포기하기로 합니다. 그리고 깃이 아닌 다른 형상관리도구를 선택해야 했는데 막상 선택하려고 보니 깃에 비해 바이너리 파일을 잘 다루는 조건을 걸든 말든 사용할 수 있는 적당한 형상관리도구는 두 가지 뿐인 것 같아 보였습니다. 하나는 SVN, 다른 하나는 퍼포스입니다. 사실 SVN 역시 오랜 세월에 걸쳐 개발 되어 오며 나쁘지 않은 환경을 갖춘 것 같고 사실 오래 전에 SVN을 사용하던 프로젝트에서도 무리 없이 사용한 적이 있었습니다. 다만 먼 과거에 SVN은 종종 깃처럼 오래된 버전으로 돌아가려고 할 때 종종 실패해서 사람을 당황하게 만들곤 했는데 현대에는 더이상 그런 결함이 없다 하더라도 굳이 직접 시행착오를 겪으며 그런 결함이 없어졌는지 직업 확인할 필요는 없어 보입니다. 그래서 결국 돌고 돌아 퍼포스를 선택합니다.

서기 2023년 겨울 현재 퍼포스는 직접 서버를 구축해 호스팅 할 수 있고 사용자 다섯 명, 총 20워크스페이스 까지는 무료입니다. 여러 디팟을 운용하고 기계가 여러 대라면 워크스페이스 수 제한은 미래에 문제를 일으킬 가능성이 0은 아니지만 이 정도면 무료로 사용하기에는 충분한 제한이라고 생각합니다. 한때 어떤 프로젝트에서는 퍼포스 사용 요금을 최소화 하기 위해 계정을 공유해 사용자 수를 극단적으로 줄이고 같은 계정을 사용하는 서로 다른 사용자가 각자 워크스페이스를 따로 만들어 워크스페이스 이름을 계정 이름처럼 사용하기도 했는데 워크스페이스 수 제한은 이런 사용 사례 때문에 생긴 것이 아닐까 싶습니다.

이전에 깃 서버를 구축하는데 사용한 forgejo에 비해 퍼포스 서버는 리눅스 환경에서 어처구니 없을 정도로 설치가 쉬웠습니다. 주로 기업을 상대로 장사하는 회사들은 설치, 업그레이드 같은 유지관리 환경을 잘 만들곤 했습니다. 가령 컨플루언스 위키 호스팅 버전은 새 버전을 설치할 때 윈도우 서버 환경에서 .exe로 된 인스톨러를 제공하기도 할 정도입니다. 퍼포스 역시 인스톨 스크립트를 통해 리눅스용 소프트웨어들이 종종 자신에게 필요한 의존성이 있는 소프트웨어 각각을 수동으로 설치하게 만들어 사용자를 난처하게 만드는 것에 비해 인스톨 스크립트 하나만으로 설치를 마칠 수 있었고 이 경험은 무척 만족스러웠습니다.

다만 서버 보안을 유지하는 건 좀 까다로울 수 있는데 처음 계획은 클라우드플레어 터널을 통해 서버를 인터넷에 노출 시키는 것이었지만 퍼포스 클라이언트는 리버스 프록시를 통해 노출된 서버에 접속하지 못하게 되어 있었습니다. 그래서 이 방법은 포기하고 미리 약속한 VPN 환경에서만 퍼포스 서버가 보이게 만들어 인터넷을 통해 서버를 노출하더라도 불특정 다수에게 서버를 노출하지는 않는 식으로 최소한의 보안을 유지하는데 만족하기로 했습니다.

그렇게 돌고 돌아 결국 개인 버전관리는 퍼포스를 사용하게 되었습니다. 퍼포스 클라이언트는 여전히 아주 오래 전 처음 볼 때와 비슷하게 생겼고 원격 디팟과 로컬 워크스페이스를 구분하고 이들 사이에 업데이트와 서브밋으로 나뉜 간결한 개념 역시 여전했습니다. 또 한번에 몇 만 파일을 업데이트 하든 바이너리 파일을 아무리 많이 올리든 말든 멀쩡하게 동작합니다. 깃처럼 메타데이터만 업데이트 하고 뻗어버리거나 파일시스템 상의 락 파일 하나를 제거 못해 뻗어버리거나 큰 파일을 보내다가 그냥 끊겨 아무 것도 못 하게 만드는 등의 괴상한 동작을 하지 않았습니다. 그냥 시키면 시키는 대로 많은 파일, 큰 파일, 바이너리 파일을 군소리 없이 디팟에 올리고 또 여러 기계에 걸친 서로 다른 워크스페이스에 받아 작업할 수 있었습니다. 과연 과거에 수 십 테라바이트에 달하는 디팟에 수 백 명에 달하는 사람들이 달라붙어 작업해도 멀쩡하게 돌아가던 그 환경 어디 가지 않는다 싶었습니다.

형상관리도구에 기대하는 기본적인 기능이 멀쩡하게 돌아가는 것만으로도 이렇게 마음이 편하고 든든한 느낌이 들 것을 알았다면 적어도 개인 영역에서는 진즉에 깃을 포기하고 검증된 퍼포스로 옮겼어야 한다는 생각이 듭니다. 일은 일이니까 어쩔 수 없지만 개인 영역에서까지 그런 고통을 감내할 이유는 없습니다. 코드를 만드는 사람이 아니면 주로 바이너리 파일을 만들게 되고 바이너리 파일을 개인 수준에서 버전 관리 하고 싶을 때 깃은 좋은 선택이 아닌 수준을 넘어 나쁜 선택이 확실합니다. 마치 굳이 멀쩡한 길 놔두고 옆에 있는 늪으로 걸어들어가 목만 내놓고 고통 받을 이유가 없습니다. 퍼포스는 오래 전이나 지금이나 큰 파일, 많은 파일, 그리고 바이너리 파일을 멀쩡하게 잘 다루며 서버를 구축하고 운영하기도 쉽고 깃에서 겪던 ‘아무 이유 없는’ 이상한 문제로부터 해방될 수 있습니다. 이렇게 돌고 돌지 말 것을 그랬습니다.

퍼포스 쓰세요. 사용자 다섯 명, 워크스페이스 20개 까지는 무료입니다.