무엇을 발견했나
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:419의 return 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 지연을 유발하는지 로그·트레이스로 확인된 적 없다.
재현 경로 (코드 근거)
ai-server/app/api/endpoints/pose.py:359 — spring_client.report_pose_data_batch(...) 호출 (블로킹)
ai-server/app/api/endpoints/pose.py:419 — 그 호출이 끝난 뒤에야 return PoseResponse(...)
- 대조 ①:
docs/decisions/latency-perception.md §10.2 타임라인 표 — 이 RPC 구간이 없다
- 대조 ②:
docs/decisions/slo-baseline.md §4-1 — "사용자 대면 아님" 근거로 §3 인용(오귀속)
ai-server/app/observability/metrics.py:46 — "SavePoseDataBatch는 rep마다 온다" 주석
제안
- 무부하 단일요청 latency부터 실측한다 (위 "미검증" 항목).
- 그 값에 따라 갈린다:
- 실제로 짧다(예: 수십 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) 쪽 정책 게이트를 보류한다.
무엇을 발견했나
docs/decisions/slo-baseline.md §4-1은 gRPCSavePoseDataBatch를 "이 경로는 사용자 대면이 아니다"로 분류하고, 근거로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(...)(= gRPCSavePoseDataBatch)가 동기 호출로 실행되고, 그 결과를 받은 뒤에야pose.py:419의return 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)를 어기는 셈이다.미검증 — 확인 안 된 것
재현 경로 (코드 근거)
ai-server/app/api/endpoints/pose.py:359—spring_client.report_pose_data_batch(...)호출 (블로킹)ai-server/app/api/endpoints/pose.py:419— 그 호출이 끝난 뒤에야return PoseResponse(...)docs/decisions/latency-perception.md §10.2타임라인 표 — 이 RPC 구간이 없다docs/decisions/slo-baseline.md §4-1— "사용자 대면 아님" 근거로 §3 인용(오귀속)ai-server/app/observability/metrics.py:46— "SavePoseDataBatch는 rep마다 온다" 주석제안
slo-baseline.md §4-1재분류 +latency-perception.md §10.2타임라인에 이 구간 추가.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