Sendbird

Sendbird Calls의 Audio-Only Room 부하테스트 진행하기

Sendbird Calls의 Audio-Only Room 부하테스트 진행하기

상담을 요청해보세요

Sendbird의 제품에 대해 더 자세한 정보를 얻고 문의사항에 대한 답변을 받으세요.

상담 요청하기

안녕하세요. Sendbird Engineering의 Server Platform, Call Squad 소속 Software Engineer 박창완입니다. 

Introduction

Sendbird Calls Group Call Product의 Load Test를 진행한 이야기를 전해드립니다. Load Test의 필요성, 계획, 이행 및 결과 분석을 어떻게 수행하였는지 알려드립니다.  결론부터 말하면, 하나의 Room 안에서 동시에 1350명의 유저가 통화 품질의 저하없이 audio call 을 할 수 있었습니다. 

Index

  • Load Test의 필요성
  • 문제 정의
  • Sendbird Calls Group Call Architecture
  • Load Test 준비 과정
    • 개발 도구
    • User Scenario
    • Resource Allocation/Isolation
    • Monitoring
    • 전체 Scenario 요약
  • 결과 및 분석
  • 결론

Load Test의 필요성

필요성을 설명하기에 앞서, Sendbird Calls Group Call Product에 대한 배경 지식을 요약하여 전달합니다. 통화를 수행할 수 있는 최소 단위는 User입니다. 이 User들은, Group call에 참여하기 위하여 Room에 들어갈 수 있습니다. Room에 들어간 User들은 서로의 음성이나 영상 데이터를 주고 받아, Group Call를 진행합니다.

Sendbird Calls Group Call Product는 두 가지의 Room을 제공합니다.

  1. large_room_for_audio_only: Audio only Group Call. 음성 통화만 가능하며, 최대 참여자 수는 25명 입니다.
  2. small_room_for_video: Video Group Call. 영상 통화가 가능하며, 최대 참여자 수는 6명 입니다.

Product의 최대 참여자 수를 정확하게 알아야 product를 홍보하거나 selling 할 때 큰 도움을 줄 수 있습니다. 고객들 또한 명확한 제한을 알아야 고객들의 Use Case에 Sendbird Call이 적합한지 판단할 수 있습니다. 우리 제품의 최대 성능을 정량적으로 측정하기 위하여, 다시 말해 실제 최대 참여자 수가 몇 명인지 알기 위하여 Load Test가 필요하게 되었습니다. 

두 Room 중 MCU 방식으로 구성된  large_room_for_audio_only에만 Load Test를 진행하였습니다. small_room_for_video는 SFU 방식으로 구성됩니다. SFU 방식의 경우 최대 참여자의 수가 Client의 network IO 성능에 bottleneck이 걸릴 것이라 생각되어 이번 테스팅에서 제외하였습니다.

문제 정의

문제를 풀기 위해, 문제를 명확하게 정의합니다.

> large_room_for_audio_only, 즉 Audio only Group Call에 최대 몇 명까지 참여하여 Group Call를 원활하게(smoothly) 진행할 수 있는가?

여기서 원활하게 란 단어를 정의하면 다음과 같습니다.

  1. 정량적 정의: Call Quality Metric(MOS)가 안정적이며 큰 폭으로 하락하지 않는다.
  2. 정성적 정의: Audio delay가 일어나지 않으며, 참여자의 목소리가 다른 참여자들에게 명확하게 전달된다.

MOS(Mean Opinion Score)란, 통화 품질을 정량적으로 측정하기 위하여 널리 사용되는 metric입니다. Jitter, Latency, Packet Loss등 컴퓨터가 수집할 수 있는 여러 metric을 사용하여 MOS 값이 계산됩니다. 실제 사람이 판단하는 통화 품질을 근사한 metric입니다. MOS는 1부터 5 사이의 값을 가지며, 값이 5에 가까울 수록 통화 품질이 좋다(excellent)고 판단할 수 있습니다.

Sendbird Calls Group Call Architecture

