쉬운 설명 · Architecture · 2026-09-16

방은 매번 새로 생깁니다.
선생님은 계속 그 자리에 있습니다.

마지막 업데이트 2026-09-16

LiveKit에서 방(room)분신(job)은 수업마다 새로 태어납니다. 그런데 워커(worker)는 며칠씩 계속 살아 있습니다. 그 “안 바뀌는 쪽”이 시간이 갈수록 느려지는 것 — 이것이 워커 열화 현상입니다.

기준 코드: develop 0587894f관측 사례: 2026-09-15 17:00~18:00

한 줄 요약. 특정 agent 워커 프로세스 하나가 시간이 갈수록 느려져서, 그 워커에 배정된 수업만 AI 첫 응답이 8~26초 늦게 나오는 현상입니다. 방이 잘못된 것도, AI가 죽은 것도 아닙니다.

01먼저, 전체 그림

왼쪽은 아이들이 들어가는 방. 오른쪽은 방 밖에서 대기하는 선생님들입니다.

LiveKit 룸과 워커의 구조 왼쪽 LiveKit 서버 안에 방 두 개가 있고, 각 방에는 아동과 agent 참가자가 있습니다. 오른쪽 ECS 워커 대기실에는 워커 세 개가 있고, 워커가 방 안으로 job 프로세스를 파견합니다. 가운데 워커는 열화 상태로 표시되어 있습니다. LiveKit 서버 방을 만들고, 선생님을 부릅니다 방 ① ppi-{userId}-{avatarId}-… 아동 브라우저 참가자 핑퐁이 agent-<jobID> 방 ② 아동 브라우저 참가자 핑퐁이 대답이 늦다 워커 대기실 · ECS 방 밖에서 며칠씩 계속 살아 있음 워커 A 건강함 · 첫 응답 2~4초 워커 B 열화 · 첫 응답 8~26초 워커 C 건강함 · 첫 응답 2~4초 분신 파견 분신 파견
방 (수업마다 새로 생김) 건강한 워커 열화된 워커
가장 흔한 오해. “워커가 방 안에 있다”가 아닙니다. 워커는 방 밖에 상주하고, 방 안에 들어가는 건 워커가 만들어 보낸 분신 프로세스(job)입니다.

02등장인물은 넷

각자 얼마나 오래 사는지가 이 이야기의 핵심입니다.

웹 서버
방 이름표를 만들고, “핑퐁이 불러주세요” 쪽지를 토큰에 붙여 아동에게 건넵니다.
요청마다 새로
LiveKit 서버
아동이 토큰으로 들어오면 그 순간 방을 만들고, 쪽지를 보고 워커를 호출합니다.
항상 떠 있음
워커
ECS에 상주하는 프로세스. 방과 무관하게 계속 살아 있으며 방 배정을 기다립니다.
며칠씩 계속
분신 (job)
워커가 방 하나당 하나씩 띄우는 자식 프로세스. 이게 방에 들어가 말을 합니다.
수업마다 새로
워커 하나는 방을 최대 2개까지 맡습니다(PPI_AGENT_MAX_JOBS_PER_WORKER=2). 분신은 미리 2개를 예열해 둡니다(num_idle_processes=2) — 그래서 아동이 들어오면 바로 말을 걸 수 있습니다.

03수업 하나가 시작될 때

다섯 걸음이면 끝납니다.

1
웹 서버가 방 이름을 짓는다
아동이 활동에 들어가면 POST /api/livekit/callppi-{userId}-{avatarId}-{voiceSessionId} 형태로 이름만 만듭니다. 방을 만드는 게 아니라 이름표만 뽑는 단계입니다.
2
토큰에 “핑퐁이 불러주세요” 쪽지를 붙인다
같은 응답에서 참가자 토큰을 발급하면서 RoomAgentDispatch({ agentName: "ppi-agent" }) 를 토큰 안에 넣습니다. 이 쪽지가 나중에 워커를 부르는 유일한 근거입니다.
3
아동이 들어오는 순간 방이 태어난다
LiveKit 서버는 토큰을 받고 그때 방을 자동 생성합니다. 코드베이스에 createRoom 호출은 아예 없습니다 — 방의 생사는 전적으로 LiveKit 서버가 관리합니다.
4
워커 한 명에게 방이 배정된다
서버 로그의 assigned job to worker 가 이 순간입니다. 누구에게 갈지는 부하(load)로 정해집니다 — 건강한지는 보지 않습니다.
5
분신이 방에 들어가 첫 인사를 한다
워커가 자식 프로세스를 띄우고, 그 프로세스가 agent-<jobID> 라는 이름으로 방에 참가합니다. 정상이면 여기서 2~4초 안에 첫 목소리가 나옵니다.

04여기서 열화가 시작됩니다

매번 새것으로 바뀌는 쪽과, 바뀌지 않는 쪽을 나란히 보세요.

매번 새로 태어나는 것

방 · 분신(job) — 수업이 끝나면 사라집니다
수업 시작수업 끝 → 소멸

