ES /docs

VoxelService::run | end - error: "Invalid potreeUrl"

RCA: VoxelService::run | end - error: "Invalid potreeUrl"

Overview#

What Happened#

2026-05-12 06:35 UTC, cupixworks-any-voxel-agent 서비스(ap-southeast-2 리전)에서 Pointcloud ID 204434에 대한 voxel 계산 작업이 "Invalid potreeUrl" 에러로 실패했다. 해당 pointcloud의 potree_url 필드가 비어있는 상태에서 voxel 계산 요청이 전달된 것이 원인이다.

Quick Facts#

Field Value
exception.message Invalid potreeUrl
top_frame cpreality-capture.ts:147
env production, ap-southeast-2

Affected Teams#

Team / Domain Error Count Impact
endeavourgroup (team ID: 180) 1 Pointcloud ID 204434의 voxel 계산 실패, voxel_state가 Error로 전환

Timeline#

  1. 2026-05-12T06:35:44.170Z — Voxel agent가 SQS 메시지를 수신하여 Pointcloud 204434 처리 시작
  2. 2026-05-12T06:35:44.170ZCPRealityCapture::validate에서 potreeUrl이 빈 문자열임을 감지, "Invalid potreeUrl" 에러 throw
  3. 2026-05-12T06:35:44.170ZVoxelService::run의 catch 블록이 에러를 로깅하고 voxel_state를 Error로 업데이트

Error Log#

Datadog Logs

text
VoxelService::run | end - error: "Invalid potreeUrl"

Impact#

  • Service: cupixworks-any-voxel-agent
  • 발생 횟수: 1
  • 최초 발생: 2026-05-12T06:35:44.170Z
  • 최근 발생: 2026-05-12T06:35:44.170Z

Root Cause Summary#

Pointcloud ID 204434의 potree_url이 null/빈 문자열인 상태에서 voxel 계산 SQS 메시지가 전달되었다. Rails 측 Cupix::VoxelService.calculate_voxels! 메서드는 potree_state 검증을 수행하지 않고 SQS 메시지를 전송한다. potree_state 검증은 check_calculate_voxels! 메서드에만 존재하며, refresh_voxels!를 통해 개별 pointcloud에 직접 호출하는 경우 이 검증이 우회된다. 결과적으로 potree 파일이 아직 업로드되지 않은(또는 revision이 0인) pointcloud에 대해 voxel 계산이 시도되어 agent 측 validation에서 실패한 것이다.

Technical Analysis#

Code Path#

  • Entry point: voxel-service.ts:45VoxelService.run() 메서드가 SQS 메시지로부터 targetId와 targetType을 수신
  • reality_capture.manager.ts:39-67loadRealityCapture()가 cupixApi를 통해 pointcloud 데이터를 로드
  • reality_capture.manager.ts:69-79createCPRealityCapture()가 서버 모델로 CPRealityCapture 인스턴스 생성 후 validate() 호출
  • Failure point: cpreality-capture.ts:144-148validate() 메서드에서 pointcloud의 potreeUrl이 빈 문자열인지 검증
