한 줄 정의

대규모 변경(large-scale change, LSC)이란 논리적으로는 하나로 연관되지만 현실적 한계 때문에 원자적으로 커밋할 수 없는 변경들의 집합입니다. 핵심 통찰은 단 하나입니다 — 코드베이스와 엔지니어 수가 커질수록, 한 번에 원자적으로 바꿀 수 있는 변경의 크기는 거꾸로 줄어듭니다. 그래서 구글은 ‘코드베이스 전체를 한 커밋으로 건드린다’는 이상 자체를 일찍이 포기했습니다. 대신 거대 변경을 독립적으로 테스트·리뷰·커밋할 수 있는 작은 샤드(shard)로 쪼개 점진적으로 흘려보내되, 이 노동을 사람이 아니라 도구·프로세스·전담 인프라팀이 떠맡게 만들었습니다. 그 결과, 심볼 이름·클래스 위치·라이브러리 선택처럼 ‘한 번 정하면 되돌릴 수 없다’고 여기던 기술적 결정들을 사후에 바꿀 수 있게 되었습니다.

쉽게 말하면

도시의 상하수도관을 통째로 교체한다고 생각해봅시다.

마을이 작을 때는 간단합니다. 한밤중에 도로 하나를 통제하고, 단수시킨 뒤, 낡은 관을 다 걷어내고 새 관으로 갈아끼우면 됩니다. 아침이면 모든 게 끝나 있죠. 이게 원자적 변경 입니다 — 한 번에, 통째로, 일관되게.

그런데 인구 수백만의 대도시라면 이 방식이 물리적으로 불가능 합니다. 도시 전체를 멈추고 모든 도로를 한꺼번에 막을 수 없습니다. 차량은 24시간 다니고 있고(누군가는 항상 리포지터리를 변경 중이고), 한 번에 다 막으면 도시가 마비됩니다(거대 커밋은 다른 모두의 작업을 멈춥니다). 게다가 어떤 구역은 너무 오래되고 복잡해서 아무도 도면조차 모릅니다(유령의 묘지).

그래서 대도시는 어떻게 할까요? 구간을 잘게 쪼갭니다. 한 블록씩 임시 우회로를 깔아 신·구 관이 동시에 물을 흘리게 해두고(타입 별칭·전달 함수), 한 구간씩 차근차근 교체합니다. 그리고 이 공사를 시민 한 명 한 명이 자기 집 앞 도로를 알아서 파헤치게 맡기지 않습니다 — 도면을 아는 전문 공사팀(인프라팀)이 중장비(Rosie·TAP 같은 자동화 도구)로 일괄 진행합니다.

이 장 전체는 결국 “거대한 코드 변경은 도시 인프라 공사처럼 다뤄라 — 원자적 한 방을 포기하고, 작은 구간으로 쪼개, 자동화된 전담팀이, 도시를 멈추지 않으면서, 점진적으로 갈아끼워라” 는 이야기입니다.

왜 중요한가?

한번 자문해봅시다. 우리 코드베이스에서 한 번의 커밋으로 안정적으로 업데이트할 수 있는 파일은 몇 개인가요? 그 숫자를 늘리지 못하게 막는 요인은 무엇인가요? 지금까지 진행한 가장 큰 커밋은 전체 크기의 몇 %였나요? 커밋 후 기대대로 동작하지 않을 때 롤백할 수 있나요? 막상 답해보면 ‘생각하는 답’과 ‘조직의 실제 답’이 꽤 다르다는 데 놀라게 됩니다.

이게 중요한 이유는, 기반 인프라는 계속 개선되어야 하는데 코드베이스가 커질수록 그 개선을 코드 전반에 반영하기가 점점 불가능해지기 때문입니다. 폐기된 API를 걷어내고, 컴파일러를 올리고, 안티패턴을 청소하고, 더 나은 라이브러리로 옮기는 일 — 이 모든 ‘미래로 가는 길’이 코드 규모에 가로막힙니다.

이게 없다면 어떻게 될까요? 코드베이스는 스스로의 무게에 짓눌려 압사합니다. 낡은 시스템은 영원히 폐기되지 못하고, 더 나은 라이브러리·컴파일러는 도입되지 못하며, 한 번 잘못 지은 설계는 영구 부채로 남습니다. 모든 형태의 의미 있는 진보가 막히는 것입니다.

LSC는 '리팩터링'이 아니라 '진화 능력' 그 자체다