이 Section에서는 Sendbird Calls Group Call Architecture를 설명합니다. Group Call Architecture를 이해하여야 Load Test의 계획 및 결과를 이해할 수 있습니다.

Sendbird Calls는 WebRTC 스택을 사용하여 개발되었으며, MSA 구조를 가집니다. WebRTC endpoint를 관리 및 생성(Signaling 등)하기 위하여 API/WS/TURN Server가 존재합니다. 이 Component들 중 API Server는 Group Call Dedicated Server Component인 Media Server를 관리하는 Controller역할을 수행합니다. Media server에서 WebRTC를 통해 SDK(end-user client)와 연결되어 media stream을 관리하고 처리합니다. 이를 위해서 WebRTC Endpoint의 생성 또한 Media server에서 관리합니다.

large_room_for_audio_only는 MCU(Multi-point Control Unit) 방식으로 구현되었습니다. MCU 방식이란, client가 server에게 보내는 모든 media stream을 mixing해서, 하나의 media stream으로 생성한 다음 이를 모든  client들에게 다시 전달하는 구조입니다. 이 mixing 작업은 높은 컴퓨팅 파워가 요구되며, Group call의 참여자가 많아질수록 연산이 더 많이 필요합니다. Mixing 작업은 Media Server에서 수행됩니다. 이런 MCU 방식을 사용하고 있기에,  Load Test시 참여자 수에 비례하여 CPU Usage가 증가하며, 최대 참여자수는 Media Server의 성능과 깊은 연관이 있을 것이라예상할 수 있습니다.

MCU Architecture

Load Test 준비: 개발 도구 및 환경

Media Stream을 보내고 받을 User를 생성하기 위하여 AWS EKS를 사용하였습니다. User를 생성하기 위한 환경을 Containerize하여 사용하기 위해 AWS ECR를 사용하였습니다. Load Test에 참여하는 User의 수를 간단하게 scale in/out하고, 각 User에 할당되는 Resource를 명시적으로 할당하기 위하여 Plain EC2 대신 AWS EKS를 채택하였습니다. EKS를 활용하여, 한 Node에 여러 User를 할당하여 효율적으로 Load Test를 수행할 수 있을거라고 예상하였습니다. 개발 환경에 필요한 원하는 상태를 선언적(Declarative)으로 관리하여, yaml파일 수정만으로 테스트 환경을 쉽게 조절할 수 있는 것도 큰 장점이었습니다.

Sendbird에서는 Datadog를 활용하여 서비스 모니터링을 하고 있습니다. 그래서 이번 Load Test에서도 동일하게 여러 컴포넌트들의 리소스 상태를 확인하고, 서비스 퀄리티를 모니터링하기 위해서 Datadog를 사용하였습니다. 

실제 유저의 동작을 mimic하기 위해서 각 Node의 Pod에서 Sendbird Calls JS SDK를 사용하는 환경을 만들어야 했습니다. 그래서 Pod에서 Puppeteer를 띄우고, SDK를 실행하여 고객이 Product를 사용하는 환경을 재현했습니다. 각 도구들을 실질적으로 어떻게 활용하였는지는 다음 Section에서 더 자세히 설명합니다. 

Load Test 준비: User Scenario

효과적으로 Load Test를 수행하기 위하여, 각 User들은 실제 Group Call User가 보이는 패턴을 따라해야 합니다. User가 Room에 들어가야 하므로, Room은 User보다 먼저 생성되어야 합니다. 다음과 같은 User Scenario를 정의합니다.

User Scenario(Recurring: Repeating 1 to 8)

  1. User 생성(다음 loop시 생성 과정 생략, 기존에 생성된 User 사용)
  2. Puppeteer 초기화
  3. User Login
  4. User가 Room에 참여
  5. Server로부터 Audio Stream이 받아지는지 확인
  6. T 초 동안 Group Call 진행(T의 값은 Load Test마다 조절)
  7. User가 Room에 나감
  8. Puppeteer 끄기