packages/cupix-tesla-voxel-agent/src/model/cpreality-capture.ts:47typescript
get potreeUrl(): string { return (this._srvModel as TESLA.Pointcloud)?.potree_url ?? ''; }
packages/cupix-tesla-voxel-agent/src/model/cpreality-capture.ts:144-148typescript
if (this.isPointcloud) {
    if (!this.potreeUrl) {
        logger.error('CPRealityCapture::validate | Invalid potreeUrl: %s for Pointcloud ID: %d', this.potreeUrl, this.id);
        throw new Error('Invalid potreeUrl');
    }

Rails 측에서 SQS로 메시지를 보내는 과정:

app/services/cupix/voxel_service.rb:3-12ruby
def calculate_voxels!(model: nil, session: nil, **kwargs)
  return unless Cupix::Tesla.launch_mode == 'CUPIXWORKS'
  raise ... if model.blank?
  raise ... unless model.is_a?(::Capture) || model.is_a?(::Pointcloud)
  raise ... 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

이 메서드에는 potree_statepotree_url 검증이 없다. 검증은 별도의 check_calculate_voxels!에만 존재:

app/models/concerns/voxel_module.rb:35-43ruby
def check_calculate_voxels!
  raise ... 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

Rails의 potree_url 메서드는 revision이 0이면 nil을 반환:

app/models/concerns/potree.rb:14-18ruby
def potree_url
  return nil if revision.zero?
  return self.sys[:origin_potree_url] if self.sys[:origin_potree_url].present?
  Cupix::StorageService.object_url(storage_option: storage_option, key: potree_basepath(revision))
end

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-any-voxel-agent status:error "VoxelService"
text
service:cupixworks-any-voxel-agent "204434"

에러 로그 (Pointcloud ID 확인):

json
{
  "timestamp": "2026-05-12T06:35:44.170Z",
  "status": "error",
  "message": "CPRealityCapture::validate | Invalid potreeUrl:  for Pointcloud ID: 204434",
  "meta": {
    "team": { "domain": "endeavourgroup", "id": 180 },
    "pointcloud": { "id": 204434 },
    "user": { "id": 8624, "email": "chris.bellamy@auav.com.au" }
  },
  "region": "ap-southeast-2",
  "host": "ip-10-1-46-225.ap-southeast-2.compute.internal"
}

동일 시간대 관련 에러:

text
2026-05-12T06:35:44.170Z - VoxelService::run | end - error: "Invalid potreeUrl"
2026-05-12T06:12:10.000Z - VoxelService::run | end - error: "ENOENT: no such file or directory, stat '/tmp/workspace/pointcloud/204400/raw/pointcloud_204400_voxels.csv'"

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Pointcloud 204434의 potree 파일이 아직 업로드되지 않은 상태에서 voxel 계산 요청이 전달됨 (potree_url이 null/empty) 로그에서 Invalid potreeUrl: (빈 문자열) 확인. Rails potree_url 메서드는 revision.zero?이면 nil 반환 (potree.rb:15). calculate_voxels!에 potree_state 검증 없음 (voxel_service.rb:3-12) Confirmed
H2 Agent가 API에서 pointcloud를 조회할 때 potree_url 필드가 serialization에서 누락됨 SQS body에 pointcloud 데이터 일부만 포함 (voxel_agent.rb:28-32: fields는 id, name, voxel_state만 포함) Agent는 API를 통해 전체 pointcloud 데이터를 다시 조회함 (reality_capture.manager.ts:43-45), SQS 메시지의 부분 데이터를 사용하지 않음 Rejected
H3 refresh_voxels!check_calculate_voxels! 검증을 우회하여 potree 미준비 상태의 pointcloud에 voxel 계산 요청 전달 refresh_voxels! (voxel_module.rb:81-82)는 개별 pointcloud에 대해 calculate_voxels!를 직접 호출, check_calculate_voxels!를 거치지 않음. Facility 레벨 호출 시에는 potree_unloaded scope 필터가 있으나 개별 pointcloud 직접 호출은 필터 없음 정확한 호출 경로 확인 불가 (worker 로그 미발견) Confirmed (보조 원인)

Fix Recommendation#

즉시 조치 (Critical)#

  • app/services/cupix/voxel_service.rb:3-12: calculate_voxels! 메서드에 pointcloud인 경우 potree_state_uploaded? 검증을 추가해야 한다. 이 메서드가 SQS 메시지 전송 전 최종 게이트키퍼 역할을 해야 한다.

단기 개선 (1주 이내)#

  • Voxel agent의 VoxelService::run (voxel-service.ts:63-66)에서 catch 블록의 에러 로그 메시지 형식을 "end - error" 대신 더 구체적인 형태로 개선하여, validation 실패(데이터 문제)와 실제 처리 에러를 구분할 수 있도록 한다.
  • Pointcloud 204434의 현재 상태를 확인하여 potree 업로드가 필요한지, 아니면 voxel 계산 대상에서 제외해야 하는지 판단한다.

장기 개선 (재발 방지)#

  • Cupix::VoxelService.calculate_voxels!에 precondition 검증을 통합하여, 모든 호출 경로(직접 호출, refresh_voxels!, check_calculate_voxels!)에서 동일한 검증 로직이 적용되도록 한다. 현재 검증 로직이 caller에 분산되어 있어 우회 가능성이 존재한다.

Monitoring#

  • Voxel agent의 "Invalid potreeUrl" 에러 추적:
text
service:cupixworks-any-voxel-agent status:error "Invalid potreeUrl"
  • Potree 미업로드 상태에서 voxel 계산 요청 감지 (Rails 측):
text
service:cupixworks-worker "ARG10060" "Potree is not uploaded"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial