GitLab 저장소 백업 시간을 48시간에서 41분으로 단축한 방법
(about.gitlab.com)- GitLab은 대용량 저장소 백업 시 오래 걸리는 문제를 발견하고 개선 작업을 수행함
- 핵심 원인은 15년 된 Git 함수의 O(N²) 복잡도였으며, 알고리듬 최적화를 통해 성능을 대폭 향상시킴
- 최적화 결과, 가장 큰 저장소의 백업 시간이 48시간에서 41분으로 줄어듦
- 개선된 방식은 리소스 효율성과 신뢰성을 제공함과 동시에 다른 Git 기반 툴과 커뮤니티에도 긍정적인 영향을 줌
- GitLab 18.0 버전부터 모든 사용자들이 추가 설정 없이 이러한 이점을 누릴 수 있게 됨
저장소 백업의 중요성과 과제
- 저장소 백업은 비상 복구 전략의 핵심 요소임
- 저장소 크기가 커질수록 신뢰할 만한 백업 과정의 복잡성이 증가함
- GitLab의 자체 Rails 저장소는 백업에 48시간이 소요되어, 백업 빈도와 시스템 성능 사이에서 선택의 어려움이 있었음
- 대규모 저장소에서 발생하는 시간, 리소스, 실패 위험, 레이스 컨디션 등 다양한 문제가 존재함
- 예약 백업이 어렵거나, 외부 도구 의존 또는 백업 횟수 감소 등 조직별로 일관적이지 못한 전략이 나오게 됨
성능 병목 현상 분석과 원인 파악
- GitLab의 백업 기능은
git bundle create
명령을 활용하여 저장소 스냅샷을 생성함 - 이 명령의 구현 과정에서 reference(레퍼런스) 수 증가에 따라 성능 병목이 발생했음
- 수백만 개의 레퍼런스를 가진 대형 저장소의 백업에 48시간 이상이 소요되는 경우가 발생함
원인 분석
- 명령 실행 중 Flame Graph 분석으로 병목 구간 확인
- 레퍼런스가 10,000개일 때, 실행 시간의 약 80%가
object_array_remove_duplicates()
함수에서 소비됨 - 이 함수는 2009년에 해당 커밋에서 중복 레퍼런스 처리 목적으로 도입된 것임
-
git bundle create
사용 시 중복된 레퍼런스가 포함될 경우 문제 발생을 방지 - 그러나 중복 검출이 중첩 for 루프 형태로 되어 있어 O(N²) 복잡도가 생김
- 레퍼런스 수가 많아질수록 병목이 심화되는 구조임
-
O(N²)에서 효율적 매핑으로의 전환
- GitLab은 중첩 루프 대신 맵 자료구조를 이용한 방식으로 Git에 패치를 기여함
- 각 레퍼런스를 맵에 추가하여 자동으로 중복을 제거하고, 효율적으로 처리함
- 이 변경으로
git bundle create
성능이 대폭 향상되고, 대규모 레퍼런스 환경에서도 확장 가능해짐 - 벤치마크 결과, 레퍼런스 10만 개 기준 6배 이상의 성능 개선 확인
대폭 줄어든 백업 시간과 효과
- 최대 저장소 백업 소요 시간이 48시간에서 41분(1.4% 수준)으로 감소함
- 저장소 크기와 무관하게 일관된 성능 제공이 가능함
- 서버 부하 감소, 백업 기반 Git 명령 전반의 성능 향상 등 부가 이점 확보
- 해당 패치는 업스트림 Git에 적용됐고, GitLab에서는 즉시 적용해 고객의 빠른 경험이 가능하도록 함
GitLab 고객을 위한 실제 변화
- 대기업 고객은 연속된 밤샘 백업을 개발 워크플로와 충돌 없이 실행할 수 있게 됨
- 복구 목표 지점(RPO) 이 줄어든 덕분에, 재해 상황에서 데이터 손실 위험이 대폭 축소됨
- 리소스 소비와 유지보수 시간, 클라우드 비용 등 운영 오버헤드까지 줄어듦
- 저장소 규모가 증가해도, 백업 빈도와 시스템 성능 사이에서 고민하지 않아도 됨
- 이제 모든 GitLab 사용자는 구성 변경 없이 이러한 이점을 누릴 수 있음
다음 단계 및 의미
- 이번 개선은 확장성 높은 엔터프라이즈급 Git 인프라 구축에 대한 GitLab의 지속적인 노력의 일부임
- GitLab이 기여한 변경사항은 업스트림 Git 프로젝트에도 반영되어, 업계 전반과 오픈소스 커뮤니티에 긍정적 파급 효과를 미침
- 이 같은 인프라 개선 노력이 다른 핵심 성능 개선 작업의 모델로 자리 잡고 있음
GitLab의 성능 접근 방식에 대한 더 깊은 이야기는 GitLab 18 버추얼 런치 이벤트에서 확인 가능함
Hacker News 의견
-
GitLab이 Git에 기여한 성능 향상 코드는 v2.50.0에 릴리즈 예정인 정보 공유 관련 커밋 링크
-
직접 경험을 통해, 내가 작성한 코드에서 n^2 연산을 제거하는 것이 항상 올바른 선택이었음을 강조. 특별한 알고리즘을 작성하지 않아도, 아주 작은 n값에서도 문제가 쉽게 드러나는 점에 놀라움을 느낌
-
Bruce Dawson의 첫 번째 컴퓨팅 법칙 인용: O(n^2)는 최악의 확장성 문제를 일으키는 지점. 프로덕션에선 충분히 빠르고, 막상 배포되면 치명적인 성능 저하가 발생하는 부분 관련 글 링크
-
O(N^2) 시간복잡도는 테스트에서는 빠르게 보이지만, 프로덕션에서 심각하게 문제가 터지는 상황을 여러 번 목격한 개인 경험 공유
-
내 경험상 대다수 문제(80~90%)에선 복잡한 알고리즘이 필요하다면 데이터 모델 자체가 올바르지 않다는 신호임. 컴파일러, DB 내부, 경로 기획 등 특별한 일부 경우에만 복잡한 알고리즘이 본질적으로 필요하다고 생각
-
n이 10 미만의 소규모 하드웨어 한정 케이스만 예외로 언급(CAN 인터페이스 등), 만약 하드웨어 교체 없이 n이 늘어날 수 있으면 반드시 n^2 연산을 피해야 하며, 설계상 제한하거나 사전 감지를 통해 재설계 필요성의 인식 권고
-
본인은 n^3 연산 때문에 단 10,000개 요소에서 병목현상이 발생하는데, 아직 해결 방법을 찾지 못한 난감함 피력
-
-
재미있는 발견이라는 평가와 함께, 본문이 1/10 길이만 되어도 충분히 효과적인 소통이 가능했으리란 아쉬움 공유. 그래도 영상 콘텐츠가 아니라 스킴 하기가 쉬워서 좋았다는 긍정적인 면도 언급
-
기사 미리 안 읽은 이들을 위해, flame graph 이후 백포팅 언급 전까지만 읽고 멈추는 게 핵심 파악에 가장 효율적이라는 팁
-
기사 전체 스타일이 LLM(대형 언어 모델)이 생성한 것처럼 느껴질 만큼 내용이 길었으며, 그 점이 기억에 남는다는 소감
-
기사 분량이 더 길었더라도 괜찮았을 거라 생각하며, 백업 번들을 두 개 ref로 왜 만들었는지 여전히 의문
-
-
48시간이나 git 폴더(수 GB)를 압축하는 데 드는 시간, 그리고 41분조차도 긴 시간으로 받아들임. git repo 전체를 그냥 스냅샷/아카이브하지 않고, git bundle을 굳이 쓰는 특별한 이유에 의문.
git bundle
이 자주 이루어지는 ZFS 백업보다 무슨 장점이 있는지 궁금함-
git 공식 FAQ에서, 이런 방식은 Git의 일반적인 무결성 검사 절차를 우회하므로 위험성이 존재. 이런 경우 컬렉션 무결성 검증을 위해 git fsck를 권장. 개인 단위에서는 Syncthing, Btrfs 스냅샷만으로도 충분히 빠르고 안정적임. 관련 문서
-
ZFS 스냅샷을 S3 같은 비-ZFS 베이스로 오프사이트 백업하기엔 제한이 있다고 언급. git bundle의 덜 알려진 기능으로,
git clone --bundle-uri
에 위치 지정이 가능하고 서버가 클라이언트에 번들 위치를 알려주면 클라이언트가 번들을 받아 빠르게 풀어 델타 업데이트만 서버에서 받으면 되며, 대형 repo 복제에 부담이 크게 줄어듦 -
결국 캐싱이 필요한 부분에 캐싱을 추가한 것 같다는 평. 통상적으로 git repo는 분산 시스템 특성 상, 다른 저장소로 미러링하고 파일시스템 스냅/백업 도구로 관리하면 되는 것 아니냐는 의문. 핵심은 중요한 버전관리 정보 자체가 분산되어 있어야 한다는 점 강조
-
-
git 백업 경험이 많진 않지만, 로컬 repo에서 직접 백업 만들면 레이스 컨디션이 생기는 이유가 의아하다는 궁금증 전달
-
상위 계층 데이터 프로토콜 때문에 이 정도로 골치 아프다면, 차라리 블록 레벨 스냅샷을 사용하지 않는 이유에 의문. Git처럼 WAL(Write Ahead Logging)이 없는 것이 걸림돌이지만, SQLite는 WAL 모드 추가만으로 손쉽게 블록 스냅샷시키는 전략을 실제 서비스 환경에서 활용 중. Git에도 이런 아키텍처가 적용되면 훨씬 안정적인 백업 전략이 가능할 것이라 생각
-
Git에 WAL과 비슷한 메커니즘이 없다는 점에서 발생하는 문제를 공감하며, 스냅샷만 믿고 백업하면 복원 시 리포지토리가 깨지는 치명적 이슈 경험 공유. 복구는 가능하지만 매우 번거로운 이슈임을 덧붙임
-
SQLite에서 최근 더 나은 방안으로 sqlite3_rsync 솔루션 등장 팁 공유
-
GitLab은 단순 매니지드 서비스가 아니라, 스스로 설치해서 다양한 환경에 쓸 수 있는 제품이므로 사용자마다 파일시스템과 스냅샷 지원 여부, 운영 체제 조건이 다름. 즉, 모든 환경에서 보편적으로 동작하는 독립적인 백업 시스템을 원한다는 GitLab 관점 설명
-
-
"알고리즘 변화로 백업 시간을 지수적으로(Exponentially) 줄였다"는 표현을 보고, O(n^2)에서 O(n^2/2^n)으로 줄였다는 뜻인가 의문. 실제론 그럴 리 없다는 추측
-
수정된 함수의 알고리즘 복잡도가 실제로 6배 빨라졌고, 그 외 사용 맥락에서는 전체 운영 시간이 1%로 줄어드는 극적인 결과라, 이 경우 "지수적 향상"이 마케팅적으로는 적절하다고 해석. 실제 복잡도 규정이 큰 의미는 없다는 설명
-
일상적인 대화에서 "exponential"은 수학적으로 딱 떨어지는 뜻이 아니라 "엄청 개선됨" 정도의 비유적 의미로 쓰임을 명확히 함
-
본인 해석으로 n이 로그(n)까지 줄어든 것일 수 있다고 생각. 양자 푸리에 변환이 기존 DFT보다 지수적으로 빠르다고 흔히 표현되는 배경과 유사하게, 복잡도가 n^2에서 nlogn으로 바뀌는 상황 언급
-
n^2 알고리즘을 log(n) 조회 방식으로 대체하면 속도가 지수적으로 향상되는 게 맞지만, 실제로는 해시맵 조회처럼 O(1)까지 가는 경우가 많아 그보다 더 빠름
-
이러한 논쟁 자체가 비생산적인 트집잡기라고 생각
-
-
C로 작성한다고 해서 성능이 무조건 뛰어나지는 않으며, 자료구조와 알고리즘이 더 우선임을 보여주는 좋은 사례라는 의견
- C는 적절한 컨테이너를 구현하는 게 워낙 어렵다 보니 이런 퍼포먼스 이슈가 더 자주 발생. C++이나 Rust는 unordered_set/HashSet 등 내장 자료구조 덕분에 개발자가 for 루프 하나로 대충 넘기지 않고 자연스럽게 최적화하는 경향. 이번 사례에서도 Git에도 string set이 있지만 표준적이지 않아 원작자가 몰랐을 가능성이 높다는 분석
-
섣부른 최적화와 예측적(anticipatory) 최적화 사이에서 균형이 필요하다는 교훈 언급. 일반적으로는 성급한 최적화를 경계하지만, 아주 빈번히 호출되는 함수에서 명백하고 쉬운 최적화는 미리 적용하는 게 좋겠다는 룰을 제안
- 구현 당시 소스 언어에 set-of-strings가 내장되어 있었다면, 원래 개발자도 이런 최적화를 쉽게 적용했을 것이라는 추정. 결국 이런 문제가 C처럼 컨테이너가 빈약한 언어의 구조적 한계에서 비롯됐다는 의견
-
관련 커밋(알고리즘 개선 내역) 링크를 직접 전달 관련 커밋 링크
- 덕분에 관련 서브미션과 커널 메일링 리스트 토론 스레드를 찾을 수 있었다는 정보 공유 관련 토론 링크