LSC 대다수는 기능을 거의 바꾸지 않는 순수 리팩터링입니다 — 명확성, 최적화, 미래 호환성 개선이 목표죠. 그래서 사소해 보이지만, 핵심은 변경의 내용이 아니라 “코드베이스 전반을 안전하게 갈아끼울 수 있는 능력을 조직이 보유하고 있는가” 입니다. 이 능력이 있어야 비로소 ‘한 번 내린 기술 결정을 나중에 되돌릴 수 있다’는 전제 위에서 설계할 수 있습니다.

핵심 내용

대규모 변경이란 — 원자성을 포기한다는 결정

LSC(large-scale change) 는 논리적으로는 연관되어 있으나 현실적 한계 때문에 원자적으로 서브밋할 수 없는 변경들의 집합입니다. 원자적 커밋이 막히는 이유는 다양합니다 — 너무 많은 파일을 건드려서 기반 도구가 단번에 커밋하지 못하거나, 변경이 너무 커서 병합 과정에서 항상 충돌이 나거나.

LSC는 리포지터리 토폴로지에 크게 좌우 됩니다. 분산 리포지터리를 쓰거나 여러 리포지터리를 연합해 쓴다면, 원자적 변경은 규모가 작아도 기술적으로 애초에 불가능 합니다.

구글에서 LSC는 거의 항상 자동화 도구로 생성되며, 그 결과물은 대체로 이렇게 분류됩니다.

  • 코드베이스 전반을 훑는 분석 도구로 찾아낸 공통 안티패턴 청소
  • 폐기 대상 API 호출을 새 API로 대체
  • 컴파일러 업그레이드 등 저수준 인프라 개선의 활성화
  • 사용자들을 옛 시스템에서 새 시스템으로 마이그레이션

규모 감각을 잡아두면 좋습니다. 구글 정도의 인프라팀이라면 기존 패턴·심볼을 가리키는 참조 수십만 개 를 바꾸는 일이 일상이고, 역대 최고 기록은 수백만 단위입니다. 흥미로운 부수 효과로, 파일 수천 개를 거뜬히 바꾸는 도구는 수십 개를 바꾸는 작은 변경도 잘 처리 합니다. 그래서 LSC 도구에 일찍 투자해두면 팀 단위 변경에도 두루 쓰입니다.

누가 LSC를 처리하나 — 중앙화의 세 가지 논리

구글에서 LSC의 상당 비중은 인프라팀 이 수행합니다(도구는 누구나 쓸 수 있지만요). 여기서 자연스러운 의문이 듭니다 — 새 시스템을 만들었으면 그냥 “다들 새 버전 쓰세요”라고 강제하면 되지 않나요? 더 쉬워 보이는 이 방식이 확장성이 나쁜 데는 세 가지 이유가 있습니다.

① 도메인 지식이 중앙에 있다

하부 시스템을 구축·관리하는 인프라팀은 그 시스템을 활용하는 수만 개의 참조를 수정하는 데 필요한 도메인 지식까지 갖추고 있습니다. 인프라를 쓰기만 하는 팀들은 마이그레이션 방법을 잘 모를 가능성이 큽니다. 이 전문 지식을 모든 팀에 새로 가르치는 것도 비효율적이고, 마이그레이션 도중 문제가 터졌을 때도 전문가들이 한데 모여 있어야 빠르게 복구됩니다.

② 보상 구조가 어긋난다

합당한 보상 없이 할 일만 늘어나는 걸 좋아할 사람은 없습니다. 새 시스템이 더 낫다는 건 보통 조직 전체 관점 의 이야기라, 개별 팀은 자발적으로 업그레이드할 매력을 못 느낄 수 있습니다. 반드시 옮겨야 할 만큼 중요하다면 그 비용은 조직 차원에서 부담하는 게 맞습니다. 중앙에서 일괄 처리하는 편이, 개별 팀에 떠넘긴 뒤 전체를 조율하는 유기적 마이그레이션 보다 거의 항상 더 빠르고 저렴합니다.

③ 낡은 시스템을 없애고 싶은 팀이 주도해야 끝난다

유기적 마이그레이션은 완벽하게 성공하기 어렵습니다. 엔지니어들이 새 코드를 짤 때 기존 코드를 예제로 복사 하는 경향도 한몫합니다(낡은 패턴이 계속 재생산됨). 낡은 시스템을 제거하고 싶어 하는 팀이 마이그레이션을 직접 담당하면 확실하게 끝낼 동기가 생깁니다. 별도 팀에 자금·인력을 쏟는 게 추가 비용처럼 보이지만, 실제로는 여러 팀이 보상 없이 각자 해야 할 일을 한곳에 모은 것 일 뿐이고, 자연스럽게 규모의 경제도 얻습니다.

