SFU 해부학 시리즈 #1
4명이 화상회의를 하고 있다. 내 브라우저는 지금 카메라 영상을 인코딩해서 보내고 있다. 그런데 누구에게? 서버에게? 상대방 3명에게 각각?
이 질문에 대한 답이 곧 아키텍처 선택이다. 그리고 그 선택이 서버 비용, 영상 품질, 확장 한계를 전부 결정한다.
WebRTC 기반 실시간 통신에는 크게 세 가지 아키텍처가 있다. Mesh, MCU, SFU. 각각이 무엇이고, 왜 2026년 현재 대부분의 상용 서비스가 SFU를 선택했는지 구조적으로 따져본다.
가장 단순한 구조다. 서버 없이 참여자끼리 직접 연결한다.
[A] <-------> [B]
^ \ / ^
| \ / |
| X |
| / \ |
v / \ v
[D] <-------> [C]
※ 4명 = 연결 6개 (N×(N-1)/2)
※ 각자 3명에게 영상을 인코딩+송신
WebRTC의 PeerConnection이 바로 이 1:1 연결이다. 2명이면 연결 1개, 깔끔하다. 문제는 사람이 늘어날 때다.
N명이면 각 참여자가 (N-1)개의 연결을 유지해야 한다. 4명이면 3개, 10명이면 9개. 내 브라우저가 같은 영상을 9번 인코딩해서 9명에게 보내는 것이다.
현실적인 한계:
Mesh가 잘 동작하는 구간은 2~3명이다. 1:1 통화, 소규모 미팅. 서버가 필요 없으니 인프라 비용이 제로에 가깝다는 장점이 확실하다. 하지만 5명만 넘어가도 품질이 눈에 띄게 떨어진다.
MCU(Multipoint Control Unit)는 정반대 접근이다. 서버가 모든 참여자의 영상을 받아서, 디코딩하고, 한 화면으로 합성(mix)한 뒤, 다시 인코딩해서 각 참여자에게 보낸다.
[A] --영상--> +=============+
[B] --영상--> | MCU |
[C] --영상--> | decode | --합성된 영상--> [A]
[D] --영상--> | mix(합성) | --합성된 영상--> [B]
| encode | --합성된 영상--> [C]
+=============+ --합성된 영상--> [D]
※ 서버가 4개 영상을 디코딩 + 1개 화면으로 합성 + 4번 인코딩
※ 클라이언트는 1개 스트림만 수신하면 됨
클라이언트 입장에서는 천국이다. 보내는 것도 1개, 받는 것도 1개. 저사양 기기에서도 잘 돌아간다. 실제로 레거시 기업용 화상회의(Cisco, Polycom 장비)가 이 방식이었다.
문제는 서버다.
MCU는 2010년대 초반까지 기업 화상회의의 표준이었지만, 참여자 수가 늘고 비용 압박이 커지면서 점점 밀려났다.
SFU(Selective Forwarding Unit). 이름 그대로, 서버가 하는 일은 선택적 전달(forwarding)뿐이다.
[A] --영상--> +==============+ --A영상--> [B]
[B] --영상--> | SFU | --A영상--> [C]
[C] --영상--> | | --A영상--> [D]
[D] --영상--> | 디코딩 없음 | --B영상--> [A]
| 인코딩 없음 | --B영상--> [C]
| 그냥 전달 | --B영상--> [D]
+==============+ -- ...
※ 서버는 RTP 패킷을 열어보지 않는다 (암호화된 채로 전달)
※ 각 클라이언트는 (N-1)개 수신 스트림을 직접 디코딩
핵심을 한 문장으로: 서버는 디코딩도 인코딩도 하지 않는다. 받은 미디어 패킷(RTP)을 암호화된 상태 그대로 필요한 참여자에게 전달만 한다. 택배 허브가 상자를 열어보지 않고 분류만 해서 보내는 것과 같다.
이게 왜 강력한가:
대신 공짜 점심은 없다:
이 문제를 해결하는 기술이 Simulcast다. 송신자가 같은 영상을 고화질/저화질 여러 벌로 인코딩해서 보내면, SFU가 수신자의 상황(대역폭, 화면 크기)에 맞는 버전을 골라서 전달한다. 발표자 영상은 720p로, 썸네일은 180p로 받는 식이다. Simulcast는 이 시리즈 뒤에서 자세히 다룬다.
| 항목 | Mesh | MCU | SFU |
|---|---|---|---|
| 서버 필요 | 없음 (시그널링만) | 필수 (고사양) | 필수 (저사양 가능) |
| 서버 CPU | 없음 | 매우 높음 (decode+encode) | 낮음 (forwarding만) |
| 클라이언트 CPU | 높음 (N-1개 인코딩) | 낮음 (1개만 처리) | 중간 (N-1개 디코딩) |
| 업로드 대역폭 | 높음 (N-1개 송신) | 낮음 (1개 송신) | 낮음 (1개 송신) |
| 다운로드 대역폭 | 높음 (N-1개 수신) | 낮음 (1개 수신) | 중~높음 (N-1개 수신) |
| 확장성 | 2~3명 | 수십명 | 수백~수천명 |
| 추가 지연 | 없음 | 200~500ms | 거의 없음 |
| 레이아웃 자유도 | 자유 | 서버 고정 | 자유 |
이론이 아니라 실제 서비스가 어떤 선택을 했는지 보면 답이 명확하다.
공통점이 보인다. 전부 SFU가 기본이다. MCU의 서버 비용을 감당하면서 수백만 사용자를 처리하는 것은 현실적으로 불가능하다. Mesh는 소규모에서만 동작한다. SFU가 서버 비용과 품질 사이에서 최적의 균형점을 제공한다.
물론 SFU가 만능은 아니다. "패킷을 전달만 한다"는 단순해 보이는 일이 실제로는 꽤 복잡하다. ICE로 연결을 수립하고, DTLS로 암호화를 협상하고, SRTP로 미디어를 보호하고, RTCP로 품질을 관리하고, Simulcast로 대역폭을 최적화해야 한다. 하나의 UDP 포트에서 수백 개의 미디어 스트림을 분류해야 한다.
SFU가 정말로 하는 일은 "단순한 전달"이 아니다. 이 시리즈에서 그 내부를 하나씩 해부한다.
다음 편: [SFU 해부학 #2] ICE-Lite로 NAT 뚫기 — STUN/TURN 없이 가능?
이 글은 OxLens — Rust로 만드는 경량 SFU 서버 프로젝트를 개발하면서 배운 것들을 정리한 시리즈입니다.