ES /docs

failed to calculate voxels for Capture ID: 745783, error: error state model cannot calculate voxel

RCA: failed to calculate voxels for Capture ID: 745783, error: error state model cannot calculate voxel

Overview#

What Happened#

2026-08-01 02:25:36 KST 에 cupixworks-worker (production, us-west-2) 에서 Capture ID 745783 의 voxel 계산이 실패했다. Capture 가 이미 error_code=TES10002 상태였는데, editing 이 skipped 로 종료되는 finalization 콜백이 calculate_voxels 를 호출했고, Cupix::VoxelService.calculate_voxels!error_code.present? 검증(voxel_service.rb:7) 에 걸려 Cupix::Errors::Argument("error state model cannot calculate voxel") 를 raise → VoxelModule#calculate_voxels 의 rescue 가 이를 error 레벨로 로깅했다.

Quick Facts#

Field Value
exception.class Cupix::Errors::Argument (code ARG40000)
exception.message error state model cannot calculate voxel
top_frame app/services/cupix/voxel_service.rb:7 (raise), app/models/concerns/voxel_module.rb:12 (log)
env production, us-west-2
tenant cupix
affected model Capture id=745783, error_code=TES10002

Affected Teams#

Team / Domain Error Count Impact
capture / editing finalization (cupixworks-worker) 1 (이 클러스터) / 14일 누적 5건 사용자 기능 영향 없음 — 이미 에러 상태인 Capture 에 대한 voxel 재계산 시도만 실패. 노이즈 에러 로그 발생.

Timeline#

  1. 2026-08-01 02:25:36 KST — Capture 745783 finalization 진입: start validation for 3D reconstruction. capture_id: 745783, editing_support: true (info)
  2. 2026-08-01 02:25:36 KST — 동일 시각, error_code=TES10002 로 인해 publish, 3d_reconstruction, analysis, CaptureIntelligence, editing 모두 정상적으로 skip 됨 (capture 745783 publish skipped: error_code(TES10002) exist, Editing skipped on Capture 745783 등 info 로그)
  3. 2026-08-01 02:25:36 KSTafter_editing_finalized :calculate_voxels 콜백 실행 → Cupix::VoxelService.calculate_voxels!error_code.present? 검증에서 ARG40000 raise → VoxelModule#calculate_voxels rescue 에서 error 로그 발생 (본 클러스터)
  4. 2026-08-01 02:26:08 KSTUpdating associated deviations for Capture 745783 (info) — 후속 처리는 정상 진행
  5. 2026-08-01 02:26:10 KSTUpdating associated sitetracks for Capture 745783 (info) — 후속 처리는 정상 진행

Error Log#

Datadog Logs

text
failed to calculate voxels for Capture ID: 745783, error: error state model cannot calculate voxel

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 1 (이 클러스터), 14일 누적 동일 fingerprint 5건 (Capture 78040, 78123, 78180, 738079, 745783)
  • 최초 발생: 2026-08-01 02:25:36 KST
  • 최근 발생: 2026-08-01 02:25:36 KST
  • 사용자 영향: 없음. 이미 에러 상태 (error_code set) 인 Capture 에 대한 voxel 재계산 시도가 실패한 것으로, 정상적인 방어 로직이 작동한 결과이며 후속 처리(deviations, sitetracks 업데이트)는 계속 진행된다. 다만 정상 흐름에서 error 레벨 로그가 발생하여 알림 노이즈가 된다.

Root Cause Summary#

Capture#editing_stateskipped (또는 done) 으로 전이될 때 등록된 finalization 콜백 체인이 error_code 유무와 무관하게 calculate_voxels 를 호출한다. after_editing_finalized :calculate_voxels (app/models/concerns/quality_assurancable/editing_element.rb:21) 는 사전에 error 상태를 확인하지 않고, VoxelModule#check_calculate_voxels! (app/models/concerns/voxel_module.rb:35-44) 도 pointcloud_type / potree_state 만 검사할 뿐 error_code 는 검사하지 않는다. 결국 Cupix::VoxelService.calculate_voxels! (app/services/cupix/voxel_service.rb:7) 의 guard clause 에서 Cupix::Errors::Argument(ARG40000) 이 raise 되고, VoxelModule#calculate_voxels (app/models/concerns/voxel_module.rb:9-17) 의 rescue => e 가 이를 Cupix::Logger.error 로 기록한다. 즉 다른 finalization 콜백들 (publish_on_finish?, run_3d_reconstruction?, run_analysis?, run_capture_intelligence?) 은 모두 error_code 를 사전 확인하여 info 레벨 skip 로그만 남기지만, calculate_voxels 만 사전 확인 없이 서비스 레이어까지 내려간 뒤 예외로 튕겨나오는 비대칭 구조가 원인이다.

