들어가며: 포크의 덫

대규모 오픈소스 프로젝트를 내부 포크(Fork)로 관리하다 보면, 시간이 지날수록 업스트림과의 간극이 벌어져 결국 '고립' 상태에 빠지기 쉽습니다. 메타(Meta)도 예외는 아니었습니다. 전 세계 수십억 사용자에게 실시간 오디오/비디오 통신(RTC)을 제공하기 위해 WebRTC를 깊이 커스터마이징했지만, 내부 포크는 점점 업스트림과 동떨어져 커뮤니티 업데이트를 받아들이기 어려운 상황이 되었죠.

이 글은 메타가 어떻게 이 '포크의 덫(Forking Trap)'에서 벗어나, 50개 이상의 유스케이스를 최신 업스트림 기반의 모듈식 아키텍처로 성공적으로 이전했는지 그 전략과 기술적 교훈을 공유합니다.

Meta engineers analyzing WebRTC dual-stack architecture diagram on whiteboard System Abstract Visual

핵심 과제: 모노레포와 정적 링커의 충돌

메타의 가장 큰 고민은 두 가지였습니다.

  1. A/B 테스트 필요성: 수십억 사용자에게 서비스를 제공하는 환경에서 한 번의 업그레이드로 모든 것을 바꾸는 것은 리스크가 큽니다. 기존 레거시 버전과 새로운 업스트림 버전을 동시에 실행하면서 안전하게 비교/검증할 수 있는 구조가 필요했습니다.
  2. 모노레포의 제약: 메타는 모노레포(Monorepo)를 사용하며, 기능 브랜치를 광범위하게 지원하지 않았습니다. 패치를 추적하고 업스트림에 지속적으로 리베이스(Rebase)할 수 있는 방법을 찾아야 했습니다.

C++ 링커의 'One Definition Rule' 위반

가장 까다로운 기술적 난관은 C++의 One Definition Rule(ODR) 위반이었습니다. 하나의 바이너리에 같은 라이브러리의 두 버전을 정적으로 링크하면 수천 개의 심볼 충돌이 발생합니다. 메타는 이 문제를 해결하기 위해 듀얼 스택 아키텍처(Dual-Stack Architecture)심 레이어(Shim Layer) 를 도입했습니다.

솔루션 1: 심 레이어와 듀얼 스택 아키텍처

심 레이어(Shim Layer) 설계

애플리케이션과 WebRTC 사이에 프록시 역할을 하는 심 레이어를 두었습니다. 앱은 WebRTC를 직접 호출하지 않고, 심 API를 통해 호출합니다. 심은 단일화된 버전 독립적 API를 노출하며, 내부적으로 'flavor' 설정을 바탕으로 런타임에 레거시 또는 최신 WebRTC 구현체로 디스패치합니다.

// 심 레이어 예시 (단순화된 개념)
namespace webrtc_shim {

enum class Flavor {
  LEGACY,
  LATEST
};

class VideoEncoder {
 public:
  virtual bool Encode(const VideoFrame& frame) = 0;
};

// Flavor에 따라 실제 구현체를 생성하는 팩토리
std::unique_ptr<VideoEncoder> CreateVideoEncoder(Flavor flavor) {
  if (flavor == Flavor::LATEST) {
    return std::make_unique<webrtc_latest::VideoEncoder>(/*...*/);
  } else {
    return std::make_unique<webrtc_legacy::VideoEncoder>(/*...*/);
  }
}

}  // namespace webrtc_shim

이 접근법 덕분에 바이너리 크기 증가를 약 38MB에서 5MB로 87%나 줄일 수 있었습니다.

심볼 충돌 해결: 자동 재네임스페이싱

ODR 문제를 해결하기 위해 메타는 자동 재네임스페이싱(Automatic Renamespacing) 스크립트를 개발했습니다. 모든 C++ 네임스페이스를 스크립트로 일괄 변경하여 webrtc::webrtc_latest::webrtc_legacy::로 분리했습니다. 네임스페이스 외부에 있는 전역 C 함수나 클래스는 별도의 식별자로 처리했습니다.

# 재네임스페이싱 스크립트 (개념)
import re

def rename_namespace(source_code: str, old_ns: str, new_ns: str) -> str:
    # 네임스페이스 선언 변경
    source_code = re.sub(fr'namespace {old_ns}', f'namespace {new_ns}', source_code)
    # using 선언 변경
    source_code = re.sub(fr'using namespace {old_ns}', f'using namespace {new_ns}', source_code)
    # 기타 심볼 참조 변경 (단순화)
    source_code = re.sub(fr'{old_ns}::', f'{new_ns}::', source_code)
    return source_code

하위 호환성 유지

재네임스페이싱만으로는 기존 코드가 깨집니다. 메타는 C++ using 선언을 활용해 새로운 네임스페이스를 기존 webrtc::로 임포트하는 방법을 사용했습니다. 이는 순수 컴파일러 지시문이므로 바이너리 크기에 영향을 주지 않습니다.

// 하위 호환성을 위한 using 선언 (헤더 파일)
namespace webrtc {
  using webrtc_latest::VideoEncoder;
  using webrtc_latest::AudioDecoder;
  // ... 필요한 심볼만 임포트
}