문제가 생겨도 다음 수업엔 깨끗한 상태로 다시 시작합니다. 누적될 수가 없습니다.

바뀌지 않고 계속 사는 것

워커 — 배포될 때까지 같은 프로세스
배포… 수십·수백 수업 …다음 배포

방을 계속 받아내며 무언가가 쌓입니다. 여기가 느려지면 그 워커에 걸린 수업이 전부 느려집니다.

열화 현상이란: 오래 살아 있는 워커 프로세스 하나가 점점 굼떠져서, 그 워커가 맡은 수업만 첫 응답이 늦어지는 것. 방도 정상, 아동 마이크도 정상, AI도 죽지 않았습니다 — 그냥 느립니다.
왜 저절로 안 낫나. LiveKit은 방을 배정할 때 워커의 부하만 봅니다. “이 워커가 느려졌는지”는 보지 않기 때문에, 열화된 워커도 계속 새 수업을 받습니다.

05어떻게 알아냈나 — 증거 둘

2026-09-15 17:00~18:00, 한 아동의 19개 세션을 워커별로 갈라 봤습니다.

15 / 19
정상 세션. 워커 9종에 흩어져 있고 첫 응답은 전부 5.8초 이하
4 / 19
지연 세션. 전부 같은 워커 하나(AW_eg5RnPhNXC2r)
1 / 3876
이게 우연히 한 워커에 몰릴 확률

증거 ① — 지연이 한 워커에만 몰렸다

정상 15건≤5.8s
eg5R #17.8s
eg5R #211.7s
eg5R #313.0s
eg5R #426.0s

세션 첫 응답 지연 — 게스트 로그의 LiveKit session descriptor createdSet latestAiAudioLogIdRef 구간

증거 ② — 그 워커만 시간이 갈수록 느려졌다

다른 워커
17:01~17:20
0.21s
다른 워커
17:32~
0.20s
eg5R
17:01~17:20
0.19~0.27s
eg5R
17:32~
0.31~0.55s

워커가 방 배정을 받고 실제로 방에 붙기까지 — assigned job to worker → 같은 jobID의 starting RTC session

이 두 번째 지표는 LLM·TTS와 무관한 순수 프로세스 응답성입니다. 중앙값 0.46초(n=17) 대 0.21초(n=273), Mann-Whitney z=5.35, p≈8.8e-8. 결정적인 건 절대값이 아니라 같은 시간대에 다른 워커는 그대로인데 이 워커만 악화됐다는 점 — 호스트 전체 부하가 아니라 그 프로세스 개별 문제라는 뜻입니다.

06헷갈리기 쉬운 것 셋

로그에 자주 뜨지만 열화와 무관한 것들입니다.

로그에서 보이는 것진짜 의미판정
not dispatching agent job since no worker is available
1시간에 586건
jobTypeJT_PUBLISHER/JT_PARTICIPANT 인 노이즈. ppi-agentJT_ROOM 으로만 등록되므로 해당 없음. 오탐
job ended … JS_FAILED "agent worker left the room"
1시간에 292건
수업이 정상 종료될 때 매번 남는 문구. 전 세션 공통 패턴. 오탐
job ended … error: "" (빈 문자열) 이건 진짜 사고 — 분신 프로세스가 SIGSEGV(exit -11)로 죽은 것. 열화(느림)가 아니라 소멸(말이 끊김)입니다. 다른 현상
증상으로 구분하기. 열화는 “AI가 늦게 말한다”, 크래시는 “AI가 사라진다”입니다. 클라이언트는 둘 다 No response from OpenAI within 5 seconds 경고를 띄울 수 있어 로그 문구만으로는 갈리지 않습니다.

07아직 확정이 아닙니다

한계. 증거 ②의 차이는 0.2초대입니다. 26초짜리 첫 응답 지연을 그 자체로 설명하지 못합니다. LLM·TTS 구간은 SFU 로그에 아예 기록되지 않기 때문에, 현재 “열화”는 정황 근거 2종에 기반한 추정입니다.

08코드에서 어디를 보면 되나

무엇위치
방 이름 생성apps/web/app/api/livekit/call/route.ts:71 createRoomName()
워커 호출 쪽지apps/web/lib/voice-agent/livekit-token.ts:113 token.roomConfig = RoomConfiguration({ agents: [RoomAgentDispatch] })
디스패치 이름apps/web/lib/voice-agent/livekit-token.ts:24 LIVEKIT_AGENT_NAME (기본 ppi-agent)
워커 1개당 방 수apps/livekit-agent/agent.py:344 PPI_AGENT_MAX_JOBS_PER_WORKER (기본 2)
부하 계산식apps/livekit-agent/agent.py:355 _ppi_agent_load() — CPU 대신 활성 job 수 기준
워커 등록apps/livekit-agent/agent.py:370 AgentServer(...), :4492 @server.rtc_session(agent_name=PPI_AGENT_NAME)
배포 대상ECS/Fargate — cluster·service ppi-livekit-prod-agent
첫 응답 측정 지점apps/web/entities/guest-session/model/use-ai-session.ts:291:4194

관련 문서