본문으로 건너뛰기Hwanseok Sim

작성일

GT PBX 개발기 4 — EC2에서 동시 통화 100건까지 걸어 보기

로컬 SIPp로 인터넷 너머 EC2 t3.medium PBX에 동시 통화를 10건에서 100건까지 계단으로 걸어 본 기록. 시험 도구와 판정 기준, 도중에 바꾼 기준, NAT 때문에 끊기지 않던 통화, 녹취가 한꺼번에 멈춘 이상 현상, ringall 분배의 비용을 정리했다(2026년 10월 28일).

GT PBX 개발기 시리즈 4/8편. 전체 목록은 gtpbx 태그에서 볼 수 있다.

PLAN §3은 상담원 20석 기준으로 피크 동시 통화를 2530건으로 잡았다. 기능 명세에는 "동시 통화 30에서 CPU 30% 미만"이 목표로 있다. 13편까지의 시험은 모두 맥의 OrbStack VM 안에서 장비 몇 대로 돌린 것이라, 인터넷과 NAT를 지나는 실제 경로에서 이 숫자가 나오는지는 확인하지 못한 상태였다. 10월 28일, 같은 설치 스크립트로 AWS EC2에 올려 둔 스테이징 PBX에 부하 시험을 했다.

같은 소스, 접속 프로필만 다르게

나중에 사내 서버도 같은 소스로 빌드한다. 그래서 EC2 전용 차이는 서버 설정 파일(/etc/gtpbx/gtpbx.env) 값, AWS 보안 그룹, 시험 계정에만 두고 asterisk/·deploy/·db/·apps/에는 EC2 분기를 넣지 않기로 했다. 설치도 1·2편에서 만든 install.sh → deploy-config.sh → deploy-app.sh → apply-firewall.sh를 그대로 쓴다. 여기서 막히는 것은 사내 서버에서도 막히니 저장소를 고친다.

구성내용
대상 PBXEC2 t3.medium (2 vCPU, 3.8GB, x86_64), Ubuntu 24.04
부하 발생기맥 + OrbStack VM gtpbx-sim의 SIPp. 장비와 상담원을 둘 다 여기서 흉내 낸다
경로OrbStack NAT → 사무실 NAT → 인터넷 → EC2 1:1 NAT → Asterisk
제어맥의 tests/load/server.mjs (Node 24, 의존성 없음). 페이지에서 설정·시작·중지, 2초마다 지표 수집, SSE로 그래프

시험 도구 tests/load/는 대상을 접속 프로필로만 구분한다. 프로필은 SIP 주소와 "PBX에서 root로 명령을 실행하는 접두어"(orb ... 또는 ssh ... sudo)뿐이다. 같은 시험을 OrbStack, EC2, 나중의 사내 서버에 같은 기준으로 돌려 비교할 수 있다. 시험 계정은 실행마다 무작위 비밀번호로 만들어 제어 서버 메모리에만 두고, 끝나면 지운다.

장비 시나리오는 1편의 device_call.xml을 바탕으로 했는데, 짧은 음성 파일을 7초마다 반복 재생하니 RTP 순번이 되돌아가 PBX가 손실을 98%로 셌다. 통화 전체 길이의 연속 pcap을 한 번 재생하는 device_load.xml로 바꿨다.

장비와 상담원을 모두 로컬에서 흉내 내므로 통화 1건에 RTP 흐름이 넷이고, 로컬 회선에 두 레그가 다 실린다. 통화당 업로드·다운로드 각각 약 175kbps다. 시험 전에 udp_probe.py로 EC2까지 RTP 크기 패킷을 흘려 N=150(약 24Mbps)까지 손실 0.16% 이하인 것을 확인했다. RTP 손실이 나오면 회선부터 다시 재서 PBX 탓인지 가르기로 했다.

판정 기준

