들어가며: 포크 트랩(Forking Trap)이란?
메타(Meta)는 Messenger, Instagram, Cloud Gaming, Meta Quest VR 캐스팅 등 수십억 사용자의 실시간 커뮤니케이션을 위해 WebRTC를 사용해 왔습니다. 하지만 오픈소스 라이브러리를 내부 저장소(monorepo)에 포크하는 순간, '포크 트랩'이 시작됩니다. 초기에는 사소한 최적화나 버그 픽스에서 시작했지만, 업스트림이 진화하고 내부 기능이 쌓일수록 업스트림 커밋을 머지하는 비용이 기하급수적으로 늘어납니다.
결국 메타는 수년간의 대규모 마이그레이션을 통해 50개 이상의 유스케이스를 최신 업스트림 기반의 모듈형 아키텍처로 옮겼습니다. 이 글에서는 그 과정에서 얻은 핵심 인사이트를 공유합니다.
참고: 이 글은 메타 엔지니어링 블로그의 원문을 기반으로 재구성했으며, 원문의 전체 내용은 여기에서 확인할 수 있습니다.

핵심 아키텍처: 심층 레이어(Shim Layer)와 이중 스택(Dual-Stack)
메타가 직면한 가장 큰 기술적 난제는 C++ 정적 링커의 One Definition Rule(ODR) 위반이었습니다. 같은 라이브러리의 두 버전을 같은 바이너리에 정적으로 링크하면 수천 개의 심볼 충돌이 발생합니다. 메타는 이 문제를 해결하기 위해 **심층 레이어(Shim Layer)**를 도입했습니다.
심층 레이어의 동작 방식
// 예시: 심층 레이어를 통한 버전 디스패치
// shim/api.h (단일화된 API)
namespace webrtc_shim {
enum class Flavor {
LEGACY,
LATEST
};
class VideoEncoderFactory {
public:
virtual std::unique_ptr<VideoEncoder> CreateEncoder(
const SdpVideoFormat& format) = 0;
virtual ~VideoEncoderFactory() = default;
};
} // namespace webrtc_shim
// shim/encoder_factory_adapter.cc (템플릿 기반 어댑터)
#include "shim/api.h"
#include "webrtc_latest/api/video_codecs/video_encoder_factory.h"
#include "webrtc_legacy/api/video_codecs/video_encoder_factory.h"
template <typename T>
class VideoEncoderFactoryAdapter : public webrtc_shim::VideoEncoderFactory {
public:
explicit VideoEncoderFactoryAdapter(std::unique_ptr<T> impl)
: impl_(std::move(impl)) {}
std::unique_ptr<webrtc_shim::VideoEncoder> CreateEncoder(
const webrtc_shim::SdpVideoFormat& format) override {
// 실제 구현체로 위임
return impl_->CreateEncoder(ConvertFormat(format));
}
private:
std::unique_ptr<T> impl_;
};
// 팩토리 함수: 전역 플레이버 설정에 따라 적절한 구현체 생성
std::unique_ptr<webrtc_shim::VideoEncoderFactory>
CreateVideoEncoderFactory() {
if (GetGlobalFlavor() == webrtc_shim::Flavor::LATEST) {
return std::make_unique<
VideoEncoderFactoryAdapter<webrtc_latest::VideoEncoderFactory>>(
std::make_unique<webrtc_latest::VideoEncoderFactory>());
} else {
return std::make_unique<
VideoEncoderFactoryAdapter<webrtc_legacy::VideoEncoderFactory>>(
std::make_unique<webrtc_legacy::VideoEncoderFactory>());
}
}
핵심 포인트
- 자동 네임스페이스 변경 (Renamespacing): 스크립트를 통해 모든 C++ 네임스페이스를
webrtc::→webrtc_latest::,webrtc_legacy::로 자동 변경했습니다. - 매크로 충돌 해결:
RTC_CHECK,RTC_LOG같은 매크로는 불필요한 include 제거, 드문 매크로 이름 변경, 공통 모듈(rtc_base 등) 공유로 해결했습니다. - 하위 호환성 유지:
using선언을 통해 기존webrtc::네임스페이스에 새로운 플레이버의 심볼을 임포트하여 기존 코드를 수정하지 않았습니다. - 코드 생성 자동화: AST 파싱을 기반으로 심층 코드를 자동 생성하여 하루 1개에서 3~4개로 생산성을 향상시켰습니다.
이 접근법으로 바이너리 크기 증가를 약 38MB에서 5MB로 87% 줄였습니다.

두 번째 도전: 모노레포에서의 지속적 업그레이드 (Feature Branches)
메타는 기능 브랜치를 지원하지 않는 모노레포를 사용하기 때문에, 업스트림 패치를 추적하고 리베이스하는 별도의 방법이 필요했습니다. 최종 선택은 별도 Git 저장소에서 기능 브랜치를 관리하는 것이었습니다.
워크플로우 개요
# 1. 업스트림 릴리스 태그 기준으로 base 브랜치 생성
git checkout -b base/7499 tags/7499
# 2. 각 패치별 기능 브랜치 생성 (예: debug-tools)
git checkout -b debug-tools/7499 base/7499
# ... 패치 적용 및 커밋 ...
# 3. 업스트림 업그레이드 시 (예: 7499 → 7559)
git checkout debug-tools/7499
git merge base/7559 # 충돌 해결
git branch -m debug-tools/7559
# 4. 모든 기능 브랜치를 순차적으로 머지하여 릴리스 후보 생성
git checkout -b rc/7559 base/7559
git merge debug-tools/7559
git merge hw-av1-fixes/7559
# ...
이 방식의 장점은:
- 병렬화가 용이하여 많은 브랜치를 동시에 처리 가능
- Git 히스토리와 컨텍스트가 자동으로 보존
- 향후 LLM 기반 자동 충돌 해결에 적합
- 각 기능 브랜치를 그대로 업스트림에 기여(Pull Request)할 수 있음

결과 및 시사점
메타는 이 프로젝트를 통해 WebRTC 버전을 M120에서 M145로 업그레이드했으며, 주요 앱에서 CPU 사용량 최대 10% 감소, 크래시율 최대 3% 개선, 바이너리 크기 100~200KB(압축 기준) 감소라는 가시적인 성과를 얻었습니다.
한국 개발 생태계에서의 적용 맥락
국내 SI 환경이나 대규모 서비스에서도 오픈소스 라이브러리를 포크하는 경우는 흔합니다. 특히 금융권처럼 안정성을 최우선으로 하는 환경에서는 포크 트랩에 빠지기 쉽습니다. 이 글의 접근법은 다음과 같은 상황에 적용해 볼 수 있습니다:
- 레거시 WebRTC 기반의 화상 회의 솔루션을 운영 중인 기업
- 대규모 모노레포를 사용하는 조직 (예: 토스, 카카오, 네이버)
- A/B 테스트가 필수적인 실시간 통신 기능을 개발하는 팀
주의사항: 이 아키텍처는 C++ 템플릿과 네임스페이스 조작에 대한 깊은 이해가 필요하며, 초기 구축 비용이 상당합니다. 소규모 프로젝트나 단기적인 포크에는 오버엔지니어링이 될 수 있습니다.
함께 보면 좋은 글
다음 단계 학습 방향
- WebRTC 심화: WebRTC의 SDP negotiation, ICE, DTLS 등 프로토콜 레벨 이해
- C++ 모듈화: C++20 모듈(Modules)을 활용한 더 깔끔한 심층 레이어 설계
- AI 기반 유지보수: 메타가 언급한 AI 에이전트를 활용한 빌드 오류 자동 수정 및 충돌 해결 방법 탐구