사례 연구: 구멍 메우기 (Google Template Library)

구글 초창기에 C++ 표준 템플릿 라이브러리를 보완하는 자체 라이브러리를 만들었습니다. 그런데 어떤 헤더 파일은 이름이 stl_util.h였고 어떤 건 map-util.h였습니다 — 구분 기호가 언더스코어(_)와 하이픈(-)으로 제각각 이었죠. 일관성을 중시하는 사람에게 짜증을 유발하는 건 물론, 어떤 파일이 어떤 기호를 쓰는지 외워야 했고 컴파일이 한참 진행된 후에야 오류가 드러나 실질적 생산성까지 갉아먹었습니다. LSC 시스템은 우선순위 높은 마이그레이션만이 아니라, 이런 사소하지만 코드베이스 전반에 퍼진 정리 작업 도 가능하게 해줍니다 — 정부가 새 도로를 깔면서 오래된 도로도 함께 보수하는 교통 인프라 업무와 비슷합니다.

원자적 변경을 가로막는 벽들

왜 거대 변경을 원자적으로 커밋할 수 없는지를 이해해야 LSC 프로세스가 왜 그렇게 생겼는지가 보입니다. 이상적 세계에서는 모든 논리적 변경이 하나의 원자적 단위로 묶여 독립적으로 테스트·리뷰·커밋되어야 하지만, 리포지터리와 엔지니어가 늘수록 이 이상은 멀어집니다.

기술적 한계

대부분의 버전 관리 시스템(VCS) 에서 작업 비용은 변경 크기에 비례합니다. 파일 수십 개 커밋은 거뜬해도, 수천 개를 원자적으로 커밋하기엔 메모리·프로세싱이 부족할 수 있습니다. 중앙집중형 VCS에서는 커밋 중 다른 사용자가 쓰기(구형은 읽기까지) 작업을 못 합니다 — 거대한 커밋 하나가 다른 모두의 일을 멈춰 세웁니다. 이건 ‘어렵다’의 문제가 아니라 인프라상 애초에 불가능 할 수 있다는 말입니다. 그래서 절차가 복잡해지더라도 거대 변경을 작고 독립적인 단위로 쪼개 이 한계를 우회합니다.

병합 충돌

변경 규모가 커질수록 병합 충돌 확률도 올라갑니다. 포함된 파일이 많을수록, 같은 리포지터리를 쓰는 엔지니어가 많을수록 더 복잡해집니다. 작은 회사라면 아무도 일하지 않는 주말에 몰래 반영하는 꼼수도 있겠지만, 구글 같은 글로벌 회사에선 누군가는 항상 리포지터리를 변경 중 이라 불가능합니다. 결국 핵심 처방은 같습니다 — 변경하는 파일이 적으면 충돌 확률이 작아 커밋 성공률이 높아진다.

유령의 묘지

No haunted graveyard

SRE들 사이에 ‘유령의 묘지 근절(no haunted graveyard)‘이란 주문이 있습니다. 유령의 묘지(haunted graveyard) 란 너무 오래되고 둔하고 복잡해서 아무도 손대려 하지 않는 시스템입니다. 잘못 건드리면 이해할 수 없는 문제가 생겨 막대한 손실을 입을 수 있어, 어느 시점부터 시간이 멈춰버린 것이죠.

유령은 프로덕션뿐 아니라 코드베이스에도 터를 잡습니다 — 떠난 사람이 작성한, 관리되지 않으면서도 돈을 벌어들이는 핵심 소프트웨어 말입니다. “건드리면 불안정해진다”는 관료주의가 덧씌워져 시간이 멈춥니다. LSC 관점에서 유령의 묘지는 거대 마이그레이션을 마무리하지 못하게 하고 낡은 시스템 폐기를 막는 걸림돌입니다.

여기서 중요한 처방이 나옵니다 — 충실한 테스트가 유령을 쫓아냅니다. 소프트웨어가 철저히 테스트되면 “변경해도 이상이 안 생긴다”는 믿음이 생기고, 시스템이 얼마나 오래되고 복잡한지는 문제가 되지 않습니다.

이질성