Technical Analysis#

Code Path#

Entry point: app/models/concerns/quality_assurancable/editing_element.rb:171-173 — editing_state 가 skipped/done 으로 전이되면 run_editing_finalized_callback 을 실행.

app/models/concerns/quality_assurancable/editing_element.rb:170-173ruby
after_transition from: any, to: %i[done], &:edit_done_handler
after_transition from: any, to: %i[skipped done] do |model, transition|
  model.run_editing_finalized_callback
end

콜백 등록: app/models/concerns/quality_assurancable/editing_element.rb:21after_editing_finalized :calculate_voxels.

app/models/concerns/quality_assurancable/editing_element.rb:15-21ruby
included do
  include ::QualityAssurancable::Editing
  include ::QualityAssurancable::EditingElement::Callbacks

  after_trash :fix_invalid_capture_editing_state
  after_untrash :fix_invalid_capture_editing_state
  after_editing_finalized :calculate_voxels

VoxelModule#calculate_voxelserror_code 를 사전 확인하지 않고 그대로 calculate_voxels! 를 호출하며, 예외를 rescue 하여 error 레벨로 기록한다.

app/models/concerns/voxel_module.rb:9-33ruby
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

# ...

def calculate_voxels!(session: nil)
  if check_calculate_voxels!
    Cupix::VoxelService.calculate_voxels!(model: self, session: session)
  end
end

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
end

Failure point: app/services/cupix/voxel_service.rb:7 — service 진입부 guard clause 에서 raise.

app/services/cupix/voxel_service.rb:3-12ruby
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?

  model.queued_voxel_state! if model.has_attribute?(:voxel_state)

  invoke(model: model, session: session, action: 'calculate_voxels', **kwargs)
end

기대 동작 vs 실제 동작

  • 기대: 다른 finalization/publish 콜백들처럼 error_code 가 있으면 사전에 check 하고 info 레벨로 skip 로그만 남긴다 (예: capture 745783 publish skipped: error_code(TES10002) exist).
  • 실제: calculate_voxels 만 사전 체크가 없어 서비스 레이어까지 진입 후 raise → rescue → error 레벨 로그.

Log Evidence#

Datadog 쿼리 (본 클러스터):

text
service:cupixworks-worker status:error @environment:production "failed to calculate voxels for Capture ID: 745783, error: error state model cannot calculate voxel"

Datadog 쿼리 (14일 누적, 동일 fingerprint):

text
service:cupixworks-worker status:error "error state model cannot calculate voxel"

Capture 745783 주변 (24h) 로그 스니펫 — error_code=TES10002 상태에서 다른 콜백은 모두 skip, calculate_voxels 만 error 로 로그됨:

text
service:cupixworks-worker "745783"
json
{"timestamp":"2026-08-01 02:25:36","status":"info","message":"start validation for 3D reconstruction. capture_id: 745783, editing_support: true","class":"Capture","function":"run_3d_reconstruction?"}
{"timestamp":"2026-08-01 02:25:36","status":"info","message":"3D reconstruction was skipped due to the existence of an capture error code. capture_id: 745783, error_code: TES10002","function":"run_3d_reconstruction?"}
{"timestamp":"2026-08-01 02:25:36","status":"info","message":"Analysis skipped: error_code=TES10002. capture_id: 745783","function":"run_analysis?"}
{"timestamp":"2026-08-01 02:25:36","status":"info","message":"CaptureIntelligence skipped: error_code=TES10002. capture_id: 745783","function":"run_capture_intelligence?"}
{"timestamp":"2026-08-01 02:25:36","status":"info","message":"capture 745783 publish skipped: error_code(TES10002) exist","function":"publish_on_finish?"}
{"timestamp":"2026-08-01 02:25:36","status":"info","message":"Editing skipped on Capture 745783","class":"Capture","function":"start_finalization"}
{"timestamp":"2026-08-01 02:25:36","status":"error","message":"failed to calculate voxels for Capture ID: 745783, error: error state model cannot calculate voxel","class":"Capture","function":"calculate_voxels"}

14일 재현 이력 — 동일 exception, 서로 다른 Capture ID:

text
2026-07-18 16:15:35 — Capture ID 78040
2026-07-19 12:17:40 — Capture ID 738079
2026-07-19 16:05:36 — Capture ID 78123
2026-07-20 16:15:37 — Capture ID 78180
2026-08-01 02:25:36 — Capture ID 745783

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Editing finalization 콜백이 error_code 사전 검증 없이 calculate_voxels 를 호출하여, Cupix::VoxelService.calculate_voxels! 의 guard clause 에서 raise 된 예외가 error 레벨로 로깅됨 editing_element.rb:21after_editing_finalized :calculate_voxels 무조건 등록, voxel_module.rb:35-44check_calculate_voxels! 가 error_code 미검사, voxel_service.rb:7 의 guard 가 exact 메시지 "error state model cannot calculate voxel" 로 raise. 동일 시각 Capture 745783 의 다른 finalization 콜백(publish_on_finish?, run_3d_reconstruction? 등) 은 error_code=TES10002 를 이유로 skip 로그 남김 Confirmed
H2 외부 voxel-service (data-pipeline-functions) 장애로 인한 timeout/5xx 예외 메시지가 코드에 하드코딩된 문자열과 정확히 일치 — 네트워크 예외 메시지와 다름. voxel-service 호출은 guard clause 이후에만 발생하며, 이 케이스는 guard 에서 raise 됨 예외 메시지 "error state model cannot calculate voxel" 은 voxel_service.rb:7 의 리터럴이며 RestClient/JSON 예외 메시지 포맷과 상이 Rejected
H3 Capture 745783 이 삭제되었거나 model 이 blank 로그에 Capture ID 가 명시되고, 후속 콜백(Updating associated deviations/sitetracks) 이 정상 실행됨. blank 였다면 ARG10000 model is required 로 raise 되었을 것 실제 메시지는 ARG40000 (error state model cannot calculate voxel) 이며 이는 model.error_code.present? 경로 Rejected
H4 크론(Cupix::Cron::Capture.recalculate_voxels) 이 error 상태 Capture 를 다시 스캔해서 재시도 크론 진입 로그가 이 시각에 없음. 로그 흐름은 finalization 콜백 체인 (start_finalization, run_3d_reconstruction?, publish_on_finish?) 순서로 이어져 editing_state 전이에서 발생 크론 시각(config/schedule.rb:76) 과 로그 시각 불일치, 크론 실행 로그 없음 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

없음 — 사용자 기능 영향 없이 error 로그만 발생하는 낮은 심각도. 후속 처리도 정상 진행됨.

단기 개선 (1주 이내)#

  • app/models/concerns/voxel_module.rb:35-44 check_calculate_voxels! 에 error_code 조기 반환 추가
    • has_attribute?(:error_code) && error_code.present? 이면 return false 하여 서비스 레이어 진입 전에 skip. 다른 finalization 콜백과 동일한 방어 패턴을 갖추게 된다.
    • 또는 app/models/concerns/voxel_module.rb:9 의 wrapper calculate_voxels 에서 error_code 체크 후 info 레벨 skip 로그 남기고 조기 return.
    • 근거: publish/3d_reconstruction/analysis/capture_intelligence 콜백은 이미 error_code 를 사전 검사해 info 레벨 "skipped" 로그만 남긴다(로그 증거 참조). 동일 패턴을 voxel 콜백에도 적용하면 error 레벨 노이즈가 사라진다.
  • 대안: rescue 절 로그 레벨 조정
    • voxel_module.rb:11-14 의 rescue 에서 예외가 Cupix::Errors::Argument (특히 code ARG40000) 인 경우 warn 또는 info 로 다운그레이드. 단, memory rule ("Scope warn-level downgrades to the specific exception class") 에 따라 rescue => e 를 통째로 warn 화하지 말고, 해당 예외 클래스/코드에 한정할 것.
    • 사전 체크 (check_calculate_voxels! 수정) 이 더 근본적인 해결이므로 우선 순위.

장기 개선 (재발 방지)#

  • Finalization 콜백 등록 시 공통 guard (error_code 검사) 를 강제하는 상위 helper (예: after_editing_finalized_when_no_error_code :calculate_voxels) 도입 검토. 향후 새로운 finalization 콜백이 추가될 때 동일한 비대칭 문제가 재발하지 않도록 보장.
  • error_code 상태 Capture 에 대한 콜백 실행 정책(모두 skip vs 일부 실행) 을 팀 내 문서화. 현재는 콜백별로 개별 판단이라 일관성이 없다.

Monitoring#

  • 에러 트렌드 확인 (동일 fingerprint 재발 감지):
text
service:cupixworks-worker status:error "error state model cannot calculate voxel"
  • error_code 있는 Capture 의 finalization 이후 error 로그 비율:
text
service:cupixworks-worker status:error @class:Capture @function:calculate_voxels
  • 정상 skip 패턴(비교용, info 레벨):
text
service:cupixworks-worker status:info "publish skipped: error_code"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (voxel_module 의 wrapper 또는 check_calculate_voxels! 에 error_code 조기 반환 한 줄 추가 + 사이드 이펙트가 다른 흐름을 막지 않도록 spec 보강)