User가 Group Call에 참여하는 동안(User Scenario 6번), SDK는 3초마다 통화 품질에 관련된 metric를 수집하여 API Server에게 전달합니다. 예시 payload는 다음과 같습니다.

Example Call Quality Payload

{

  “packets_lost”: 0,

  “mos”: 4.3551722632,

  “packets_lost_rate”: 0,

  “total_packets_lost_rate”: 0,

  “rtt”: 83,

  “jitter”: 4,

  “packets_received”: 50,

  “total_packets_lost”: 0,

  “jitter_buffer_delay”: 907545.6,

  “mos_inverted”: -4.3551722632,

  “retransmitted_packets_sent”: 0,

  “total_packets_received”: 14823,

  “codec”: “audio/OPUS”,

  “total_packets_sent”: 14827

}

Load Test 준비: Resource Allocation/Isolation

각 User는 하나의 Pod에 할당합니다. WebRTC는 컴퓨팅 파워에 따른 RTC quality를 조절하기때문에 User의 리소스 사이에 최대한 독립성을 만들어 주어야 Load Test의 정확도가 올라갈 수 있습니다. 한 User가 동작하기 위해 필요한 Resource를 측정하여, 하나의 Pod에 Resource를 얼마나 할당할 지 계산하였습니다. Pod에는 Resource limit을 풀어두었고, Single Node에서 Single Pod 일 때와 Double Pod일 때의 Resource 소모량을 측정하였습니다. K8S dashboard로 CPU및 Memory Usage를 확인하였으며, AWS CloudWatch로 Network Inbound Traffic를 측정하였습니다. 그 결과는 다음과 같았습니다.

  • Single Node, Single Pod: CPU: 200m, MEM: 100 MB, Inbound Traffic: 0.12 MB/min
  • Single Node, Double Pod: CPU: 400m, MEM: 200 MB, Inbound Traffic: 0.28 MB/min

위의 경향성을 분석하여, 각 User가 원활하게 동작하기 위해 Pod에 할당할 Resource 양을 결정하였습니다.

  • CPU: 250m (50m buffer). MEM: over 150 MB(50 MB buffer): 600 MB

각 Pod에 할당할 Resource를 정했으니, Node instance type 및 각 Node에 몇 개의 Pod를 할당할지 계산합니다. 사용한 Instance Type은 m5.large이며 가용 Resources는 2vCPU, 8GiB입니다. 한 Node당, 할당한 User가 생성된 Pod의 수는 6개입니다. 다음과 같은 간단한 계산을 통하여, 할당한 Resource가 충분하다는 것을 확인할 수 있습니다.

  • CPU: 250m * 6 = 1500m < 2000m (2vCPU)
    • 남은 500m는 K8S control pods/dashboard pods를 위해 남겨둡니다.
  • MEM: 600 MB * 6 = 3600 MB < 8000 MiB
    • 남은 Memory중 약 700 MB는 Node의 default Memory usage입니다.

정리하여, Node로 m5.large instance type를 사용하며, 각 Node당 6개의 Pod를 할당합니다. 한 Pod당 하나의 User가 대응되므로, 한 Node당 6명의 User가 할당됩니다.

Load Test 준비: User Scenario를 구현한 Javascript code를 각 Pod에게 전달하기 위해 K8S ConfigMap을 사용하였습니다. ECR에서는 Sendbird Calls Javascript SDK 및 임의의 User Scenario Javascript code를 looping하는 executor만 존재하고, configmap를 통하여 어떤 user scenario를 줄 지 업데이트하는 방식으로 구현하였습니다.

Load Test 준비: Monitoring

Room에 참여하는 User 수가 증가함에 따라, Call Quality(MOS) 및 Server Resource 변화를 monitoring해야 합니다. 이를 통하여 최대 참여자의 수를 확인할 수 있습니다.

Call Quality를 monitoring하기 위하여 3초마다 통화 품질에 관련된 metric를 수집하여 API Server에게 전달합니다. 이 metric 중에서 MOS값을 수집하여 실시간으로 볼 수 있는 Datadog Load Tester Dashboard를 만들었습니다. 이 Dashboard를 사용하여 하위 10개 MOS, Min/Max/Avg/Median MOS를 실시간으로 모니터링합니다. 또한 Server component를 monitoring 하는 Dashboard를 통해서 Media Server의 CPU Behavior와 Network IO를 확인할 수 있습니다.