LSC가 가능하려면 작업 대부분을 사람이 아니라 컴퓨터 가 처리해야 합니다. 그런데 컴퓨터는 사람과 달리 모호한 일을 잘 못 합니다. 그래서 변경을 정확한 위치에 올바르게 반영하려면 환경이 일관되어야 합니다. 다양한 VCS·CI를 운영하고 프로젝트마다 도구·스타일이 제각각이면 코드베이스 전체를 아우르는 변경은 진행하기 어렵습니다. 반대로 환경을 단순화해 일관성을 높이면 컴퓨터가 변경을 자동 수행하기 좋아집니다. 그래서 구글은 프리서브밋 검사를 표준 프로세스에 녹이되, LSC 테스트 때는 팀 특화 검사를 생략 하도록 조언합니다(그 검사 다수는 새 기능 개발 팀을 위한 것이라 LSC에선 변경 내용과 무관하게 복잡성만 키우기 때문).

테스트

변경의 덩치가 커지면 제대로 테스트하기가 훨씬 어렵습니다. 구글 CI는 변경이 직접 영향을 주는 테스트뿐 아니라 간접적으로 그 파일을 이용하는 코드의 테스트까지 모두 수행하기 때문입니다. 한 가지 발견은 — 종속성 그래프에서 멀리 떨어질수록 변경 때문에 테스트가 실패할 확률이 줄어든다 는 것입니다.

작고 독립적인 변경은 검증하기 쉽습니다. 수정 파일이 25개뿐이면 테스트가 실패해도 근본 원인을 쉽게 찾지만, 10,000개라면 해운대에서 바늘 찾기입니다. 여기에 트레이드오프가 있습니다 — 작은 변경을 자주 하면 같은 테스트가 여러 번 실행됩니다. 하지만 실패 원인을 추적하는 엔지니어의 시간이 추가 테스트의 컴퓨팅 시간보다 훨씬 비싼 자원 이므로, 구글은 이 트레이드오프를 기꺼이 감수합니다.

사례 연구: TAP 열차 (Test Automation Platform)

LSC 테스트는 느리고 좌절스러울 수 있습니다 — 변경이 어느 이상 커지면 코드베이스 전체가 나만 빼고 사막의 모래언덕처럼 이동해, 내 로컬을 헤드(head)와 동기화시키지 못하기도 합니다. 구글은 이때 TAP 열차(The Train) 로 속도를 높입니다. LSC들이 서로 독립적이고, 영향받는 테스트 대다수가 대부분의 LSC에서 문제없이 통과한다는 사실을 활용해, 여러 변경을 한 열차에 태워 테스트 총합을 줄이는 방식입니다.

열차는 3시간마다 출발하며 다섯 단계로 진행됩니다.

  1. 실린 변경 각각에 대해 무작위로 고른 테스트 1,000개를 수행한다.
  2. 1,000개를 통과한 변경들을 모아 대표 변경 하나를 뽑는다 — 이 대표가 곧 ‘열차’다.
  3. 변경들에 직접 영향받는 테스트를 모두 실행한다(큰/저수준 LSC면 리포지터리의 모든 테스트라 6시간 이상 걸리기도 한다).
  4. 불규칙(flaky)하지 않으면서 실패한 테스트만 추려, 개별 변경에 독립적으로 다시 수행해 실패의 원흉 을 가려낸다.
  5. 각 변경에 대한 보고서를 생성한다 — LSC가 서브밋해도 안전한지 판단하는 근거가 된다.
코드 리뷰

모든 변경은 서브밋 전 리뷰를 거쳐야 하고 LSC도 예외가 아닙니다. 거대 커밋 검토는 지루하고 번거롭고 오류가 스며들기 쉽습니다(특히 수동 변경이면 더). 그래서 특정 상황에선 여전히 사람이 개입해 변경이 올바른지 확인해주는 게 좋습니다.

사례 연구: scoped_ptr → std::unique_ptr

초기부터 구글은 C++에서 scoped_ptr라는 자체 스마트 포인터를 광범위하게 썼습니다. C++11에 와서 같은 기능을 하면서 더 안전한 표준 타입 std::unique_ptr가 등장했지만, 당시 코드베이스엔 scoped_ptr 참조가 수백만 개 소스 파일 에 퍼져 있었습니다. 옮기려면 역사상 최대 규모의 LSC가 필요했죠.

