얼쑤에서 연주 녹음을 보낸 사용자는 평가 화면을 기다립니다. 앱 입장에서는 파일을 서버에 전달했는지, 평가를 받을 수 있는지 두 가지를 확인해야 합니다. 둘을 한 요청에서 처리하면 파일을 저장한 뒤에도 분석이 끝날 때까지 업로드 연결을 유지해야 합니다. 저는 녹음을 접수한 뒤 202 Accepted로 답하고, 앱이 같은 연습 세션의 상태를 따로 조회하도록 연결했습니다. RFC 9110의 202 Accepted

서버의 접수 시점

백엔드는 앱이 보낸 파일을 검사해 R2에 원본을 저장합니다. DB에 원본 키와 녹음 정보를 기록하고 세션을 PENDING으로 바꾼 뒤, 저장이 커밋되면 업로드를 202로 응답합니다. 분석 제출은 같은 커밋을 기준으로 이벤트 리스너가 따로 시도합니다.

녹음 업로드 → R2 원본 저장 → DB에 PENDING 기록·커밋
                              ├─ 업로드 접수 → 202 응답
                              └─ 커밋 후 이벤트 → 분석 제출 시도
앱 → 같은 세션의 상태 조회 → DONE이면 별도로 평가 조회

실행기가 작업을 받으면 별도 DB 연결에서 커밋된 세션을 읽고, 분석 서버가 서명 URL로 원본을 가져옵니다. 분석 연결이 준비되지 않았거나 대기열이 가득 찬 경우에도 업로드 접수는 유효합니다. 이때 남은 PENDING은 마감 뒤 상태 조회에서 FAILED와 TIMEOUT으로 바뀝니다.

2초마다 조회하고 끊긴 시간 측정

대기 화면이 열려 있으면 접수 직후 같은 세션을 한 번 조회합니다. PENDING을 받으면 그 응답에서 2초 뒤 다시 묻습니다. DONE이면 조회를 끝내고 평가를 요청합니다. FAILED도 조회를 끝내고, 응답에 담긴 실패 사유에 맞는 화면을 보여줍니다. 다른 화면에 있거나 앱이 백그라운드로 가면 조회를 멈춥니다. 분석이 진행 중이었다면 대기 화면으로 돌아올 때 바로 한 번 더 묻습니다.

PENDING이 돌아올 때마다 앱은 마지막 상태 응답 시각을 갱신합니다. 연결 오류 등으로 읽을 수 있는 상태 응답을 70초 동안 받지 못하면 앱에서 시간 초과 화면을 띄웁니다. 앱의 시간 초과는 화면 상태에만 적용되며, 서버의 분석 마감은 서버 시계로 따로 판단합니다.

앱은 재시도 요청이 202나 409로 돌아오면 같은 세션의 상태를 다시 읽습니다. 통신이 끊긴 사이 서버가 DONE을 기록했다면 이 조회에서 평가 화면으로 갈 수 있습니다. 진행 중인 시도를 서버가 어떻게 보존하는지와 운영 장애에서 복구한 과정은 재분석 글에 정리했습니다. 녹음 재분석에서 결과가 섞이지 않게

가상 시계로 경계 검증

Kotlin Multiplatform 공통 테스트에 가짜 시계와 응답 대역을 넣었습니다. 서버 응답 순서와 시각을 정해 놓고 대기 화면이 몇 번 조회하는지, 어떤 상태에서 멈추는지 확인했습니다.

테스트 조건확인한 앱 동작
첫 두 번은 PENDING, 세 번째는 DONE시작 즉시, 2초, 4초에 각각 한 번 조회했습니다. 평가 ID를 받은 뒤 시계를 10초 더 움직여도 총 조회는 3회였습니다.
첫 응답이 FAILED응답의 실패 사유를 앱 상태에 기록했고 10초를 더 움직여도 조회는 첫 1회에 그쳤습니다.
시작 뒤 상태 조회가 계속 실패69,999ms에는 실패 상태가 비어 있었고 70,000ms에 앱의 시간 초과 상태가 됐습니다.
첫 조회 후 앱을 백그라운드에 5분 둠조회 횟수는 1회로 유지됐습니다. 복귀 직후 한 번, PENDING 응답 2초 뒤 한 번 더 조회했습니다.

표의 시간은 테스트에서 움직인 시계 값입니다. 별도의 iPhone 확인에서는 마이크를 허용해 녹음한 뒤 평가 화면에 도달했습니다. 실기기에서는 녹음부터 평가 표시까지의 흐름을 확인했고, 조회 간격과 중단 경계는 공통 테스트의 가상 시계로 확인했습니다.