failed to calculate voxels for Capture ID: 686800, error: error state model cannot calculate voxel
RCA: failed to calculate voxels — error state model cannot calculate voxel
Overview#
What Happened#
2026-04-24 02:42 UTC에 cupixworks-api 서비스에서 Capture 686800에 대한 voxel 계산 시도가 실패했다. Capture가 이미 에러 상태(error_code: AGT5105)인데도 calculate_voxels 콜백이 무조건 실행되어 ARG40000 예외가 발생했다. 이 패턴은 최근 14일간 123건 발생한 반복적 이슈다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::Argument |
| exception.message | error state model cannot calculate voxel |
| top_frame | app/services/cupix/voxel_service.rb:7 |
| env | production, us-west-2 |
| deploy | production-us-west-2-20260424T0117Z0-a4578cc0-cupixworks |
Timeline#
- 02:42:13Z — PUT /api/v1/captures/686800 — error_code, voxel_state 등 상태 업데이트
- 02:42:24Z —
calculate_voxels콜백 실행,ARG40000에러 발생 - 02:42:26Z —
start_finalization실행, 3D reconstruction/analysis/publish 모두 AGT5105로 skip - 2026-04-24 — Error Sweeper가 클러스터 감지, RCA 수행
Error Log#
failed to calculate voxels for Capture ID: 686800, error: error state model cannot calculate voxel
Impact#
- Service:
cupixworks-api - 발생 횟수: 1 (이 클러스터), 123건 (동일 패턴, 최근 14일)
- 최초 발생: 2026-04-24T02:42:24.795Z
- 최근 발생: 2026-04-24T02:42:24.795Z
- 영향 범위: 121개 고유 Capture ID에서 발생. production과 stage 환경 모두 영향. 에러 자체는
rescue로 잡히고false를 반환하므로 finalization 프로세스가 중단되지는 않지만, 불필요한 error 레벨 로그가 대량 발생하여 모니터링 노이즈를 유발한다.
Root Cause Summary#
job_stopped_callback 메서드에서 initiate_editing_state가 error_code 할당보다 먼저 호출되고, editing state가 skipped로 전환되면서 after_editing_finalized 콜백으로 등록된 calculate_voxels가 무조건 실행된다. 이후 Cupix::VoxelService.calculate_voxels!에서 model.error_code.present? 체크에 걸려 ARG40000 예외가 발생한다. 이는 에러 상태 capture에서 voxel 계산이 불필요하다는 기대 동작과 일치하지만, 콜백 등록 시 error_code 여부를 확인하지 않아 매번 에러 로그가 기록되는 구조적 문제다.
Technical Analysis#
Code Path#
1. Entry Point — Job 완료 콜백
job_stopped_callback에서 initiate_editing_state가 error_code 할당(line 39)보다 먼저 호출된다(line 34).
def job_stopped_callback(job)
run_callbacks(:job_stopped_callback) do
if job.update_jobable_state?
update_refinement_state
initiate_editing_state # Line 34 - editing state 전환 트리거
start_finalization # Line 35
update(processing_finished_at: DateTime.now)
stat_processing_finished_at if self.respond_to?(:stat_processing_finished_at)
self.log_trace_event('processing_finished')
self.error_code = job.error_code if job.error_code.present? # Line 39 - error_code 설정
flush_record_geo_coordinate_in_worker if record_geo_coordinate_flush_required?
done_state # Line 42
end
end
end
2. Editing State 전환 — skipped로 전환 시 콜백 발동
initiate_editing_state는 QA service가 비활성이면 skipped_editing_state로 전환한다.
def initiate_editing_state
skipped_editing_state and return unless qa_service?
skipped_editing_state and return if migrated?
skipped_editing_state and return if cqa_skip?
fire_events("#{default_editing_state}_editing_state")
end
skipped 또는 done으로 전환되면 run_editing_finalized_callback이 호출된다:
after_transition from: any, to: %i[skipped done] do |model, transition|
model.run_editing_finalized_callback
end
3. Callback 등록 — calculate_voxels가 무조건 등록
after_editing_finalized :calculate_voxels가 error_code 조건 없이 등록되어 있다:
after_editing_finalized :calculate_voxels
4. Voxel 계산 시도 — error_code 체크 없이 VoxelService 호출
calculate_voxels는 rescue로 에러를 잡아 error 로그를 남기고 false를 반환한다:
def calculate_voxels(session: nil)
calculate_voxels!(session: session)
rescue => e
Cupix::Logger.error("failed to calculate voxels for #{self.class.name} ID: #{id}, error: #{e.message}",
class: self.class.name, function: __method__, model: { id: id, type: self.class.name })
false
else
true
end
check_calculate_voxels!에도 error_code 검사가 없다:
def check_calculate_voxels!
raise Cupix::Errors::Parameter.new(code: 'ARG10000', reason: 'model must be a capture or pointcloud') unless self.is_a?(::Capture) || self.is_a?(::Pointcloud)
return false if self.respond_to?(:pointcloud_group?) && self.pointcloud_group?
return false if self.has_attribute?(:pointcloud_type) && self.pointcloud_type == '3d_reconstructed'
raise Cupix::Errors::Parameter.new(code: 'ARG10060', reason: 'Potree is not uploaded') if self.respond_to?(:potree_state_uploaded?) && !self.potree_state_uploaded?
true # error_code 체크 없음
end
5. Failure Point — VoxelService에서 error_code 감지
def calculate_voxels!(model: nil, session: nil, **kwargs)
return unless Cupix::Tesla.launch_mode == 'CUPIXWORKS'
raise Cupix::Errors::Argument.new(code: 'ARG10000', reason: 'model is required') if model.blank?
raise Cupix::Errors::Argument.new(code: 'ARG10000', reason: 'model must be a capture or pointcloud') unless model.is_a?(::Capture) || model.is_a?(::Pointcloud)
raise Cupix::Errors::Argument.new(code: 'ARG40000', reason: 'error state model cannot calculate voxel') if model.error_code.present?
end
Log Evidence#
Datadog에서 Capture 686800의 전체 처리 타임라인을 검색했다:
service:cupixworks-api 686800
Time: 2026-04-24T01:42:24Z to 2026-04-24T03:12:24Z
핵심 로그 엔트리:
{
"timestamp": "2026-04-24T02:42:13.782Z",
"level": "info",
"message": "PUT /api/v1/captures/686800",
"controller": "CapturesController",
"action": "update",
"status": 200,
"params": "voxel_state, reconstruction_state, analysis_state, error_code"
}
{
"timestamp": "2026-04-24T02:42:24.795Z",
"level": "error",
"message": "failed to calculate voxels for Capture ID: 686800, error: error state model cannot calculate voxel",
"class": "Capture",
"function": "calculate_voxels",
"host": "ip-10-1-144-228.us-west-2.compute.internal",
"request_id": "799f9b9b-4840-408d-aef2-c65f67ce1ff1"
}
{
"timestamp": "2026-04-24T02:42:26.802Z",
"level": "info",
"message": "3D reconstruction was skipped due to the existence of an capture error code. capture_id: 686800, error_code: AGT5105",
"function": "run_3d_reconstruction?",
"capture_state": { "state": "done", "error_code": "AGT5105", "processing_status": "preprocessor", "running_state": "stopped" }
}
14일간 동일 패턴 반복 확인:
service:cupixworks-api "failed to calculate voxels"
Time: now-14d to now
Results: 123 logs across 121 unique Capture IDs
| Date | Count |
|---|---|
| 2026-04-21 | 60 |
| 2026-04-16 | 23 |
| 2026-04-14 | 11 |
| 2026-04-17 | 9 |
| 2026-04-23 | 7 |
| 2026-04-15 | 7 |
| 2026-04-24 | 5 |
| 2026-04-11 | 1 |
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | calculate_voxels 콜백이 error_code 확인 없이 무조건 실행되어, 에러 상태 capture에서 불필요한 error 로그 발생 |
editing_element.rb:21에 조건 없이 after_editing_finalized :calculate_voxels 등록. check_calculate_voxels!(voxel_module.rb:35-44)에 error_code 체크 없음. 14일간 123건 반복 발생 |
— | Confirmed |
| H2 | job_stopped_callback에서 error_code가 initiate_editing_state 이후에 설정되어 순서 문제 발생 |
jobable/capture.rb:34에서 initiate_editing_state 호출 후 line 39에서 error_code 설정. 그러나 PUT API(02:42:13Z)에서 이미 error_code가 DB에 설정된 상태이므로, 콜백 시점에 model.error_code가 이미 존재 |
error_code가 PUT API로 먼저 설정되므로 순서 문제 자체가 아닌, 콜백에서 error_code를 체크하지 않는 것이 근본 원인 | Rejected (부분적으로 관련) |
| H3 | Voxel-service 자체의 장애로 계산 실패 | — | voxel-service에 에러 로그 없음. 에러가 VoxelService.calculate_voxels! line 7의 사전 검증 단계에서 발생하여 외부 서비스 호출 전에 차단됨 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
app/models/concerns/voxel_module.rb:35-44의check_calculate_voxels!메서드에error_code.present?체크를 추가하여, error_code가 있으면false를 반환하도록 수정. 이렇게 하면VoxelService.calculate_voxels!까지 도달하지 않고 조기에 skip하므로 error 로그가 발생하지 않는다.- 또는
app/models/concerns/voxel_module.rb:12의rescue블록에서ARG40000에러 코드인 경우 로그 레벨을warn으로 낮추는 방법도 가능하다.
단기 개선 (1주 이내)#
after_editing_finalized :calculate_voxels콜백 등록 시 조건부 실행으로 변경 (editing_element.rb:21). 예:after_editing_finalized :calculate_voxels, if: -> { error_code.blank? }
장기 개선 (재발 방지)#
job_stopped_callback(jobable/capture.rb:29-48)의 실행 순서를 정리하여, error_code 설정을initiate_editing_state보다 먼저 수행하는 것을 검토. 이렇게 하면 editing 콜백 시점에 이미 error_code가 설정되어 있어 조건부 skip이 더 신뢰성 있게 동작한다.- 에러 상태 capture에서 불필요하게 트리거되는 다른 콜백도 점검 (3D reconstruction, analysis, publish은 이미 error_code 체크가 있어 skip 처리됨).
Monitoring#
- 다음 Datadog 쿼리로 수정 후 에러 감소를 확인:
service:cupixworks-api "failed to calculate voxels" status:error
- 수정 배포 후 24시간 내 발생 건수가 0에 수렴하는지 모니터링
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial —
check_calculate_voxels!에 1줄 추가 또는 콜백 조건 변경 - 에러가
rescue로 잡혀 finalization 프로세스에는 영향 없음. 실질적 위험은 모니터링 노이즈와 불필요한 로그 볼륨 증가.