몇 개월에 걸쳐 여러 엔지니어가 동시에 매달렸습니다. scoped_ptr 참조를 std::unique_ptr로 바꾸면서, 동시에 scoped_ptr 자체도 std::unique_ptr처럼 동작하게 서서히 바꿔갔습니다. 한창일 때는 매일 15,000개 이상의 파일을 건드리는 700개 이상의 독립된 변경 을 생성·테스트·커밋했습니다(지금은 10배도 처리). 거의 모든 LSC가 그렇듯 이것도 미묘한 의존성(하이럼의 법칙)과의 지난한 싸움이었습니다. 마지막엔 scoped_ptrstd::unique_ptr별칭(alias) 으로 만든 뒤, 옛 별칭을 새 별칭으로 대체하는 식으로 마무리했습니다.

LSC를 떠받치는 인프라

도구가 변경을 만들고 관리하고 리뷰하고 테스트하지만, 도구보다 중요한 건 아마도 LSC를 둘러싼 문화적 규범의 진화 일 것입니다.

정책과 문화

모노리포 덕에 누구나 다른 프로젝트의 파일을 수정하고 승인 권한자에게 검토를 요청할 수 있습니다. 역사적으로 변경 비용과 리뷰 비용은 대칭적 이어서 한 명/한 팀이 만들 수 있는 변경 범위를 자연히 제한했습니다. 그런데 LSC 도구가 진화하며 큰 변경을 매우 저렴하게 생성할 수 있게 되자(한 명이 수많은 리뷰어에게 부담을 지우기도 쉬워짐), 구글은 LSC 작성자를 위한 가벼운 승인 프로세스 를 두었습니다. 목적은 LSC를 막는 게 아니라, 작성자가 구글의 기술·인적 지원을 최대한 활용해 가장 이상적인 변경을 만들도록 돕는 것입니다.

문화적으로도 중요한 전환이 있었습니다 — 코드 소유자가 책임감을 갖는 것도 중요하지만, 제품팀이 인프라팀을 신뢰 하고 LSC 작성자가 그 변경에 가장 적합한 도메인 지식을 갖췄음을 인정하는 것이 LSC를 조직에 연착륙시키는 첫 단추입니다. 그래서 영향받는 개별 팀의 소유자는 특정 커밋에 댓글로 질문할 수 있지만, 더 넓은 차원의 LSC에 거부권을 행사하지는 못합니다. 누적된 선례와 잘 정리된 FAQ가 이 신뢰를 뒷받침합니다.

코드베이스 인사이트

LSC를 하려면 코드베이스 전반을 분석할 수 있어야 합니다. 텍스트 기반 분석은 물론 의미(semantic) 추적 도 중요합니다. 구글은 의미론적 인덱싱 도구 Kythe 로 코드 영역 간 연관 관계 지도를 만들어 “이 함수를 호출하는 코드는 어디 있지?”, “이 클래스에서 파생된 클래스들은?” 같은 질문에 답합니다. 컴파일러 기반 인덱스로 추상 구문 트리(AST) 기반 분석·변환도 수행하며, 이를 ClangMR·JavaFlume·Refaster 같은 도구가 활용합니다. 작은 변경이라면 커스텀 도구, perl, sed, 정규식 매칭, 셸 스크립트로도 충분합니다.

핵심 원칙은 — 인력 투입량이 코드베이스보다 느리게 커져야 한다 는 것입니다. 리포지터리가 커져도 사람이 개입하는 시간은 크게 늘지 않아야 합니다. 경험상 수정 대상이 500곳 을 넘어가면 사람이 일일이 고치기보다 변경 생성 도구를 익혀 쓰는 편이 효율적이었습니다.

Rosie — 변경 관리 플랫폼

LSC 인프라에서 가장 중요한 도구는 마스터 변경을 여러 샤드로 나눈 뒤 테스트·메일링·리뷰·커밋 단계를 독립적으로 관리해주는 것입니다. 구글은 Rosie 를 씁니다. Rosie는 단순한 도구라기보다 LSC를 구글 규모에서 진행할 수 있게 해주는 플랫폼 으로, 광범위한 변경에 수반되는 수많은 변경을 독립적으로 테스트·리뷰·서브밋할 수 있는 작은 샤드들로 나눠줍니다.

언어 지원

LSC는 주로 언어별 로 진행되며 난이도는 언어에 따라 크게 다릅니다.

언어 특성LSC 난이도이유
타입 별칭·전달 함수 지원쉬움마이그레이션 중에도 신·구 코드가 함께 동작
정적 타입쉬움강력한 정적 분석·컴파일러 정보를 도구 제작에 활용, 테스트 전에 문제를 걸러냄
동적 타입(파이썬·루비·JS)어려움점진적 마이그레이션이 까다로움

