Exception occurred at set_upload_state. from: upload_done, to: upload_done, state_updated_at: 2026-0
RCA: Exception occurred at set_upload_state (upload_done to upload_done)
Error Log#
Exception occurred at set_upload_state. from: upload_done, to: upload_done, state_updated_at: 2026-04-13 07:23:59 UTC
Impact#
- Service:
cupixworks-api - 발생 횟수: 2
- 최초 발생: 2026-04-13T07:24:02.174Z
- 최근 발생: 2026-04-13T07:51:22.078Z
Root Cause Summary#
클라이언트가 이미 upload_done 상태인 Capture에 대해 upload_state: 'upload_done'으로 PUT 요청을 보내면서 발생한 중복 상태 전이 오류입니다. set_upload_state 메서드(parameter/capture.rb:119)에서 동일 상태 전이를 명시적으로 차단하는 guard 조건(upload_state_upload_done?)에 의해 Cupix::Errors::InvalidState (code: STAT40000)가 발생하며, rescue 블록에서 에러 로그가 기록됩니다. 이는 클라이언트 측에서 upload 완료 API를 중복 호출(retry 또는 race condition)하는 것이 원인입니다.
Technical Analysis#
Code Path#
- Entry point (API):
app/controllers/api/v1/captures_controller.rb:46—update액션이repository_instance.update(params)를 호출합니다.
# app/controllers/api/v1/captures_controller.rb:46-49
def update
@model = repository_instance.update(params)
super
end
- Repository:
app/repositories/capture_repository.rb:182-194—update메서드가set_parameters(params)를 호출합니다.
# app/repositories/capture_repository.rb:182-194
def update(params = {})
super
set_parameters(params)
begin
@model.save!
rescue StandardError => e
raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: 'Invalid argument', message: e.message)
end
@model
end
- Parameter dispatch:
app/concerns/parameter/capture.rb:17-19—set_parameters가set_upload_state(params)를 호출합니다.
# app/concerns/parameter/capture.rb:17-19
def set_parameters(params = {})
set_error_code(params)
set_upload_state(params)
- Failure point:
app/concerns/parameter/capture.rb:119— 이미upload_done상태인 모델에 대해 guard 조건이true를 반환하여InvalidState예외를 발생시킵니다.
# app/concerns/parameter/capture.rb:116-131
when 'upload_done'
raise Cupix::Errors::InvalidState.new(code: 'STAT10000', reason: 'At least 1 pano or video is required to make capture as upload_done state') unless @model.processible_materials_exist?
raise Cupix::Errors::InvalidState.new(code: 'STAT40000', reason: 'State not changed') if @model.upload_state_upload_done?
@model.upload_done_upload_state!
when 'pano_uploading'
# ...
end
rescue Cupix::Errors::InvalidState => e
Cupix::Logger.error("Exception occurred at set_upload_state. from: #{@model.upload_state}, to: #{params[:upload_state]}, state_updated_at: #{@model.state_updated_at}")
raise e
- State machine 정의:
app/models/concerns/statable/capture.rb:197-270—upload_statestate machine은transition all => :upload_done을 허용하지만, application layer에서 동일 상태 전이를 차단합니다.
# app/models/concerns/statable/capture.rb:224-226
event :upload_done do
transition all => :upload_done, if: :processible_materials_exist?
end
기대 동작: 클라이언트가 upload_state: 'upload_done'을 요청할 때, Capture가 upload_ready 또는 다른 이전 상태에서 upload_done으로 전이됩니다.
실제 동작: Capture가 이미 upload_done 상태인데 동일한 전이 요청이 들어와서, line 119의 guard 조건(upload_state_upload_done?)이 true를 반환하고 STAT40000 에러가 발생합니다.
Log Evidence#
두 건의 에러 로그를 Datadog에서 확인했습니다.
검색 쿼리:
service:cupixworks-api status:error "set_upload_state"
에러 로그 1 (07:24:02.174Z):
Exception occurred at set_upload_state. from: upload_done, to: upload_done, state_updated_at: 2026-04-13 07:23:59 UTC
- Host:
ip-10-1-144-228.us-west-2.compute.internal - Request ID:
412ed36e-300b-4f0c-8443-38ad13bb0044 - Region:
us-west-2, Tenant:cupix
에러 로그 2 (07:51:22.078Z):
Exception occurred at set_upload_state. from: upload_done, to: upload_done, state_updated_at: 2026-04-13 07:51:20 UTC
- Host:
ip-10-1-80-134.us-west-2.compute.internal - Request ID:
f0d86735-1d55-4e20-b297-d1f92e3e743a - Region:
us-west-2, Tenant:cupix
정상 전이 로그 (동일 시간대, 50건 확인):
검색 쿼리:
service:cupixworks-api status:info "upload_state changed"
정상적인 전이는 모두 upload_ready → upload_done 패턴을 따릅니다:
[Capture] upload_state changed from upload_ready to upload_done
class_name: "Capture",function: "log_event",remark: "uploading_finished"- 에러 로그에는 이러한 structured attributes(
class_name,function,cupix_trace_id)가 없음 — 정상 전이 콜백이 아닌 rescue 블록에서 로그가 발생했기 때문
주요 관찰 사항:
- 두 번째 에러 발생 14초 후(07:51:36Z) 동일 호스트(
ip-10-1-80-134)에서 정상upload_ready → upload_done전이가 성공 (model_id: 679510). 이는 시스템이 정상 동작 중에 일부 중복 요청만 실패한 것을 보여줍니다. - 두 에러 모두
us-west-2리전,cupixtenant에서만 발생했습니다. state_updated_at값이 에러 발생 시각보다 2~3초 이전이며, 이는 이전 성공 전이 시점의 timestamp입니다.
Fix Recommendation#
즉시 조치 (Critical)#
해당 없음 — 이 에러는 의도된 방어 로직입니다. STAT40000 에러 코드로 동일 상태 전이를 차단하는 것은 올바른 동작이며, 데이터 무결성에 영향을 주지 않습니다.
단기 개선 (1주 이내)#
- 로그 레벨 변경:
app/concerns/parameter/capture.rb:130에서Cupix::Logger.error를Cupix::Logger.warn으로 변경하는 것을 고려합니다. 이 상황은 클라이언트의 중복 요청이며, 시스템 에러가 아닌 경고 수준의 이벤트입니다. error 레벨로 로깅하면 불필요한 에러 알림이 발생합니다. - Idempotent 처리 검토: 클라이언트가 동일 상태 전이를 요청할 때 에러 대신 현재 상태를 성공으로 반환하는 idempotent 패턴 적용을 검토합니다. 이미
upload_done상태라면 추가 작업 없이 200 응답을 반환하면 됩니다.
장기 개선 (재발 방지)#
- 클라이언트 중복 호출 원인 파악: 클라이언트(SDK 또는 프론트엔드)가 upload 완료 API를 중복 호출하는 이유를 조사합니다. 네트워크 retry, 사용자 더블 클릭, 또는 비동기 callback 중복이 원인일 수 있습니다.
- 구조화된 로깅: 에러 로그에
class_name,model_id,cupix_trace_id등의 structured attributes를 추가하여 디버깅을 용이하게 합니다.
Monitoring#
- 이 에러의 빈도를 모니터링하여 특정 클라이언트나 Capture에서 반복적으로 발생하는지 추적:
service:cupixworks-api status:error "set_upload_state" "upload_done, to: upload_done"
- 로그 레벨 변경 후에는 warn 레벨로 모니터링:
service:cupixworks-api status:warn "set_upload_state" "State not changed"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial — 로그 레벨 변경은 1줄 수정, idempotent 처리는 guard 조건의 동작 변경(에러 대신 early return)으로 구현 가능