코드 생성 자동화

심 레이어의 어댑터와 컨버터를 수동으로 작성하는 것은 엄청난 작업이었습니다. 메타는 AST(Abstract Syntax Tree) 파싱을 기반으로 한 코드 생성 시스템을 구축하여 생산성을 1일 1개에서 34개로 34배 향상시켰고, 사람의 실수도 줄였습니다.

솔루션 2: 기능 브랜치 전략

모노레포 환경에서 패치를 추적하기 위해 메타는 별도의 Git 저장소에서 기능 브랜치(Feature Branches) 를 관리하는 방법을 선택했습니다.

  • 각 Chromium 릴리스(예: M143)에 대해 base/7499 브랜치를 생성합니다.
  • 각 패치(예: debug-tools)에 대해 debug-tools/7499 브랜치를 base/7499 위에 만듭니다.
  • 업그레이드 시 모든 기능 브랜치를 순차적으로 머지 포워드(Merge Forward)하여 r7559와 같은 릴리스 후보 브랜치를 만듭니다.

이 방식은 병렬 처리가 가능하고, Git 히스토리를 보존하며, 향후 LLM을 이용한 충돌 자동 해결에도 적합합니다.

결과: 지속적인 업그레이드 체계 확보

이 아키텍처를 통해 메타는 WebRTC M120에서 시작하여 현재 M145까지 지속적으로 업그레이드하고 있습니다. 주요 성과는 다음과 같습니다.

항목개선 효과
CPU 사용률최대 10% 감소
크래시율최대 3% 개선
바이너리 크기100~200KB (압축 기준) 감소
보안usrsctp 등 레거시 라이브러리 제거, 취약점 해결

이 프로젝트는 완전한 재작성 없이도 기술 부채를 현대화할 수 있음을 증명했습니다.

Server rack with multiple network connections symbolizing scalable real-time communication infrastructure IT Technology Image

국내 개발 생태계에서의 적용 맥락

메타의 접근법은 국내 SI(시스템 통합) 환경이나 대규모 서비스를 운영하는 기업에 특히 유용한 교훈을 줍니다.

  • 레거시 포크 관리: 많은 국내 기업이 오픈소스 라이브러리를 내부 포크로 관리하다가 업데이트에 어려움을 겪습니다. 심 레이어와 듀얼 스택 접근법은 안전하게 전환할 수 있는 청사진을 제공합니다.
  • 모노레포 환경: 국내에서도 모노레포 도입이 증가하는 추세입니다. 기능 브랜치 전략은 모노레포에서도 효과적으로 패치를 관리할 수 있는 방법을 보여줍니다.
  • A/B 테스트 중요성: 국내 서비스도 사용자 경험에 민감합니다. A/B 테스트 가능한 업그레이드 파이프라인은 필수적입니다.

이 기술의 한계 또는 주의사항

  • 복잡성: 심 레이어와 듀얼 스택은 아키텍처 복잡도를 크게 높입니다. 소규모 프로젝트에는 과도한 엔지니어링일 수 있습니다.
  • 유지보수 비용: 코드 생성 도구와 자동화 파이프라인을 지속적으로 유지보수해야 합니다.
  • 팀 역량: C++ 템플릿 메타프로그래밍, AST 파싱 등 고급 기술이 필요합니다.

다음 단계 학습 방향

  1. C++ 심볼 가시성과 ODR: C++ 링킹과 ODR에 대한 깊은 이해가 필요합니다. 관련 서적이나 CppCon 발표를 추천합니다.
  2. 듀얼 스택 패턴: 이 패턴은 WebRTC 외에도 다른 라이브러리(예: OpenSSL, FFmpeg)의 안전한 업그레이드에 적용 가능합니다.
  3. AI 기반 충돌 해결: 메타가 언급한 LLM 기반 머지 충돌 자동 해결은 앞으로 더 주목받을 기술입니다. 관련 논문과 오픈소스 프로젝트를 살펴보세요.

Cloud infrastructure diagram illustrating continuous upgrade pipeline for WebRTC library Coding Session Visual

결론: 포크의 덫에서 벗어나는 법

메타의 사례는 '포크의 덫'이 단순한 기술적 문제가 아니라, 조직의 장기적인 기술 부채 관리 전략의 일부임을 보여줍니다. 완전한 재작성 없이도 심 레이어, 듀얼 스택, 자동화된 패치 관리라는 세 가지 축을 통해 현대화를 이룰 수 있었습니다.

이 접근법은 WebRTC에 국한되지 않습니다. 어떤 오픈소스 라이브러리든, 내부 포크가 업스트림과 멀어지고 있다면 지금이 바로 '탈출'을 계획할 때입니다.

"가장 위험한 포크는 아무도 업데이트하지 않는 포크다."

함께 보면 좋은 글:

본 글은 Meta Engineering 블로그의 내용을 기반으로 작성되었습니다.

본 콘텐츠는 신뢰할 수 있는 출처를 바탕으로 AI 도구를 활용하여 초안이 작성되었으며, 편집자의 검토를 거쳐 발행되었습니다. 전문가의 조언을 대체하지 않습니다.