흥미로운 통찰은 — 개발자 생산성에 치중하는 언어일수록 유지보수가 더 어려워지는 경향이 있다는 점입니다(필연적 한계는 아니지만 현재 모습이 그렇습니다). 마지막으로 자동 포맷터(google-java-format·clang-format)도 중요한 역할을 합니다 — 자동 생성된 변경도 사람이 짠 코드와 일관된 스타일을 유지하게 해주어, 다른 도구들이 스타일을 신경 쓰지 않아도 되게 만듭니다.

LSC 프로세스 네 단계

인프라가 갖춰지면 실제 프로세스가 돌아갑니다. 크게 네 단계지만 경계는 매우 모호합니다.

flowchart LR
    A["① 권한 부여<br/>제안서·도메인 리뷰<br/>위원회 승인"] --> B["② 변경 생성<br/>거대한 전역 변경 1개<br/>자동화·스타일 정규화"]
    B --> C["③ 샤드 관리<br/>Rosie가 샤드로 분할<br/>테스트→메일→리뷰→서브밋"]
    C --> D["④ 마무리 청소<br/>옛 심볼·시스템 제거<br/>재유입 차단"]
① 권한 부여

작성자는 간단한 제안 문서를 씁니다 — 변경을 제안하는 이유, 코드베이스 전반에 주는 예상 영향(예: 생성될 샤드 수), 리뷰어들이 던질 만한 질문과 답변. 리팩터링될 API 소유자들로부터 ‘도메인 리뷰’도 받습니다. 그런 다음 전체 프로세스 감독자 10여 명으로 구성된 위원회 에 제안을 보냅니다. 위원회는 대체로 LSC 리뷰 전체를 한 명의 ‘글로벌 승인자’ 에게 할당합니다 — 기계적인 LSC는 변경의 본질과 빌드 자동화를 깊이 아는 전문가 한 명에게 맡기는 게 효율적이기 때문입니다. 위원회는 승인에 관대하며, 명확한 변경은 재량껏 빠르게 일괄 처리하고, 갈등 시 에스컬레이션 기관 역할도 합니다.

② 변경 생성

승인을 얻으면 작성자는 거대한 전역 변경 하나를 생성한 뒤 다수의 독립적 샤드로 쪼갭니다(VCS의 기술적 한계 때문에 하나의 글로벌 변경으로는 처리 못 함). 생성은 가능한 한 자동화합니다 — 누군가 옛 방식 코드로 되돌아가거나 병합 충돌이 나면 상위 변경을 다시 갱신해야 하기 때문입니다. 도구가 글로벌 변경을 못 만들 땐 여러 사람이 나눠 생성하기도 합니다. 그리고 사람이 읽기 편하도록 스타일 가이드와 자동 포맷터를 적용합니다.

작전명 RoseHub — 사람이 손으로 끌어낸 LSC

2017년 초, 아파치 Commons 라이브러리에서 취약점이 발견됐습니다. 취약한 버전이 클래스패스에 있는 모든 자바 앱을 원격 실행할 수 있는 심각한 상황이었죠(‘미친 가젯, Mad Gadget’). 깃허브의 수많은 오픈 소스도 노출됐습니다. 몇몇 진취적인 구글 직원들이 자체 LSC 프로세스를 가동해, BigQuery로 영향받는 프로젝트를 찾아내고 패치를 날려보냈습니다. 보낸 패치가 2,600개 가 넘었는데, 자동 관리 도구 대신 50명이 넘는 자원자 가 이 LSC를 성공적으로 이끌었습니다 — LSC가 꼭 완전 자동이어야만 하는 건 아님을 보여주는 사례입니다.

③ 샤드 관리 — 반려동물이 아니라 가축처럼

Rosie가 거대 변경을 프로젝트 경계·소유권 규칙에 따라 작은 샤드 로 쪼개, 각각을 독립된 테스트 → 메일 → 리뷰 → 서브밋 파이프라인에 태웁니다. 이 단계를 관통하는 사고방식이 ‘반려동물 vs 가축’입니다.

관점일반 변경 = 반려동물(pet)LSC의 개별 변경 = 가축(cattle)
관여 방식엔지니어가 변경 하나에 속속들이 관여, 메인 반영 시 자랑스러움이름 없고 얼굴 없는 커밋으로 취급
롤백·거부심적으로 고통스러움전체 무리가 영향받지 않는 선에서 작은 비용으로 언제든 가능
전제수 일~수 주의 수작업높은 수준의 자동화 (테스트 실패·병합 충돌이면 가볍게 롤백)

