E2E 지연 측정 — 4.3절 예산이 실제로 지켜지는가

무엇을

발화 종료 → 카드 표시까지 구간별 지연을 운영 경로에서 잰다(4.3절).

잴 수 있게 된 지 얼마 안 됐다

2026-09-14 까지는 못 쟀다. TranscriptEvent 에 이벤트 도착 시각이 없어서 발동 시각을 「발화 종료 + 346ms」로 모형화하고 있었고, 그대로 채점하면 p50 = p95 = 346 · 적절 발동률 1.0 이라는 가짜 만점이 나왔다(절대 원칙 10).

09-14 에 콜 미디에이터가 received_at_ms 를 실어 보내고 트리거가 그것을 쓰도록 고쳤다 (w4-trigger-arrival-time) — 실시간 경로의 trigger_at_ms 는 이제 측정값이다. 이 티켓은 그 값을 실제로 모으는 일이다.

⚠ 평가 하네스는 여전히 측정 불가다 — 골든셋에 도착 시각이 없다. 이 티켓은 운영 경로만 잰다.

구간

발화 종료 → STT 최종 결과 → 콜 미디에이터 수신 → 트리거 판정 → 검색 → 생성 → 브라우저 표시

내부 처리 p95 ≤1,000ms(4.1절)는 그중 「트리거 판정 → 생성」 구간이다 — 전체 E2E 와 같은 숫자가 아니다. 두 수를 섞어 적지 않는다.

완료 조건


설계 — 2026-09-20 (_project/decisions/119)

코드를 전수 확인해 무엇이 있고 무엇이 없는지를 구간별로 갈랐다.

구간 지금 할 일
① 발화 종료 → STT 최종 도착 ✅ received_at_ms − utterance_end_ms 그대로 쓴다
② 트리거 판정 ✅ 실시간 경로는 측정값 그대로
③ 검색 ❌ ②③④가 한 덩어리 쪼갠다 — retrieval_ms
④ 생성 ❌ 위와 같다 쪼갠다 — generation_ms
②+③+④ ✅ internal_latency_ms(저장까지 됨) 뜻을 「서버 내부 처리」로 고정
⑤ 전송 → 방송 ❌ 콜 미디에이터가 e2e_latency_ms 를 채운다(방송 시각 − utterance_end_ms)
⑥ 브라우저 렌더 ❌ 재지 않는다 — 결정 기록에 이유를 적었다

e2e_latency_ms 는 컬럼·DTO 가 있는데 아무도 채우지 않아 늘 NULL 이었다(09-20 전수 grep). 서버가 모르는 값을 지어내지 않은 것이라 그 자체는 옳다 — 채울 주체를 정한 적이 없었을 뿐이다.

할 일 (순서)

⚠ 하네스는 계속 「측정 불가」다 — 골든셋에 시계가 없다. 거기에 시각을 지어 넣지 않는다(절대 원칙 2).

준비 — 2026-09-21 코드 확인 (정성윤)

e2e_check.py 는 지금 구간 값을 받을 수 없다. 그래서 「측정」 칸의 첫 일은 수집 경로다.

할 일 순서

  1. 재생기 --watch 가 받은 recommendation 메시지의 네 값(internal_latency_ms · retrieval_ms · generation_ms · e2e_latency_ms)을 JSONL 로 남긴다 — 류준 님 스크립트라 먼저 알린다
  2. e2e_check.py 가 그 파일을 읽어 구간별 p50·p95 와 표본 수를 보고서에 낸다
  3. 어디서 돌리나 — 운영 DB 는 이 머신에서 닿지 않는다(RDS 는 VPC 안). 운영으로 재려면 DB 판정을 끄고 방송 값만 모으는 모드가 필요하다. 로컬 전체 스택이면 지금 그대로 된다
  4. 모델이 운영 노드에 붙은 뒤 한 번 더 잰다 — 리랭커 지연이 여기서 처음 운영 값으로 나온다

2026-09-30 — 측정 준비 (정성윤)

2026-09-30 — 운영 실측 · 내부 처리 구간 (정성윤)

