socket 서버 CPU 100%·디스크 폭주 위험 감사 및 dev 부하 테스트 실행 계획 예방 감사 apps/socket

마지막 업데이트 2026-07-22

2026-07-07 ppi-socket-prod CPU 반복 100% 스파이크 후속: PPI-1106 디스크 폭주 계획 브랜치 PPI-1106-loadtest

한 줄 요약

PPI-1106(녹음 post-mix 디스크 폭주)에 이어, socket 서버가 CPU 100%·디스크 풀·OOM으로 무너질 수 있는 로직을 코드 감사로 사전 식별했다. 가장 취약한 곳은 stem ffmpeg 동시 실행 전역 상한 부재(C1), broadcastRoomList의 O(룸×peer) 재계산 + 네임스페이스 전체 emit 증폭(C2), hot-path logger.info 폭주(C3), 그리고 Redis peer/room 키 TTL 부재(D2)·인메모리 Router peerCount 드리프트(D3)다. 실측 prod CPU 그래프의 주기적 100% 스파이크는 C2/C3 패턴과 정합적이며, 이를 dev(t4g.small 2vCPU) 부하 테스트로 실증하고 붕괴 지점을 정량 측정한다.

배경 — prod CPU 반복 100% 스파이크

ppi-socket-prod의 CPU 사용률이 16:00~21:30 구간에서 유휴(20%대)와 100% 스파이크를 반복. 짧고 날카로운 스파이크가 특정 이벤트(수업 시작 러시·모니터 대시보드 다수 접속)와 겹치는 패턴으로, 지속 부하보다 증폭성 이벤트 폭주를 시사한다.

↑ 실측 그래프(ppi-socket-prod)의 형태를 도식화. 유휴 대비 급격한 스파이크가 반복.

CPU 폭주 후보

가드없음 / 부분 / 있음

ID위치 · 로직가드검증
C1 stem ffmpeg 동시 실행 무제한
recordingManager.ts:966 — 녹음 입력별 ffmpeg를 룸당 2개(child/ai) spawn. post-mix는 세마포어(2)로 보호되나 stem은 전역 상한 없음 → ffmpeg 수 = 2 × 활성 녹음 룸으로 선형 증가
없음 S3
C2 broadcastRoomList 증폭
broadcast-utils.ts:48,166,177 — 다수 이벤트 핸들러가 호출. 매번 O(룸×peer) 재계산 + io.emit으로 네임스페이스 전체에 전송. 1 이벤트 → 전체 대시보드 emit
부분 S2 S4
C3 hot-path 로깅
session-handlers.ts:208·219·227·238·246, monitoring-handlers.ts:347·413 — speaking/transcript/transport-state 매 이벤트 logger.info + 객체 직렬화. 고빈도·상한 없음
없음 S2 S4
C4 워커 수 = CPU 코어 전부
sfu-config.ts:34workerCount = os.cpus().length(dev=2). 다수 룸 몰리면 기저 CPU 높음. MEDIASOUP_WORKER_COUNT로만 조정
설계 S1
C5 메트릭 수집 15s 타이머
metrics.ts:243collectGauges가 전체 룸 순회 + 전체 워커 getResourceUsage() await. 룸 많으면 15s마다 부하
부분 S1

C2 시각화 — 1 이벤트가 전체 emit으로 증폭

중빈도 이벤트 1건설정 토글·모니터링
getRoomList 재계산O(룸 × peer)
io.emit룸 한정 아님
대시보드 1
대시보드 2
… N

핫패스(speaking/talking)는 의도적으로 broadcast를 호출하지 않지만, auto-response·VAD 설정·모니터링 토글 같은 중빈도 이벤트가 몰리면 재계산 비용 × 전체 클라이언트 수로 CPU가 급증한다.

C1 시각화 — stem ffmpeg 선형 증가 (전역 상한 없음)

녹음 룸 5개
10 ffmpeg
녹음 룸 15개
30 ffmpeg
녹음 룸 30개
60 ffmpeg

룸당 child+ai 2개 프로세스가 무제한 누적. dev 2vCPU에서는 소수 룸으로도 CPU가 포화될 수 있다. (post-mix는 세마포어로 2개 제한되지만 stem은 아님)

디스크 · 메모리 폭주 후보