핵심은 수많은 샤드가 거부되어도 그건 그저 작업의 특성일 뿐, 자동화가 잘 돼 있다면 매우 저렴하게 다시 생성할 수 있다 는 마음가짐입니다. “가축 몇 마리를 잃었다고 크게 상처받지 않는다”는 것이죠.

테스트하기. 각 샤드는 TAP으로 테스트됩니다. 대다수 샤드는 1,000개 이하의 테스트만 돌리고, 더 많이 필요한 샤드들은 하나의 그룹으로 묶어 효율을 높입니다.

단계무엇을 수행하나비용
1차그룹 내 샤드들에 영향받는 모든 테스트를 한꺼번에 수행어차피 거의 모든 테스트를 돌리므로, 그룹에 샤드를 추가하는 비용은 거의 공짜
2차샤드별로, 그 샤드에 영향받는 테스트 중 1차에서 실패한 것만 다시 수행어느 샤드가 실패의 원인인지 가려냄

다만 독립 실행 땐 확률이 매우 낮은 사건도 엄청난 규모로 한꺼번에 돌리면 거의 확실하게 일어납니다 — 불규칙한(flaky) 테스트 가 LSC 작성자의 골칫거리인 이유입니다. 개별 팀엔 별 영향이 없어도 LSC 시스템의 처리량을 심각하게 떨어뜨립니다. 그래서 구글 자동화 도구는 서브밋 시 최근에 튄 불규칙 테스트를 무시 합니다(작은 회귀 위험은 사람끼리 소통해 해결하는 게 더 쉬움).

리뷰어에게 메일 보내기. Rosie는 테스트로 안전을 검증한 뒤 적절한 리뷰어에게 메일합니다. OWNERS 파일 에서 디렉터리별 소유자를 확인해 자격 있는 사람에게 요청하고, 응답이 없으면 자동으로 다른 리뷰어를 고릅니다. 또한 LSC 이전부터 실패하던 프리서브밋 검사는 거의 무시합니다(로컬 코드 소유자가 자기 코드를 항상 오류 없이 관리해야 한다는 건 인프라팀과의 사회적 계약입니다).

리뷰하기. Rosie가 만든 변경도 표준 코드 리뷰를 거칩니다. 다만 로컬 소유자들이 LSC를 여느 변경만큼 엄격히 보지 않는 경우가 많습니다(인프라팀을 신뢰해 피상적으로 통과시킴). 그래서 구글은 단순 승인이 아니라 맥락을 살펴야 하는 변경만 로컬 소유자에게 보내고, 나머지는 글로벌 승인자 에게만 보냅니다. 글로벌 승인자는 변경을 일일이 보는 대신 패턴 기반 도구로 기대에 부합하는 변경들을 자동 승인 하고, 병합 충돌이나 도구가 판단 못 한 일부만 직접 리뷰합니다 — 프로세스 확장성을 높이는 또 하나의 장치입니다.

서브밋하기. 마지막으로 개별 변경을 커밋합니다(역시 프로젝트별 프리커밋 검사를 거침). Rosie 덕에 구글은 매일 수천 개의 변경을 생성·테스트·리뷰·서브밋 할 수 있고, 각 팀은 사용자를 효과적으로 마이그레이션할 수 있습니다.

④ 마무리 청소

LSC마다 ‘완료’의 정의가 다릅니다 — 옛 시스템을 완벽히 제거할 수도, 옛 참조가 자연히 사라지도록 내버려둘 수도 있습니다. 어느 쪽이든 애써 제거한 심볼·시스템이 다시 유입되는 걸 막는 방책 이 필요합니다. 구글은 Tricorder 프레임워크로, 누군가 폐기 대상 객체를 새로 쓰면 코드 리뷰 때 알려줘 변경의 역행을 막습니다.

비교 / 트레이드오프

