Skip to content

SavePoseDataBatch가 rep마다 TTS 응답을 막는 동기 호출 — slo-baseline §4-1 분류와 불일치 #676

Description

@Khyojae

무엇을 발견했나

  • docs/decisions/slo-baseline.md §4-1은 gRPC SavePoseDataBatch를 "이 경로는 사용자 대면이 아니다"로 분류하고, 근거로 latency-perception.md §3이 "휴식 중 처리 — 운동 흐름 무관"으로 분류했다고 인용한다.
  • 하지만 docs/decisions/latency-perception.md §3.2("세트 경계 / 휴식 중")가 실제로 "휴식 중"으로 분류한 RPC는 ReportFeedbackBatch(BT-SET)다. SavePoseDataBatch는 별개 RPC이고, §3.2에는 등장하지 않는다 — §4-1의 인용이 두 RPC를 혼동했다.
  • ai-server/app/observability/metrics.py:46 주석: "SavePoseDataBatch 는 rep 마다 오고 CompleteAnalysis 는 세션당 한 번" — 스스로 "rep마다"라고 적어놨다.
  • ai-server/app/api/endpoints/pose.py:359에서 spring_client.report_pose_data_batch(...)(= gRPC SavePoseDataBatch)가 동기 호출로 실행되고, 그 결과를 받은 뒤에야 pose.py:419return PoseResponse(...)가 나간다 — 이 PoseResponse가 단말의 TTS 발화를 트리거하는 바로 그 응답이다.
  • docs/decisions/latency-perception.md §10.2("rep 완료 → 발화", ⭐로 표시된 "핵심 사용자 체감" 시나리오, 100ms 즉시감 목표)의 단계별 타임라인 표에는 이 gRPC 구간이 아예 없다 — AI 내부 처리(~30ms) 후 바로 PoseResponse를 보내는 것으로 그려져 있다.

왜 문제인가

코드상 SavePoseDataBatch는 "휴식 중 배치"가 아니라 "rep 완료 → 발화" 핵심 경로에 낀 동기 블로킹 호출이다. 우리가 가진 유일한 이 RPC의 실측(docs/decisions/slo-baseline.md §4-0, c=100 동시 부하)은 p50 429ms / p99 488~1,047ms다. 이 지연 규모가 무부하 단일요청에서도 비슷하다면, 매 rep마다 "100ms 즉시감" 목표를 몇 배 초과하게 된다 — 이 프로젝트의 핵심 UX 설계 전제(§10.2)를 어기는 셈이다.

미검증 — 확인 안 된 것

  • 무부하 단일요청 지연이 얼마인지 측정된 적이 없다. §4-0의 429~1,047ms는 c=100 동시성 조건의 값이라 그대로 "사용자 1명이 rep 끝낼 때 걸리는 시간"으로 쓸 수 없다. (이걸로 정책 임계값을 만들면 §4-1이 이미 지운 "실측값에 임의 배수" 함정과 같은 순환논리가 된다.)
  • 실사용자 트래픽에서 이 블로킹이 실제로 체감 TTS 지연을 유발하는지 로그·트레이스로 확인된 적 없다.

재현 경로 (코드 근거)

  1. ai-server/app/api/endpoints/pose.py:359spring_client.report_pose_data_batch(...) 호출 (블로킹)
  2. ai-server/app/api/endpoints/pose.py:419 — 그 호출이 끝난 뒤에야 return PoseResponse(...)
  3. 대조 ①: docs/decisions/latency-perception.md §10.2 타임라인 표 — 이 RPC 구간이 없다
  4. 대조 ②: docs/decisions/slo-baseline.md §4-1 — "사용자 대면 아님" 근거로 §3 인용(오귀속)
  5. ai-server/app/observability/metrics.py:46 — "SavePoseDataBatch는 rep마다 온다" 주석

제안

  1. 무부하 단일요청 latency부터 실측한다 (위 "미검증" 항목).
  2. 그 값에 따라 갈린다:
    • 실제로 짧다(예: 수십 ms) → 문서만 정정한다. slo-baseline.md §4-1 재분류 + latency-perception.md §10.2 타임라인에 이 구간 추가.
    • 실제로 길다(수백 ms대) → 비동기화(fire-and-forget + 재시도) 검토가 필요하다. 다만 ai-server/app/grpc/spring_client.py:43 주석이 이미 *"SavePoseDataBatch 도 실패하면 rep 하나 분량이 통째로 사라진다"*고 적어놨으므로, 비동기화는 유실 방지 설계(현재의 재시도/서킷 로직을 어떻게 유지할지)가 같이 필요하다.

관련

  • docs/decisions/slo-baseline.md §4-0(gRPC 실측 baseline), §4-1(정책 판정선)
  • docs/decisions/latency-perception.md §3.2, §9.2, §10.2
  • 이 발견은 loadtest 스크립트에 "정책적 실패"(SLO 위반을 실패로 카운트) 게이트를 추가하는 작업 중 gRPC 경로용 임계값 근거를 찾다가 나왔다 — 그 작업에서는 이 이슈가 닫히기 전까지 gRPC(ghz) 쪽 정책 게이트를 보류한다.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmeasure아직 안 잰 것. 고칠 코드가 아니라 측정 과제다product제품 코드(Spring·AI·프론트)의 결함. 실험과 무관하게 남는다

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions