ES /docs

PotreeService::handlingMessageErrors | Errors in error handling - {"response":{"statusCode":403,"bod

RCA: PotreeService::handlingMessageErrors | Errors in error handling

Overview#

What Happened#

2026-06-04 02:15 KST에 cupixvista-any-potree-agent 서비스에서 pointcloud ID 28736에 대한 potree 변환 처리 중 에러 핸들링 과정에서 이차 에러가 발생했다. 초기 에러(undefined response) 발생 후 에러 핸들러가 pointcloud 상태를 Error로 업데이트하려 했으나, 해당 pointcloud가 이미 삭제되어 Tesla API가 403(Not Found) 응답을 반환했다.

Quick Facts#

Field Value
exception.class HttpError
exception.message Pointcloud not found (ARG10002)
top_frame potree-service.ts:519
env production, us-west-2

Timeline#

  1. 2026-06-04 02:14:59 KST — potree-agent가 SQS 메시지 수신, pointcloud 28736 처리 시작
  2. 2026-06-04 02:15:00 KST — PotreeConverter 실행
  3. 2026-06-04 02:15:10 KST — 처리 중 에러 발생, handlingMessageErrors 진입
  4. 2026-06-04 02:15:10 KSTupdatePotreeState 호출 시 403 에러 (Pointcloud not found)
  5. 2026-06-04 02:15:10 KST — SQS 메시지 삭제 완료

Error Log#

Datadog Logs

text
PotreeService::handlingMessageErrors | Errors in error handling - {"response":{"statusCode":403,"body":{"result":{"code":"ARG10002","type":"Cupix::Errors::NotFound","reason":"Pointcloud not found","message":"Pointcloud not found"}},...,"request":{"uri":{"pathname":"/api/v1/pointclouds/28736"},"method":"PUT",...}},"statusCode":403,"name":"HttpError"}

Impact#

  • Service: cupixvista-any-potree-agent
  • Team: mtu-team
  • 발생 횟수: 1
  • 최초 발생: 2026-06-04 02:15 KST
  • 최근 발생: 2026-06-04 02:15 KST

Root Cause Summary#

Potree 변환 처리 중 초기 에러가 발생하여 handlingMessageErrors가 호출되었다. 이 핸들러는 updatePotreeState를 통해 pointcloud 상태를 Error로 업데이트하려 했으나, 대상 pointcloud(ID: 28736)가 이미 Tesla API에서 삭제된 상태였다. Tesla는 Cupix::Errors::NotFound를 HTTP 403으로 매핑하므로 403 응답이 반환되었고, 이 이차 에러가 catch 블록(line 518-519)에서 로깅된 것이 본 클러스터의 에러이다. 근본 원인은 에러 핸들러가 pointcloud 존재 여부를 확인하지 않고 상태 업데이트를 시도하는 구조적 문제이다.

Technical Analysis#

Code Path#

  • Entry point: potree-service.ts:54 (checkingQueue)
  • 메시지 처리: potree-service.ts:130-136 (runByMessagesrunByMessage)
  • 에러 포착: potree-service.ts:76-78runByMessages()에서 throw된 에러를 catch
  • 에러 핸들러: potree-service.ts:502-525 (handlingMessageErrors)
  • Failure point: potree-service.ts:517 (updatePotreeState)

에러 핸들링 흐름:

packages/cupix-tesla-potree-agent/src/potree-service.ts:74-78typescript
try {
    await this.runByMessages();
} catch (error) {
    await this.handlingMessageErrors(error);
}

초기 에러(PotreeConverter 실패 등)가 발생하면 handlingMessageErrors로 진입한다.

packages/cupix-tesla-potree-agent/src/potree-service.ts:512-520typescript
const apiErrorObject = this.getApiErrorToDeleteMessage(error);
if (apiErrorObject != undefined || this.checkReceiveCountToDeleteMessage()) {
    try {
        errorAndMessage.error = apiErrorObject;
        await this.deleteByMessage(this.messageInProcess);
        if (this._modelInProcess != undefined) await this.updatePotreeState(this._modelInProcess.id, TESLA.PointcloudPotreeState.Error);
    } catch (error) {
        logger.error('PotreeService::handlingMessageErrors | Errors in error handling - %s', JSON.stringify(error));
    }
}

getApiErrorToDeleteMessage에서 초기 에러의 .response가 undefined이면 'undefined response' 문자열을 반환한다:

packages/cupix-tesla-potree-agent/src/potree-service.ts:465-468typescript
const response = CPUtils.isJsonString(error) ? JSON.parse(error) : error.response;
if (response == undefined) {
    logger.warn('PotreeService::getApiErrorToDeleteMessage | undefined response - %s', JSON.stringify(error));
    return 'undefined response';
}

이후 updatePotreeStatePUT /api/v1/pointclouds/28736을 호출하지만, Tesla API의 before_action :set_pointcloud에서 해당 레코드를 찾지 못해 NotFound → 403을 반환한다:

app/repositories/base_repository.rb:351-355ruby
if model.nil?
  if self.where(attrs).in_trash.present?
    raise Cupix::Errors::NotFound.new(code: 'ENT4000', reason: "#{current_class.name} not found")
  else
    raise Cupix::Errors::NotFound.new(code: 'ARG10002', reason: "#{current_class.name} not found")
  end
end
app/controllers/concerns/client_error_controller.rb:27,57-58ruby
rescue_from Cupix::Errors::NotFound, with: :not_found_403_error

def not_found_403_error(exception)
  raise_error(403, exception)
end

기대 동작: 에러 핸들러가 pointcloud 존재 여부를 확인하거나 updatePotreeState 실패를 무시해야 함. 실제 동작: pointcloud가 삭제된 상태에서 updatePotreeState 호출 시 403 에러가 발생하고, 이를 별도 try-catch로 잡아 error 레벨로 로깅.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixvista-any-potree-agent status:error "PotreeService::handlingMessageErrors"
Time: 2026-06-03T16:00:00Z to 2026-06-03T18:00:00Z
text
service:cupixvista-any-potree-agent
Time: 2026-06-03T16:00:00Z to 2026-06-03T18:00:00Z

핵심 로그 시퀀스 (KST):

json
{"timestamp": "2026-06-04 02:14:59", "status": "info", "message": "PotreeService::runByMessage | id: 28736"}
json
{"timestamp": "2026-06-04 02:14:59", "status": "info", "message": "PotreeService::runByMessage | state: queued, resource_state: uploaded, potree_state: created"}
json
{"timestamp": "2026-06-04 02:15:00", "status": "info", "message": "PotreeService::runPotreeConvertor | exec - /tmp/PotreeConverter/build/PotreeConverter ./potree_converter_input.json"}
json
{"timestamp": "2026-06-04 02:15:10", "status": "warn", "message": "PotreeService::getApiErrorToDeleteMessage | undefined response - {}"}
json
{"timestamp": "2026-06-04 02:15:10", "status": "error", "message": "PotreeService::handlingMessageErrors | Errors in error handling - {\"response\":{\"statusCode\":403,\"body\":{\"result\":{\"code\":\"ARG10002\",...\"reason\":\"Pointcloud not found\"}}},\"request\":{\"uri\":{\"pathname\":\"/api/v1/pointclouds/28736\"},\"method\":\"PUT\"}}"}
json
{"timestamp": "2026-06-04 02:15:10", "status": "error", "message": "PotreeService::handlingMessageErrors | Error and message object - {\"error\":\"undefined response\",\"sqsMessage\":{\"MessageId\":\"0e4026b6-ead6-41db-b7a2-fdc5bdcefcd3\",\"Attributes\":{\"ApproximateReceiveCount\":\"1\"}}}"}

시퀀스 분석:

  • 처리 시작 시(02:14:59) pointcloud 28736은 존재했음 (state: queued)
  • 약 11초 후(02:15:10) 에러 핸들링 시점에 pointcloud가 이미 삭제됨
  • 초기 에러의 .response가 undefined였으므로 PotreeConverter 자체 실패(비 HTTP 에러)로 추정

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 에러 핸들러에서 updatePotreeState 호출 시 pointcloud가 이미 삭제되어 403 발생 로그에서 PUT /api/v1/pointclouds/28736이 403 반환, Tesla 코드에서 NotFound → 403 매핑 확인 (client_error_controller.rb:27), base_repository.rb:355에서 ARG10002 raise Confirmed
H2 인증 토큰 만료로 인한 403 HTTP 403 응답 에러 body에 ARG10002/Pointcloud not found 명시, 인증 에러면 401 반환됨 (client_error_controller.rb:23-24) Rejected
H3 권한(permission) 부족으로 인한 403 Tesla가 permission denied도 403으로 반환 (client_error_controller.rb:26) Permission denied는 code PERM10000으로 반환됨 (base_repository.rb:360), 실제 code는 ARG10002 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 파일: packages/cupix-tesla-potree-agent/src/potree-service.ts:517
  • updatePotreeState 호출 전에 pointcloud 존재 여부를 확인하거나, 해당 호출 실패 시 에러를 무시(warn 레벨로 로깅)하도록 변경. 에러 핸들러 내부에서 발생하는 이차 에러는 시스템에 치명적이지 않으므로, 403/404 응답 시 warn으로 로깅하고 진행하는 것이 적절함.

단기 개선 (1주 이내)#

  • handlingMessageErrorsupdatePotreeState 호출을 별도 try-catch로 감싸되, 현재 구조에서는 이미 try-catch가 있으므로 catch 블록의 로그 레벨을 errorwarn으로 변경. 삭제된 리소스에 대한 상태 업데이트 실패는 정상적 운영 시나리오이므로 error가 아닌 warn이 적절.

장기 개선 (재발 방지)#

  • SQS 메시지 처리 시 pointcloud 존재 여부를 사전 검증하는 패턴 도입: createCPPointcloudByPointcloudId(line 238-256)처럼 ARG10002에 대한 graceful handling을 updatePotreeState에도 적용.
  • 전체 agent 서비스에서 에러 핸들러 내 API 호출 실패를 일관되게 warn 레벨로 처리하는 공통 유틸리티 도입 검토.

Monitoring#

  • updatePotreeState 실패 빈도 모니터링 쿼리:
text
service:cupixvista-any-potree-agent "Errors in error handling" status:error
  • 로그 레벨 변경 후 warn 빈도 추적:
text
service:cupixvista-any-potree-agent "Errors in error handling" status:warn

Risk Assessment#

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