Load Test 준비: 전체 Scenario 요약

목표한 최대 참여자 수를 N이라고 했을 때, Load Testing에 필요한 전체 Scenario는 다음과 같습니다. 

  1. Sendbird Dashboard를 사용하여 application 생성: app_id 생성
  2. Sendbird Calls Platform API: Create a Room을 사용하여 Room 생성: room_id 생성
  3. Boot up EKS Cluster, Spawn Nodes
  4. Configmap를 활용하여 app_idroom_id, user scenario를 각 Pod에 전달 할 준비.
  5. 목표한 최대 참여자 수 만큼 점진적으로 Pod의 수를 Scale out
    1. Pod들은 앞서 전달한 user scenario를 수행
    2. 초당 참여자 수 증가율 설정
  6. Monitoring 도구로 Call Quality(MOS) 및 Server Resource 확인
  7. Reap EKS Cluster

 

결과 및 분석

저희가 처음에 목표한 최대 참여자의 수는 2160명이었기에, N=2160으로 설정하였습니다. 총 2160개의 Pod가 필요하므로 사용된 총 Node의 수는 2160 // 6 = 360개 입니다. 매 5초마다 5명의 User가 Room에 참여합니다. 매 초마다 한 User씩 참여하므로, 모든 User가 Room에 참여하기 위해 2160초 혹은 36분이 소요됩니다. 각 유저는 T=72분 동안 Group Call를 진행합니다.

가용 가능한 모든 CPU 자원을 소모하여 throttling이 발생하는 것을 방지하기 위해, Media Server는 AWS에서 사용 가능한 최대 성능인 c5.24xlarge instance type를 사용하였고, Media Server의 최대 CPU Threshold를 80%로 설정하였습니다. 

아래는 Load Test를 진행하는 과정에서 기록한 event timeline입니다. Binary Search와 비슷한 형태로 최대 참여자 수를 측정하였습니다.

  • 0 min: User들이 Room에 들어오기 시작합니다.
  • +3m: Media Server CPU가 선형적으로 증가하는 것을 멈추고 상수함수로 전환하였습니다.
    •  Media Server Process의 single thread CPU utilization이 100%를 기록합니다.
    • 이 때 1400명의 User가 Room에 참여한 상황입니다.
  • +46m: 2000명의 User가 마침내 Room에 참여합니다.
    • Audio delay를 확인하였습니다. 간헐적인 오디오 침묵이 이어집니다.
    • User들이 정상적인 Group Call를 하지 못하므로, Pod(User)의 개수를 Scale in하기로 결정합니다.
  • +77m: User의 수를 Scale in하여, User의 수가 1400명에 도달합니다.
    • Audio delay가 현저히 줄었지만 아직 완벽하게 없어지지 않았습니다.
    • 아직도 Media Server Process의 single thread CPU utilization이 100%를 기록합니다.
  • +87m: User의 수를 Scale in하여, User의 수가 1300명에 도달합니다.
    • Audio delay가 완벽하게 없어졌습니다.
  • +104m: User의 수를 Scale in하여, User의 수가 1000명에 도달합니다.
    • Media Server Process의 single thread CPU utilization이 65%를 기록합니다.
    • User들이 Functional한 Group Call를 하고 있으므로, Pod(User)의 개수를 Scale out하기로 판단합니다.
  • +125m: User의 수를 Scale out하여, User의 수가 1400명에 도달합니다.
    • 다시 Media Server Process의 single thread CPU utilization이 100%를 기록합니다.
    • Audio delay가 다시 생기기 시작합니다.
  • +126m: User의 수를 Scale in하여, User의 수가 1350명에 도달합니다.
    • Media Server Process의 single thread CPU utilization이 하락하여 90%를 기록합니다.
    • Audio delay가 완벽하게 없어진 것을 확인했습니다.