ID위치 · 로직가드/구멍검증
D1 stem .ogg 세션 중 무제한 생성
recordingManager.ts:1048,1089 — producer 교체마다 새 세그먼트 생성 + stemSegmentPaths에 push. 세션 종료 시에만 일괄 삭제
세션 중 정리 없음 S3
D2 Redis peer/room 키 TTL 부재
peer-store.ts:114-133, room-store.ts:154-182 — 명시적 disconnect + reaper(leader+heartbeat)에만 의존
안전망 TTL 없음 S5
D3 인메모리 Router peerCount 드리프트
routerManager.ts:234-258 — peerCount≤0에만 close. decrement 누락 경로(replaced skip 등) 시 Router+worker 영구 잔존, reaper 없음
인메모리 backstop 없음 S5
D4 captionTurns 무제한 누적
recordingManager.ts:842 — STT 턴마다 전사 텍스트 push. 세션 종료 시에만 GC
종료 시 해제 S3
D5 cleanStaleTmpFiles 정리 구멍
recordingManager.ts:275readdir 실패 시 catch{return}로 고아 파일 정리 통째 skip. 좀비 종료는 fire-and-forget(재시도 없음)
구멍 로그 관찰
D6 startRecording 실패 시 stem 미정리
recordingManager.ts:682-694 — 시작 실패 catch에서 sdp만 unlink, 생성된 stem .ogg는 누락. 2h stale backstop에만 의존
2h backstop S3
PPI-1106과의 관계: 디스크 폭주의 단일 파일 무한 인코딩 경로는 이미 4중 방어(-t 6h·15분 타임아웃·손상 stem 드롭)로 막혔다. 이번 감사가 겨냥하는 건 그와 다른 축 — 동시성(C1)·증폭(C2)·누적(D1~D4)으로 리소스가 서서히/급격히 고갈되는 경로다.

가드가 이미 충분한 영역 (양호)

부하 테스트 시나리오 → 위험 매핑

시나리오부하겨냥 위험
S1 연결/JOIN 스케일게스트 연결 50→100→200→400 (시그널링만)C4 C5
S2 broadcast+로깅 [핵심]모니터 50~100 + 설정 이벤트 초당 10→50→100. 로깅 ON/OFF A·B 비교C2 C3
S3 녹음+재접속 폭주실 브라우저 2~5 세션, 새로고침·네트워크 토글로 producer 교체 반복C1 D1 D4 D6
S4 복합(실전 근사)S1+S2+S3 동시 인가, prod 스파이크 패턴 재현 대조C1 C2 C3
S5 비정상 종료disconnect ack 없이 강제 종료, reaper 회수 관측D2 D3
실행 방침(확정): 미디어/녹음 부하는 실 브라우저 소수 + 재접속 반복으로 만든다(mediasoup producer는 순수 소켓 클라이언트로 못 만듦). 부하 강도는 CPU 100%·디스크 풀·OOM이 실제 발생하는 붕괴 지점까지 밀어 한계 수치를 정량 기록한다. 서버가 죽으면 마지막 스크레이프(append CSV)로 붕괴 직전 상태를 확정하고 재배포로 잇는다.

관측 지표 (Prometheus /metrics + REST)

15초 주기 gauge, 모든 메트릭에 instance_id 라벨. 부하 인가 전 30초 유휴 기준선 포함해 10초 간격 스크레이프.

지표용도
ppi_socket_ffmpeg_processesC1 — 녹음 ffmpeg 개수 vs CPU 상관
ppi_socket_broadcast_room_list_total · _duration_secondsC2 — broadcast 호출 빈도·재계산 소요(계측 신규 추가)
ppi_socket_connected_clients · ppi_socket_rooms_activeS1 — 연결/룸 스케일
ppi_mediasoup_{routers,transports,producers,consumers}D3 — 부하 해제 후에도 안 떨어지면 누수
ppi_mediasoup_worker_cpu_user · /workers/statsC4 — 워커별 CPU
process_resident_memory_bytes · event loop lagD4 — RSS 증가, 이벤트 루프 지연
/rooms · /debug/redis-stateD2 — 메모리 vs Redis 대조, 키 잔존 확인

개선 백로그 (부하 결과로 확정된 것만 착수)

실행 환경 · 관련 문서

범위 주의: 모든 부하는 *-socket-dev.dubuhealth.in 한정. prod URL(ppisocketprod)에는 절대 부하를 걸지 않는다.