비디오 Consumer 수 상수화로 서버 부하 최적화
- 사용자 경험
- 최적화

프로젝트 소개
어떤 프로젝트를 진행하고 있었는가?
Plum은 비대면 강의의 단방향 소통 문제를 해결하기 위해 만든 실시간 화상 강의 서비스다. WebRTC 기반의 mediasoup를 사용해 발표자와 참여자가 실시간으로 영상과 음성을 주고받을 수 있도록 구현했다.
한 강의실에 평균 100명이 참여한다고 가정하고 설계했다. 참여자가 늘어날수록 서버와 클라이언트 양쪽에서 리소스 부담이 커지기 때문에, 기능 구현과 동시에 성능을 고려해야 하는 상황이었다.
문제 발견
어떤 문제에 마주했는가?
처음에는 모든 참여자의 비디오를 동시에 수신하는 방식으로 구현했다. 기능 자체는 문제없이 동작했지만, Prometheus 모니터링을 통해 예상치 못한 구조적 문제를 발견했다.
참여자 수가 늘어날수록 mediasoup 서버에서 생성되는 Consumer 수가 N×(N-1)로 폭증하고 있었다. 100명이 참여한다면 Consumer 수는 9,900개에 달한다. 실제로 20명 이상이 참여하는 시점부터 서버 CPU가 급증했고, 브라우저에서도 다수의 비디오를 동시에 디코딩하면서 렌더링 프레임이 떨어지기 시작했다.

모든 참여자의 비디오를 동시에 수신하는 구조 자체가 근본 원인이었다.
해결 과정
첫 번째 시도: 요청 시점 수신
어떤 방식으로 해결하고자 했는가?
페이지에 보이는 참여자의 비디오만 수신하는 방식을 적용했다. 한 페이지에 5명이 표시된다면 해당 5명의 Consumer만 생성하고, 페이지를 벗어난 참여자는 즉시 Consumer를 해제하는 방식이다. 화면에 없는 참여자의 Consumer를 생성하지 않으니 리소스 사용량은 크게 줄었다.
어떤 한계점이 존재했는가?
문제는 페이지를 전환하는 순간이었다. 새 페이지의 참여자들은 아직 Consumer가 없는 상태이기 때문에, 전환과 동시에 Consumer를 새로 생성해야 했다. Consumer 생성에는 서버와의 시그널링 과정이 필요하기 때문에 짧은 지연이 불가피했다.
로딩 스피너를 보여주는 방식으로 대응할 수 있었지만, 이는 근본적인 해결이 아니었다. 페이지를 넘길 때마다 매번 로딩 스피너를 마주하는 경험은 화상 강의라는 서비스 특성과 맞지 않았다.
두 번째 시도: ±1 프리패치
어떤 방식으로 해결하고자 했는가?
페이지 전환 시 지연의 원인은 전환 시점에 Consumer를 생성하는 것이었다. 이를 해결하기 위해 현재 페이지뿐 아니라 앞뒤 페이지의 Consumer를 미리 구독해두는 방식을 적용했다.
페이지당 5명 기준으로 현재 페이지 5명, 앞 페이지 5명, 뒤 페이지 5명으로 최대 15명의 Consumer만 유지한다. 페이지를 전환하면 이미 구독된 상태이기 때문에 지연 없이 바로 재생됐다. 동시에 새로운 앞뒤 페이지를 미리 구독하고, 멀어진 페이지의 Consumer는 해제해 항상 최대 15명을 유지하도록 했다.

참여자가 100명이라도 동시에 유지되는 Consumer는 최대 15 × 14 = 210개로 고정된다. 전체 수신 방식의 9,900개와 비교하면 97% 이상 절감되는 수치다.

왜 ±1 로 설정했는가?
±1이 적절한 범위인지 확인하기 위해 실제로 테스트를 진행했다. 추가 Consumer 수신이 서버와 클라이언트 성능에 미치는 영향을 측정했고, 최대 10개의 추가 Consumer를 수신하는 상황에서도 서버와 클라이언트 모두 성능 저하가 없음을 확인했다.
그렇다면 ±2는 어떨까? ±2로 확장하면 구독 인원은 25명, Consumer 수는 25 × 24 = 600개가 된다. ±1의 210개와 비교하면 약 3배 증가하는 수치다. 성능 테스트 결과 ±2에서도 성능 저하는 없었다. 하지만 UX 개선 효과가 미미했다. ±1에서 이미 앞뒤 페이지가 미리 구독된 상태이기 때문에, ±2를 추가해도 사용자가 체감할 수 있는 차이가 없었다.
불필요한 리소스 소모 없이 UX를 보장할 수 있는 최소한의 범위가 ±1이라고 판단했다.
결론
결과적으로 얼마나 개선됐는가?
±1 프리패치 전략을 적용한 결과, 100명 참여 기준으로 Consumer 수가 9,900개에서 210개로 줄었다. 참여자 수가 늘어나도 Consumer 수는 항상 210개로 고정되어 서버 부하를 예측 가능한 범위로 제한할 수 있게 됐다.
실제 유저 57명이 동시 접속한 테스트에서도 CPU 급증 없이 안정적인 연결을 유지했다. 페이지 전환 시에도 로딩 지연 없이 바로 재생되어 사용자 경험도 함께 개선됐다.
최적화는 단순한 수치가 아니라, 사용자가 느끼는 불편을 줄이는 것이다.
이번 작업을 통해 성능 최적화의 기준이 단순히 "얼마나 줄였는가"가 아니라는 것을 깨달았다. ±2도 성능상 문제가 없었지만, 사용자가 실제로 체감할 수 있는 변화가 없다면 리소스를 더 쓸 이유가 없었다. 최적화의 기준은 "사용자가 체감하는가"여야 한다.
지표가 없으면 문제도 보이지 않는다.
Prometheus 모니터링 없이는 이 문제를 인지하지 못했을 것이다. 코드만 봐서는 N × (N-1) 구조가 얼마나 빠르게 폭증하는지 직관적으로 파악하기 어렵다. 측정 가능한 지표가 있었기에 문제를 파악하고 개선할 수 있었다. 앞으로는 성능 개선 작업을 시작하기 전에 먼저 측정 환경을 갖추는 것을 습관으로 삼을 것이다. 지표를 기반으로 문제를 분석하는 방식을 이어가려 한다.
어떤 단점까지 허용할 수 있는지를 먼저 정의하자.
선택지를 비교할 때 장점만 보는 게 아니라 단점의 허용 범위를 먼저 정의해야 한다는 것을 배웠다. 요청 시점 수신은 리소스 효율이 좋았지만 UX 단점이 허용 범위를 넘었고, ±2는 성능은 충분했지만 추가 비용 대비 UX 개선이 미미했다. 각 선택지의 단점이 허용 가능한지를 먼저 판단한 뒤 결정하는 과정이 중요했다.