지표기준 (N ≤ 30)
호출 성공실패 0 (거절 코드, BYE 대기 초과, 응답 없음)
PBX CPUp95 30% 미만, steal 제외
CPU steal평균 5% 미만
RTP손실 1% 미만, 지터 30ms 미만
녹취·DB연결 통화 수 = recording_file 행 수 = 실제 파일 수, queue_log 건수 일치
로그시험 구간 Asterisk ERROR·WARNING 신규 0

N이 30을 넘는 단계는 합격·불합격이 아니라 한계 측정으로 정했다. 실패율 1% 초과, CPU p95 80% 초과, RTP 손실 3% 초과 중 하나가 나오면 그 직전 N을 한계로 적고 멈춘다.

이 표는 처음 모습이 아니다. 처음에는 CPU 최대값과 steal p95로 판정했는데, N=20에서 steal p95 6.1%로 시험이 멈췄다. 표본을 보니 steal 5% 이상인 표본이 매분 03초에만 12개씩 튀었고 평균은 1.0%로 그대로였다. PBX 없이 EC2에 더미 부하만 걸어도 같은 순간에 튀었고, 게스트 쪽에 분 단위로 도는 작업은 없었다. 호스트 쪽 간섭으로 보고 steal은 평균으로, CPU는 p95로 판정하도록 바꿨다(최대값은 참고로 남긴다).

도구 쪽 문제도 몇 개 나왔다. asterisk 프로세스 CPU가 한 표본에서 4412.4%로 찍혔다. pidof -s asterisk가 잠깐 뜬 asterisk -rx 클라이언트를 고르는 바람에 값이 빠졌고, 빠진 값을 0으로 넣어 다음 차이가 누적값이 된 것이었다. pid를 pid 파일에서 읽고, 이상한 표본은 버리되 사유별 개수를 결과에 남기게 고쳤다. 실행 도중 맥 덮개가 닫혀 절전에 들어가 표본 사이가 9363.9초 비는 일도 있었다. 그 실행은 무효로 하고, 이후에는 절전을 막고 "표본 간격 30초 이하"를 판정 항목에 넣었다.

첫 연결에서 나온 NAT 문제

계단 시험(S2) 전 단계(S1)에서 동시 1~5통화로 경로를 점검했고, 여기서 운영 설정을 두 번 바꿨다.

첫 번째는 상담원 등록 거절이었다. 상담원 엔드포인트는 등록 주소를 상담원망 목록으로 제한하고 있었는데, 사무실 공유기 뒤에서 등록하면 Contact가 사설 IP라 전부 거절됐다. 같은 거절이 82번 쌓이자 fail2ban이 시험하던 사무실 IP를 막아 SSH까지 끊겼다. 다른 네트워크에서 일하는 상담원도 있으니 상담원은 네트워크 제한을 없애고 NAT 옵션을 켰다(마이그레이션 0010).

두 번째는 끊기지 않는 통화였다. 동시 5통화·30초에서 PBX 쪽은 정상인데 장비 5대가 모두 PBX의 BYE를 받지 못했다. 발생기 경로의 UDP NAT 매핑은 조용하면 20~30초 만에 끊긴다. PBX의 qualify(OPTIONS)가 60초 간격이라 그 사이 매핑이 사라지고, PBX가 보내는 BYE가 버려졌다. qualify를 25초로 줄였다(0011). 시험 계정에만 넣었던 우회 값은 걷어 내고 운영 값 그대로 5통화·150초 15/15를 통과했다.

계단 시험 S2: N=10부터 100까지

10월 28일 오후부터 저녁까지, 상담원 수 A를 동시 통화 N과 같게 두고 1건/초로 늘리며 5분씩 돌렸다. 대상 소스는 X5(3편)까지 들어간 상태였다.