위 event timeline이 진행되는 동안, MOS 값은 시종일관 4를 넘게 유지하였습니다. Audio delay가 실제로 발생하였음에도, SDK가 판단한 Call Quality는 좋았습니다. 유동적으로 User의 수를 조절하여, 최적해인 1350명이라는 수치를 얻을 수 있었습니다.

다음은 Load Test를 진행하는 과정에서 얻은 metric 및 그래프입니다.

Call Quality(MOS) 그래프입니다. Audio delay가 발생해도, MOS 값의 유의미한 변화는 관찰되지 않았습니다.

Media Server의 CPU Usage Behavior입니다. CPU upper threshold가 80%임에도 불구하고, Load Test시에 대한 최대 CPU Utilization은 55%를 넘지 않았습니다. 최대 참여자 수는 Media Server CPU Threshold에 영향받는 것이 아니라, single thread CPU utilization에 bottleneck이 걸린다는 것을 확인할 수 있었습니다. Single thread에 대한 가용 CPU가 넉넉할 때는, CPU Usage가 선형적으로 증가하였습니다. Single thread의 CPU Utilization이 100%에 도달하기 전까지 Room에 참여한 User수와 CPU가 정비례하는 것을 확인할 수 있었습니다.

Bottleneck이 되는 thread는 audio mixing을 수행합니다. 방에 참여한 모든 User의 upload media stream을 처리하기에, Thread CPU Usage는 O(N)(N은 참여자 수) 을 따르는 것을 이론 및 실험적으로 모두 확인하였습니다.

Media Server의 Network Inbound/Outbound Behavior입니다. Audio Delay가 발생함에도 불구하고, Network IO는 참여한 User 수에 정비례하게 늘어났습니다. CPU usage behavior와 다른 패턴을 보입니다.

결론

c5.24xlarge instance로 Media Server를 setup하였습니다. 이 때 MCU 방식으로 구성된  large_room_for_audio_only Room의 최대 참여자 수는 1350명이었습니다. 최대 참여자 수의 bottleneck은 Media server에 있는 Audio Mixer Thread의 CPU Utilization이었습니다. 최대 참여자 수에 도달하였을 때 전체 Media Server의 CPU Utilization은 약 50%이었습니다. 최대 참여자 수에 도달하였을때, User들은 Audio delay 및 MOS drop을 경험하지 않았습니다. MOS 값은 대부분(p99) 4 이상이어서, Call Quality drop은 일어나지 않는다는 것을 확인하였습니다.

Sendbird Calls의 Audio Only Group Call 는 최대 1350명이 동시에 Group Call에 참여하여, 동시에 말을 하더라도 안정적으로 서비스가 유지됨을 확인하였습니다. 이러한 정량적인 데이터를 바탕으로, Sendbird Calls는 더 효율적이고 안정적인 Scalability 정책을 수립할 수 있게 되었습니다. Product의 최대 참여자 수를 정확하게 알아내어 고객들의 Use Case에 Sendbird Call이 적합한지 판단할 때에 도움을 줄 수 있게 되었습니다.

아쉬웠던 점

MOS가 높게 나와도 audio delay및 여러 예상치 못한 상황이 발생할 수 있습니다. 이러한 상황이 발생하는지 확인하기 위해, Load Test가 진행되는 Room에서 실제 두 명의 개발자가 SDK를 사용하여 들어가 말을 하는 작업을 수행하였습니다. 여러 User 가 Room에 참여한 상황에서, 개발자 User가 말을 하면 다른 개발자 User가 실제로 그 말을 들을 수 있는지 확인하였습니다. 이런 방법으로 Audio delay를 확인하여 유동적으로 계획을 수정하여 Load Test를 수행하였습니다. 이러한 delay에 대해 RTT나 latency를 모두 monitoring하여 정량적으로 Audio delay의 발생 유무를 판단할 수 있었다면 더 정확한 Load Test 결과를 얻을 수 있었을 것입니다.

Categories: Sendbird