전통적 리팩터링 vs LSC
관점전통적 리팩터링 (원자적)LSC (샤드 기반)
변경 단위논리적 변경 하나를 한 커밋에거대 변경을 독립적 샤드 수백~수천 개로 분할
일관성커밋 시점에 코드베이스 전체가 일관신·구가 한동안 공존(별칭·전달 함수)
규모 한계코드/엔지니어가 늘면 한계 노출인력 투입이 코드보다 느리게 늘도록 설계
주체변경을 원하는 누구나도메인 지식을 가진 전담 인프라팀 + 자동화
실패 처리롤백이 곧 큰 사건가축처럼 저렴하게 롤백·재생성
원자적 변경을 막는 다섯 벽과 그 처방
무엇이 문제인가LSC의 처방
기술적 한계VCS가 수천 파일 원자 커밋 불가, 다른 작업 정지작고 독립적인 샤드로 분할
병합 충돌파일·동시 작업자 많을수록 충돌 급증변경 파일 수를 줄여 충돌 확률↓
유령의 묘지아무도 못 건드리는 시스템이 진보를 막음충실한 테스트로 변경 안전성 확보
이질성환경이 제각각이면 컴퓨터가 자동 처리 불가환경 일관화 + 팀 특화 검사 생략
테스트큰 변경일수록 실패 원인 추적이 어려움작게 쪼개기 + TAP 열차로 테스트 총합↓

내 생각

  • LSC는 결국 ‘도구·테스트에 대한 선행 투자’다. 파일 수천 개를 다루는 도구는 수십 개짜리 변경도 잘 처리한다는 부수 효과가 핵심입니다 — 사내 코드모드(codemod)·OpenRewrite·jscodeshift 같은 AST 변환 자동화에 일찍 투자해두면, 작은 마이그레이션에서도 두고두고 회수됩니다.

  • ‘유령의 묘지 = 테스트 부재’라는 등식이 가장 실무적이다. 아무도 못 건드리는 레거시 모듈의 본질은 복잡성이 아니라 회귀를 잡아줄 안전망이 없다는 것 입니다. 리팩터링 전에 특성화 테스트(characterization test)부터 덮는 게, 곧 유령을 쫓는 첫 수순입니다.

  • 별칭·전달 함수 = 무중단 마이그레이션의 핵심 기법. scoped_ptrstd::unique_ptr 사례의 교훈은 곧 백엔드의 확장-수축(expand-contract) 패턴 입니다 — 신 API를 구 API 위에 별칭으로 얹어 공존시키고, 호출부를 샤드 단위로 옮긴 뒤, 마지막에 구 API를 걷어냅니다. DB 컬럼 이름 변경·API 버저닝에 그대로 적용됩니다.

  • ‘반려동물 vs 가축’은 배포·인프라에서 코드 변경으로 확장된 사고법. 개별 커밋에 감정 이입하지 않고 저렴하게 롤백·재생성하는 태도가 자동화의 전제입니다. 단, 이게 성립하려면 롤백이 진짜 싸야 한다는 선행 조건(CI·자동 생성)이 충족돼야 합니다.

  • OWNERS 파일 + 글로벌 승인자 = 리뷰 확장성의 정석. 모든 LSC를 로컬 소유자가 보면 병목이 됩니다. ‘맥락 판단이 필요한 것만 사람에게, 나머지는 패턴 매칭으로 자동 승인’이라는 분기는 PR 자동 리뷰 봇·CODEOWNERS 설계에 그대로 옮겨올 만합니다.

관련 개념

  • Ch01 소프트웨어 엔지니어링이란 — ‘프로그래밍 + 시간 = 소프트웨어 엔지니어링’, 그리고 LSC를 평생 따라다니는 하이럼의 법칙의 출처입니다
  • Ch16 버전 관리와 브랜치 관리 — 모노리포·트렁크 기반 개발이 LSC의 토대입니다. ‘항상 누군가 리포지터리를 변경 중’이라는 병합 충돌 문제도 여기서 출발합니다
  • Ch15 폐기 — LSC의 ‘마무리 청소’는 곧 안전한 폐기입니다. 옛 시스템을 제거하고 재유입을 막는 과정이 직접 맞물립니다
  • Ch11 테스트 개요 · Ch14 더 큰 테스트 — ‘충실한 테스트가 유령의 묘지를 쫓는다’는 LSC의 안전망입니다. TAP 열차의 샤드 테스트 전략도 여기 닿아 있습니다
  • Ch09 코드 리뷰 — OWNERS·글로벌 승인자·패턴 기반 자동 승인은 코드 리뷰 정책을 LSC 규모로 확장한 형태입니다
  • Ch21 의존성 관리 — ‘헤드에서 지내기’에서 제공자가 다운스트림을 직접 마이그레이션하는 능력이 바로 이 장의 LSC 인프라입니다