N성공CPU p95 / 평균통화당 CPU (전체 / asterisk)steal 평균RTP 손실 · 지터 최대큐 대기 p50 / p95 / 최대 (초)
1050/5013.5 / 9.5%0.88 / 0.63%1.0%0.05% · 13ms1 / 3.5 / 7
2099/9919.2 / 14.7%0.68 / 0.52%1.0%0.03% · 15ms1 / 7 / 15
30144/14426.1 / 19.8%0.60 / 0.54%1.2%0.11% · 18ms1 / 6 / 8
50157/15737.2 / 27.1%0.53 / 0.50%1.6%0.01% · 29ms2 / 8 / 10
75187/18755.8 / 41.5%0.56 / 0.51%1.9%0% · 22ms2 / 12 / 14
100200/20070.7 / 51.6%0.54 / 0.51%2.2%0.09% · 36ms4 / 21 / 32

N=100까지 호출 실패는 0이었고, 녹취와 queue_log는 전부 일치했으며, Asterisk ERROR·WARNING과 ODBC 오류도 0이었다. 운영 기준인 동시 30에서 CPU p95는 26.1%로 기준을 넘지 않았지만 여유가 작다. 직선으로 맞추면 p95 30%는 N≈36에서 닿는다. N=100에서는 지터가 36ms로 품질 기준(30ms)을 먼저 넘었다. 이 인스턴스의 실용 상한은 N≈75~100으로 적었다. 사내 서버와 비교할 숫자로는 통화당 CPU 평균 약 0.54%(t3.medium 2 vCPU 전체 대비)를 남겼다. t3의 vCPU는 하이퍼스레드 하나라 사내 서버 코어와 성능이 달라서, 같은 측정을 사내 서버에서 다시 하기로 했다. N=100에서 멈추기로 하고 N=150은 돌리지 않았다.

표의 N=75는 재시도 결과다. 첫 시도는 183건 중 18건이 BYE 대기 초과로 실패해 중단 기준에 걸렸다. PBX 쪽은 queue_log·녹취가 183건 모두 맞고 오류도 없었으니 PBX 한계는 아니었다. 원인은 시험 장비였다. 장비 AOR은 등록을 최대 120초만 유지하는데, SIPp 장비는 호출을 시작할 때 한 번만 등록하고 통화 중에는 다시 등록하지 않았다. 통화가 120초를 넘기면 BYE 전에 contact가 만료되고, qualify도 같이 멈추면서 NAT 매핑이 끊긴다. 실패 18건이 모두 BYE 전에 contact가 만료된 통화였다. 실제 장비처럼 통화 중에도 90초마다 재등록하도록 시나리오를 고친 뒤 N=75는 187/187, N=100은 200/200이었다.

이 일로 실제 현장 장비의 재등록 주기를 확인할 항목이 하나 생겼다. 120초 안에 재등록하지 않는 장비라면 긴 통화 끝에 "통화가 안 끊기는" 일이 생길 수 있다.

S3 실제 피크 모양과 녹취 잘림

S3는 상담원 20명에 동시 호출 30건, 늘 10건이 대기하는 모양으로 10분을 돌렸다. 201/201 성공, 포기 0이었다. 대기 호출은 거의 들어온 순서대로 연결됐다(순서가 어긋난 호출 6건, 최대 한 자리). 대기 5초 이상인 185건 중 대기 음악이 녹취에 들어간 것은 0건으로, 1편의 "연결된 구간만 녹음"이 부하에서도 지켜졌다.

그런데 녹취 15건이 연결 시간보다 2초 넘게 짧았다. 연결 시각은 서로 달랐는데 녹취 끝은 모두 0.1초 안의 같은 순간이었다. 그 순간 진행 중이던 통화 전부였고, 통화 자체와 BYE, queue_log는 정상이었다. 같은 표본에서 지터가 2ms에서 12ms로 뛰었다. 도구의 녹취 판정은 "3초 이상"만 봐서 통과로 나왔고, 통화별로 녹취 길이와 연결 시간을 맞대 보고서야 보였다.

