Bug Analysis · Audio Misrecognition Agent

10/12 20시 그룹 수업 중 오인식 에이전트 분석 결과 누락

마지막 업데이트 2026-10-13

강한 후보: 20시 그룹 수업 피크에 audio-analysis admission queue가 포화되어 /api/audio-analysis/analyze가 429를 다량 반환했고, 웹 클라이언트는 재시도 없이 실패 결과를 만들지만 실패 로그를 영속화하지 않는다. 동시에 성공한 no-response/user-related 분석 로그는 세션 로그 UI에서 숨겨질 수 있어 “중간 분석 결과가 빠진 것”처럼 보이는 복합 경로가 성립한다.

대상: 2026-10-12 20:00 KST 그룹 수업 코드 기준: ppi develop 4d909f5c 데이터: Grafana Loki ppi-web prod 실기기 E2E: 미실행

분석 요청 총량

1,802

19:45~20:21 KST AUDIO_ANALYSIS_PROXY 완료 로그

실패 응답

450

429=432, 504=15, 502=3 · 약 25%

피크 패턴

20:02~20:20

매분 429가 반복 발생. 단발 장애보다 queue 포화 패턴.

1. 관찰 단서

시간대(KST)관찰해석
19:45~20:21proxy_request_completed 1,802건 중 429 432건, 504 15건, 502 3건오인식 분석 요청 4건 중 1건꼴로 성공 결과를 받지 못한 구간
20:02200=45, 429=35, 504=1수업 시작 직후 그룹 카드들이 동시에 분석 요청을 밀어 넣은 피크 후보
20:09200=39, 429=35, 502=1, 504=2수업 중반에도 포화가 지속되어 “대화 중간 누락” 체감과 부합
20:16~20:17429=32, 33일시적 복구가 아니라 분석 서비스 처리량 대비 요청량 초과 상태

2. 재현 경로 후보

① 그룹 수업 피크
여러 아동 카드에서 아동/AI 턴마다 분석 요청 생성
② 서버 admission 포화
maxConcurrency=8, maxQueueSize=16, queueWaitMs=3000
③ 429 반환
ANALYSIS_SERVER_BUSY + Retry-After
④ 클라이언트 실패 처리
재시도 없이 errResult.error 생성, 성공 로그 저장 경로 미진입
⑤ 진행자 체감
buildAudioCorrectionAlert가 error 결과를 무시 → 알림/분석 결과 누락

3. 코드 검증 결과

파일/라인의미영향
apps/audio-analysis/src/config.ts:13-25분석 서비스 admission 기본값: 동시 8, 대기 16, 대기 3초그룹 수업 피크에서 24개 초과 요청이 몰리면 429 가능
apps/audio-analysis/src/admission-controller.ts:37-55active가 꽉 차면 queue에 넣고, queue가 꽉 차거나 3초 초과 시 rejectqueue_full/queue_timeout 재현 조건
apps/audio-analysis/src/server.ts:200-209AdmissionRejectedError를 429 ANALYSIS_SERVER_BUSY로 변환운영 Loki 429와 코드 경로 일치
apps/web/hooks/use-turn-analysis.ts:300-357분석 API 호출 후 !res.ok면 throw. 429 재시도 없음일시 포화에도 해당 턴은 성공 결과로 회복되지 않음
apps/web/hooks/use-turn-analysis.ts:408-431catch에서 errResult를 만들고 onResult만 호출실패 분석은 onLogData로 저장되지 않음
apps/web/lib/audio-correction-alert.ts:16-24result.error가 있으면 alert null실패 결과는 진행자 오인식 알림으로도 노출되지 않음
apps/web/hooks/use-audio-analysis-logger.ts:117-147성공 분석만 type: audio_analysis 세션 로그로 저장실패 원인이 사후 세션 로그에 남지 않는 관측 공백
apps/web/lib/audio-analysis/session-log-audio-analysis.ts:9-15분석 로그를 assistant audio 대상에만 병합user/no-response 관련 성공 분석은 UI에서 빠질 수 있음
apps/web/components/pages/session-log.tsx:121-128relatedToLogId가 있는 audio_analysis 독립 행을 숨김DB에는 있어도 세션 로그 화면에서는 누락처럼 보임

4. 반증 루프

가설 A — 429 포화로 실패 결과가 누락됐다

가설 B — 저장됐지만 UI에서 숨겨졌다

5. 최소 재현 레시피

  1. prod와 같은 admission 설정(maxConcurrency=8, maxQueueSize=16, queueWaitMs=3000)으로 apps/audio-analysis를 띄운다.
  2. 그룹 수업처럼 20개 이상 아동 카드가 짧은 시간에 /api/audio-analysis/analyze를 호출하도록 만든다. provider 호출은 8~20초 정도 지연되게 둔다.
  3. 25번째 이후 요청 일부가 429 ANALYSIS_SERVER_BUSY가 되는지 확인한다.
  4. 웹 클라이언트에서 해당 턴의 use-turn-analysis catch 경로가 실행되고, onLogData가 호출되지 않는지 확인한다.
  5. 진행자 알림은 buildAudioCorrectionAlert에서 result.error로 null 처리되는지 확인한다.
  6. 별도 반례로 no-response 분석 성공 로그를 relatedToLogId=userLogId로 저장하고 session-log 화면에서 표시되지 않는지 확인한다.

6. 해결 제안

  1. P0 관측 공백 제거: 429/502/504 실패도 audio_analysis 로그로 저장하되 result.error, status, retryAfter, trigger, relatedToLogId를 포함한다.
  2. P0 재시도: 429 + Retry-After 수신 시 1~2회 bounded retry를 적용한다. 단, 세션 종료/claim 상실 시 stale 방지는 유지한다.
  3. P1 UI 수정: session-log에서 user/no-response 관련 audio_analysis도 user row에 병합하거나 독립 행으로 노출한다.
  4. P1 서버 용량/큐 조정: 20시 피크 기준으로 admission maxConcurrency/queue 또는 audio-analysis replica를 늘리고, queue depth/active/reject reason을 Loki에 직접 남긴다.
  5. P2 디버그 패널: 진행자 카드에 “분석 실패/재시도 중/서버 busy” 상태를 노출해 “누락”과 “분석 실패”를 구분한다.

7. 검증 상태와 남은 확인