From 88ef92628d9373e89df31b8b8f03b7e9d0458b1b Mon Sep 17 00:00:00 2001 From: ttipo Date: Tue, 23 Jun 2026 21:05:36 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=99=94=EC=83=81=ED=9A=8C=EC=9D=98=20?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=205=20=E2=80=94=20=EB=B6=80=ED=95=98=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=8F=84=EA=B5=AC/=EB=B0=A9?= =?UTF-8?q?=EB=B2=95=EB=A1=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit livekit-cli(lk load-test) 기반 부하 테스트 러너와 가이드를 추가한다. 다수 publisher/subscriber 를 시뮬레이션해 SFU 부하·미디어 품질을 측정한다. - livekit/load-test.sh: docker 로 lk load-test 실행. preset(smoke|medium|full) + 환경변수 조정(PUBLISHERS/SUBSCRIBERS/DURATION/RESOLUTION/LAYOUT/SIMULCAST). 자격증명은 루트 .env 에서 읽는다. - livekit/LOAD_TEST.md: 실행/결과 해석/합격 기준/실행 대상(Cloud=smoke만, 셀프호스팅=full·클러스터 부하분산)/서버 모니터링/한계 정리. 검증: smoke(2 pub + 3 sub, 15s) 를 dev(LiveKit Cloud) 대상으로 실행 → 6/6 트랙 구독, 패킷 손실 0%, 정상 비트레이트 확인(도구 동작 검증 완료). 대규모(160명) 검증은 단계 6 셀프호스팅 환경에서 수행한다. Co-Authored-By: Claude Opus 4.8 (1M context) --- livekit/LOAD_TEST.md | 100 +++++++++++++++++++++++++++++++++++++++++++ livekit/load-test.sh | 85 ++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 livekit/LOAD_TEST.md create mode 100755 livekit/load-test.sh diff --git a/livekit/LOAD_TEST.md b/livekit/LOAD_TEST.md new file mode 100644 index 0000000..7c72e14 --- /dev/null +++ b/livekit/LOAD_TEST.md @@ -0,0 +1,100 @@ +# 화상회의 부하 테스트 가이드 (단계 5: 확장 검증) + +`lk load-test`(livekit-cli)로 가상 참여자를 대량 접속시켜 **SFU 부하·미디어 품질**을 측정한다. +러너: [`livekit/load-test.sh`](./load-test.sh) + +--- + +## 1. 목적 + +- 다수 참여자 접속 시 **품질(지연·패킷 손실·대역폭)** 이 유지되는지 검증 +- (셀프호스팅 클러스터) 여러 SFU 노드로 **미디어 트래픽이 분산**되는지 검증 +- 문서 목표: 최대 **160명** 단일 방, 동시 표시 ~50명, Simulcast/SVC 로 수신측 화질 차등 + +--- + +## 2. 사전 준비 + +- Docker 설치 (러너가 `livekit/livekit-cli` 이미지를 사용) +- 자격증명: 루트 `.env.${APP_ENV:-development}` 의 `LIVEKIT_URL` / `LIVEKIT_API_KEY` / `LIVEKIT_API_SECRET` + - 러너가 자동으로 읽어 컨테이너에 주입한다. + +--- + +## 3. 실행 + +```bash +# 소규모(도구/연결 검증) — Cloud 에서도 안전 +./livekit/load-test.sh smoke # 2 pub + 3 sub, 15s, 저해상도 + +# 중간 규모 +./livekit/load-test.sh medium # 5 pub + 30 sub, 1m + +# 대규모(문서 목표) — 반드시 셀프호스팅/전용 인스턴스에서! +./livekit/load-test.sh full # 10 pub + 150 sub, 2m, 고해상도 +``` + +세부 조정(환경변수): + +| 변수 | 의미 | 예 | +|---|---|---| +| `PUBLISHERS` | 영상 발행 가상 참여자 수 | `10` | +| `SUBSCRIBERS` | 구독 전용 가상 참여자 수 | `150` | +| `DURATION` | 지속 시간 | `2m`, `30s` | +| `RESOLUTION` | 발행 화질 | `low` / `medium` / `high` | +| `LAYOUT` | 구독 레이아웃(구독 수 모사) | `speaker` / `3x3` / `4x4` / `5x5` | +| `SIMULCAST` | Simulcast 발행 | `on`(기본) / `off` | +| `ROOM` | 방 이름 | `loadtest-full` | +| `NUM_PER_SECOND` | 초당 입장 가속 | `5` | + +```bash +# 예) 160명 규모, 진행자 1명 고해상도 + 159명 구독, 3분 +PUBLISHERS=1 SUBSCRIBERS=159 RESOLUTION=high DURATION=3m ./livekit/load-test.sh full +``` + +--- + +## 4. 결과 읽는 법 + +러너는 두 표를 출력한다. + +- **Track loading**: 구독자별 각 트랙의 수신 패킷 수, 비트레이트, 패킷 손실 +- **Subscriber summaries**: 구독자별 `Tracks N/N`(구독 성공 비율), 총 비트레이트, 총 패킷 손실, 에러 + +**합격 기준(목표치)** +- `Tracks` 가 모두 `N/N` (요청 트랙 전부 구독) +- `Pkt. Loss` 가 낮게 유지 (목표 < 2~3%) +- `Error` = 0 +- 참여자 수를 늘려도 위 지표가 급격히 악화되지 않을 것 + +> smoke 예시 결과: 6/6 트랙, 패킷 손실 0%, ~119kbps/구독자 — 정상. + +--- + +## 5. 어디서 실행하나 (중요) + +| 대상 | 권장 | +|---|---| +| **LiveKit Cloud** (현재 dev) | `smoke` 만. 대량 부하는 동시접속 쿼터·약관 위반 소지 → 금지 | +| **셀프호스팅 단일 노드** (단계 6) | `medium`~`full` 로 단일 노드 한계 측정 | +| **셀프호스팅 클러스터** (단계 6) | `full` 이상으로 **부하 분산(노드 간 트래픽 분배)** 검증 | + +- 부하 **분산** 검증은 SFU 노드가 2개 이상인 클러스터 구성에서만 의미가 있다(단일 노드는 분산 대상이 없음). + +--- + +## 6. 서버 측 모니터링 (셀프호스팅) + +부하 중 서버 지표를 함께 본다. + +- LiveKit Prometheus 메트릭(`/metrics`) + Grafana: 노드별 참여자/트랙 수, CPU, 송수신 대역폭 +- 호스트: CPU, 네트워크 대역폭, UDP 버퍼 +- 노드별 참여자 분포(클러스터링/캐스케이딩이 고르게 분산하는지) + +--- + +## 7. 한계 / 참고 + +- `lk load-test` 의 발행 미디어는 합성 트래픽이라 실제 카메라 화질과 100% 동일하지는 않다(상대적 부하 비교용으로 충분). +- 클라이언트 측 다운링크(시청자 회선) 부하는 별도 검증 필요. +- 본 단계는 **검증 도구·방법론**이며, 실제 대규모 검증은 단계 6(셀프호스팅 배포) 환경에서 수행한다. diff --git a/livekit/load-test.sh b/livekit/load-test.sh new file mode 100755 index 0000000..13d2027 --- /dev/null +++ b/livekit/load-test.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# ===================================================================== +# LiveKit 부하 테스트 러너 (화상회의 단계 5: 확장 검증) +# --------------------------------------------------------------------- +# livekit-cli(lk load-test)로 가상 publisher/subscriber 를 대량 접속시켜 +# SFU 의 부하·미디어 품질(지연/패킷손실/대역폭)을 측정한다. +# +# 사용법: +# ./livekit/load-test.sh [preset] +# preset: smoke(기본) | medium | full +# +# 세부 조정(환경변수로 덮어쓰기): +# ROOM, PUBLISHERS, SUBSCRIBERS, DURATION, RESOLUTION(low|medium|high), +# LAYOUT(speaker|3x3|4x4|5x5), SIMULCAST(on|off), NUM_PER_SECOND +# +# 자격증명은 루트 .env.${APP_ENV:-development} 에서 읽는다 +# (LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET). +# +# ⚠️ 주의: +# - 대규모(full=160참여자) 테스트는 반드시 "셀프호스팅 LiveKit(단계 6)" 또는 +# 전용 테스트 인스턴스를 대상으로 실행하세요. +# - LiveKit Cloud 무료/개발 티어에 대한 대량 부하는 동시 접속 쿼터 초과 및 +# 이용약관 위반 소지가 있습니다. Cloud 에서는 smoke(소규모)로 도구 검증만 하세요. +# - 부하 분산(클러스터링) 검증은 SFU 노드가 2개 이상인 셀프호스팅 구성에서만 의미가 있습니다. +# ===================================================================== +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env.${APP_ENV:-development}}" +PRESET="${1:-smoke}" + +# 프리셋별 기본값(환경변수가 있으면 그것을 우선) +case "$PRESET" in + smoke) + PUBLISHERS="${PUBLISHERS:-2}"; SUBSCRIBERS="${SUBSCRIBERS:-3}" + DURATION="${DURATION:-15s}"; RESOLUTION="${RESOLUTION:-low}"; LAYOUT="${LAYOUT:-3x3}" ;; + medium) + PUBLISHERS="${PUBLISHERS:-5}"; SUBSCRIBERS="${SUBSCRIBERS:-30}" + DURATION="${DURATION:-1m}"; RESOLUTION="${RESOLUTION:-medium}"; LAYOUT="${LAYOUT:-5x5}" ;; + full) + # 문서 목표: 최대 160명(진행자 소수 발행 + 다수 구독), 동시 표시 ~50명 + PUBLISHERS="${PUBLISHERS:-10}"; SUBSCRIBERS="${SUBSCRIBERS:-150}" + DURATION="${DURATION:-2m}"; RESOLUTION="${RESOLUTION:-high}"; LAYOUT="${LAYOUT:-speaker}" ;; + *) + echo "알 수 없는 preset: $PRESET (smoke|medium|full)" >&2; exit 1 ;; +esac + +ROOM="${ROOM:-loadtest-$PRESET}" +NUM_PER_SECOND="${NUM_PER_SECOND:-5}" +SIMULCAST="${SIMULCAST:-on}" +SIMULCAST_FLAG=() +[ "$SIMULCAST" = "off" ] && SIMULCAST_FLAG=(--no-simulcast) + +# .env 에서 필요한 값만 읽는다(CRLF 제거). 파일이 없으면 기존 환경변수 사용. +read_env() { + if [ -f "$ENV_FILE" ]; then + grep -E "^$1=" "$ENV_FILE" | tail -1 | cut -d= -f2- | tr -d '\r' + fi +} +export LIVEKIT_URL="${LIVEKIT_URL:-$(read_env LIVEKIT_URL)}" +export LIVEKIT_API_KEY="${LIVEKIT_API_KEY:-$(read_env LIVEKIT_API_KEY)}" +export LIVEKIT_API_SECRET="${LIVEKIT_API_SECRET:-$(read_env LIVEKIT_API_SECRET)}" + +if [ -z "${LIVEKIT_URL:-}" ] || [ -z "${LIVEKIT_API_KEY:-}" ] || [ -z "${LIVEKIT_API_SECRET:-}" ]; then + echo "LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET 를 찾을 수 없습니다 ($ENV_FILE)." >&2 + exit 1 +fi + +echo "▶ LiveKit load-test [$PRESET]" +echo " URL=$LIVEKIT_URL ROOM=$ROOM" +echo " publishers=$PUBLISHERS subscribers=$SUBSCRIBERS duration=$DURATION resolution=$RESOLUTION layout=$LAYOUT simulcast=$SIMULCAST" +echo + +# livekit-cli(lk)는 $LIVEKIT_URL/$LIVEKIT_API_KEY/$LIVEKIT_API_SECRET 를 자동으로 읽는다. +docker run --rm \ + -e LIVEKIT_URL -e LIVEKIT_API_KEY -e LIVEKIT_API_SECRET \ + livekit/livekit-cli load-test \ + --room "$ROOM" \ + --duration "$DURATION" \ + --publishers "$PUBLISHERS" \ + --subscribers "$SUBSCRIBERS" \ + --video-resolution "$RESOLUTION" \ + --layout "$LAYOUT" \ + --num-per-second "$NUM_PER_SECOND" \ + "${SIMULCAST_FLAG[@]}"