하나씩 지웠다. SIP 쪽 NAT 재매핑은 보안 로그상 장비 인증 출발지가 같은 주소·같은 포트라 아니었다. PBX reload나 watchdog, 녹취 마무리 스크립트에도 흔적이 없었다. 다른 작업 세션이 EC2를 건드린 것은 4분 40초 뒤였다. 남은 후보는 둘이다. RTP 흐름만 NAT 매핑이 바뀌어 strictrtp가 새 주소의 RTP를 버렸거나, MixMonitor 쪽 문제다. 첫 후보가 맞다면 통화 중 공인 주소가 바뀌는 현장 장비(LTE 등)에서 음성과 녹취가 끊기는 운영 위험이 된다.

계측을 붙여 다시 돌렸다. 채널 통계를 2초마다 원본으로 남기고, 15초마다 1초씩 RTP 헤더를 캡처했다. 결과는 199/199, 녹취와 연결 시간 차이 −0.58~+0.88초로 재현되지 않았다. 채널 398개 중 수신이 멈춘 것은 0이었다. 원인은 확정하지 못했다(F7). 다음 시험에서도 같은 계측과 통화별 녹취 비교를 켜 두기로 했다.

ringall의 비용

큐 대기 p95는 N=10에서 3.5초, N=100에서 21초로 늘었다. field 큐는 ringall이라 호출 하나가 빈 상담원 전원을 울리는 동안 다음 호출은 배정되지 않고 retry(5초) 뒤에 다시 시도한다.

신호량도 상담원 수에 비례한다. OrbStack에서 같은 공인 IP 뒤 상담원 10명일 때 호출 1건에 약 37패킷이 그 IP에서 PBX로 갔다. 1편의 방화벽은 상담원망 밖 출발지 IP마다 SIP를 초당 20패킷, 버스트 60으로 자른다. 한 사무실에 상담원 10명이면 초당 0.5건만 몰려도 버스트를 다 쓰고 버리기 시작한다. 지금 부하 시험은 발생기 IP가 상담원망 목록 안이라 이 제한이 걸리지 않았다. 사무실 공인 IP가 고정이 아니어서 목록에 넣는 방법은 쓸 수 없고, 제한값을 올리는 쪽으로 정했다. 값은 같은 IP 뒤 상담원 10·20·30명 단계로 재는 시험(R1) 뒤에 정한다.

한 번에 한 명씩 울리는 분배(rrmemory, leastrecent 등)로 바꾸면 호출당 신호가 상담원 수와 상관없이 약 4패킷으로 일정해지고 몰림 배정 지연도 줄어든다. 대신 첫 상담원이 받지 않으면 울림 시간 15초만큼 다음 사람으로 넘어가는 지연이 생긴다. ringall은 "모두 동시에 울리고 먼저 받는 사람이 받는다"는 상담 업무 방식이라 확정 사양이다. 그래서 몰림 시험(S4)에서 field 큐만 잠시 바꿔 비교 측정만 하고 되돌리는 계획을 세웠다. 바꿀지는 업무 결정이다.

남은 시험

단계내용상태
S4 몰림N=A=30, 10건/초. ringall retry 5초·2초, 1명씩 분배 비교미실행
S5 장시간N=A=30, 1시간. 메모리·디스크·재등록미실행
S6·R1 속도 제한발생기 IP를 상담원망에서 빼고 nftables 제한이 어떻게 자르는지미실행
분석N=75 첫 시도의 BYE 대기 초과, S3 녹취 잘림(F7)원인 미확정 포함
사내 서버같은 소스로 빌드하고 같은 시험미실행

이 시험의 장비와 상담원은 모두 SIPp였다. 실제 IP 전화기, 실제 현장 장비, 사내 서버에서는 아직 같은 숫자를 확인하지 않았다.

다음 글

GT PBX 개발기 5 — AMI 이벤트로 실시간 현황 만들기