네 조건 — 측정일 2026-09-30 22:55 KST(DB now()) · 운영 server 0.1.43(최근 7일 슬라이스. 전체 기간은 0.1.2x~0.1.43 여러 판이 섞여 있다) · 명령 scratchpad/qa-latency-check.sh(파드 안 psycopg SELECT, recommendation.internal_latency_ms NULL 제외 — 정성윤이 ! 로 실행) · 표본 수는 표에. 구간은 「트리거 판정 → 검색 → 생성」(서버 내부)뿐이다. STT·미디에이터·브라우저 구간은 이 값에 없다(e2e_latency_ms 는 DB 에 0행 — 방송 직전에만 채우므로 예상대로). 카드 0장 추천은 0/115 라 표본에서 빠진 것이 없다.

범위 n p50 (ms) p95 (ms) max (ms) 예산 p95 ≤ 1,000
최근 7일 — 지금 운영 실구성(규칙+NER · KoE5 dense · 리랭커 없음) 26 538 655 1,157 ✅
전체 기간 · web-speech(개발 페이지 실통화) 60 580 904 1,157 ✅
전체 기간 · synthetic-script(대본 재생) 55 601 1,313 3,300 ❌ — 아래
전체 기간 합계 115 590 1,118 3,300 ❌

2026-10-01 — done (정성윤)

2026-10-01 — e2e 실측 (정성윤) · 완료 조건을 실측으로 채움

재생기 --watch --save-ws 로 운영에 합성 통화 6건(SYN-004·005·008·010·013·016, --speed 4)을 흘려 /ws 프레임 714줄을 모았다(data/processed/e2e-ws-2026-10-01.jsonl, gitignore). scripts/latency_from_ws.py 로 셈. 발동한 추천 47건.

구간 n p50 p95 max 예산
e2e (발화 종료 → 방송 직전, decisions/119) 47 1,629 3,503 4,493 — (STT 미경유라 ① 구간 0)
서버 내부 (트리거 → 검색 → 생성) 47 938 1,358 1,570 ≤ 1,000 — p95 초과
검색 retrieval_ms 47 938 1,358 1,570 내부 = 검색 전부(생성 0)

어디가 느린가 — 서버 내부가 곧 검색(KoE5 dense 임베딩 + ES)이고, e2e − 내부 ≈ 700 ms(p50) 는 콜 미디에이터 채널 줄(CoalescingQueue)에서 앞 발화의 추천을 기다린 시간이다 — --speed 4 로 발화를 4배 빨리 넣어 줄이 길어졌다. 같은 날 운영 DB 의 최근 7일 값(n 26 · p50 538 · p95 655)보다 높다 — 그쪽은 사람 속도의 QA 통화다. 이 값은 「4배속 합성 통화 6건」 조건에서의 값이고, 사람 속도 통화의 상한으로 쓴다. 2 vCPU 노드에서 임베딩이 연속으로 겹치면 1초를 넘는다 — 예산을 넘는 구간은 검색(임베딩) 이다. 네 조건: 2026-10-01 11:2x KST · 운영 server 0.1.44·call-mediator 0.2.12 · 명령 위 그대로 · n 47(통화 6). 인용은 두 표(DB 7일 · 4배속 재생)를 조건과 함께 나란히 — 섞지 않는다.

2026-10-01 밤 — 최종 점검에서 정정 (정성윤) · 앞 절의 e2e 값이 재현되지 않았다

e2e 값 정정. 같은 파일(data/processed/e2e-ws-2026-10-01.jsonl)을 같은 스크립트로 다시 세니 763줄 · 발동 51건이다. 앞 절의 「714줄 · n 47」 은 마지막 재생이 끝나기 전에 읽은 값이다(운영 DB 도 재생 6통화 추천 51건 — 9·12·8·2·13·7). 고친 값:

구간 n p50 p95 max
e2e 51 1,580 3,576 4,493
서버 내부 = 검색 51 938 1,398 1,570

DB 최근 7일을 다시 읽었다(10-01 밤, 운영 RDS, n 117). 09-30 의 「n 26 · p95 655」 는 그 시각의 사실이지만 오늘 통화가 쌓여 지금은 다르다.

엔진 n p50 p95 max 1초 초과
google-stt(음성 2채널) 12 420 721 723 0
web-speech(브라우저 마이크) 13 548 865 1,157 1
synthetic-script(글자 채널 대본) 92 828 2,404 4,719 29
전체 117 723 1,733 4,719 30