ES /docs

Failed to execute densemapper for capture ID: 33815 - error: {}

RCA: Failed to execute densemapper for capture ID: 33815 - error: {}

Overview#

What Happened#

2026-04-20 23:13 UTC에 cupixworks-capture-3dreconstruction-instance 서비스에서 capture 33815의 densemapper 실행이 실패했다. densemapper 네이티브 바이너리가 약 67분간 실행된 후 exit code 1로 종료되었으며, 에러 객체 직렬화 버그(JSON.stringify(new Error(...)){})로 인해 실제 에러 메시지가 로그에서 손실되었다.

Quick Facts#

Field Value
exception.message Failed to execute densemapper for capture ID: 33815 - error: {}
top_frame app.cjs:8318 (ThreeDReconstruction.runThreeDReconstruction)
env production, eu-central-1

Timeline#

  1. 22:06:31 UTC — Job 90769 초기화, capture 33815 로드 시작 (video 1개, cluster 1개)
  2. 22:06:48 UTC — densemapper 프로세스 실행 시작 (domain: innovo, region: us-west-2)
  3. 23:13:59.288 UTC — densemapper child process가 exit code 1로 종료
  4. 23:13:59.289 UTCThreeDReconstruction::runThreeDReconstruction catch 블록에서 에러 포착, JSON.stringify(error){}
  5. 23:13:59.290 UTC — "Failed to execute densemapper" 에러 로그 기록
  6. 23:13:59.696 UTC — 서비스 10초 후 강제 종료 예약

Error Log#

Datadog Logs

text
Failed to execute densemapper for capture ID: 33815 - error: {}

Impact#

  • Service: cupixworks-capture-3dreconstruction-instance
  • Team: innovo
  • 발생 횟수: 1
  • 최초 발생: 2026-04-20T23:13:59.290Z
  • 최근 발생: 2026-04-20T23:13:59.290Z

동일 시간대에 capture 683809 (lemartec 팀, us-west-2)에서도 같은 패턴의 densemapper 실패가 발생하여 총 2건의 유사 에러가 확인되었다. 3D reconstruction 처리가 실패하면 해당 capture의 point cloud 생성이 완료되지 않아 사용자가 3D 결과물을 볼 수 없다.

Root Cause Summary#

densemapper 네이티브 바이너리가 약 67분간 실행 후 exit code 1로 비정상 종료되었다. 실제 crash 원인은 densemapper 바이너리 내부에 있으나, Node.js 측 에러 전파 과정에서 JSON.stringify(error)로 Error 객체를 직렬화할 때 빈 객체 {}가 출력되어 진단 정보가 완전히 손실되었다. Error 객체의 message, stack, name 프로퍼티는 non-enumerable이므로 JSON.stringify()로 직렬화하면 빈 객체가 반환된다. 이 직렬화 버그가 근본 원인 파악을 불가능하게 만든 직접적인 원인이다.

Technical Analysis#

Code Path#

  • Entry point: three-d-reconstruction-service.ts:57 (ThreeDReconstruction.init())
  • 실행 흐름: init()run()runThreeDReconstruction()ThreeDReconstructorManager.execute()ChildProcessManager.execute() → fork된 child process에서 ThreeDReconstructionProcess.execute()child_process.spawn()으로 densemapper 셸 스크립트 실행

1단계: densemapper child process 실행 및 실패

ThreeDReconstructionProcess는 densemapper 바이너리를 셸 명령으로 spawn한다. 프로세스가 exit code 1로 종료되면 Error 객체를 생성하여 reject한다:

packages/cupix-capture-3d-reconstruction-agent/src/process/three-d-reconstruction.process.ts:59-68typescript
_spawn.on('exit', (code, signal) => {
    if (code === null) {
        const errorMessage = `Process terminated unexpectedly by signal: ${signal}`;
        this.log(`ThreeDReconstructionProcess::execute | ${errorMessage}`);
        reject(new Error(errorMessage));
    } else if (code !== 0) {
        const errorMessage = `Process terminated with non-zero code: ${code}`;
        this.log(`ThreeDReconstructionProcess::execute | ${errorMessage}`);
        reject(new Error(errorMessage));
    } else {
        this.log('ThreeDReconstructionProcess::execute | 3D Reconstruction processing completed successfully');
        resolve({ success: true });
    }
});

2단계: BaseProcess에서 에러 메시지 추출 후 IPC 전송

BaseProcess.sendError()error.message만 추출하여 parent process에 전송한다:

packages/base/src/process/base.process.ts:56-59typescript
protected sendError(id: string, error: unknown): void {
    const errorMessage = error instanceof Error ? error.message : String(error);
    this.sendResponse(id, '', false, undefined, errorMessage);
}

3단계: ChildProcessManager에서 새 Error 객체 생성

parent의 ChildProcessManager.handleMessage()는 IPC로 받은 response.error 문자열로 새로운 Error 객체를 생성한다:

packages/base/src/manager/child-process.manager.ts:179-183typescript
if (response.success) {
    pending.resolve(response.data);
} else {
    pending.reject(new Error(response.error || 'Unknown error'));
}

4단계: JSON.stringify(error){} (Failure point)

runThreeDReconstruction의 catch 블록에서 JSON.stringify(error)를 사용하여 에러를 로깅한다. JavaScript의 Error 객체는 message, stack, name 프로퍼티가 non-enumerable이므로 JSON.stringify(){}를 반환한다:

packages/cupix-capture-3d-reconstruction-agent/src/three-d-reconstruction-service.ts:321-324typescript
} catch (error: any) {
    logger.error(`ThreeDReconstruction::runThreeDReconstruction | Capture ID: ${cpCapture.id} | Error: %s`, JSON.stringify(error));
    this.jobManager.setErrorCode(ErrorCode.Densemapper.Execute);
    throw new Error(`Failed to execute densemapper for capture ID: ${cpCapture.id} - error: ${JSON.stringify(error)}`);
}

기대 동작: error.message 값인 "Process terminated with non-zero code: 1"이 로그에 기록되어야 한다. 실제 동작: JSON.stringify(error)"{}" 출력, 에러 메시지 손실.

Log Evidence#

Datadog 검색 쿼리:

text
service:cupixworks-capture-3dreconstruction-instance status:error @environment:production "33815"

capture 33815의 전체 타임라인 (13개 로그):

text
22:06:31.276 [info]  ThreeDReconstruction::init
22:06:31.276 [info]  ThreeDReconstruction::authenticate | begin
22:06:31.332 [info]  CupixAuth::setSession | session_id: 5e55e307...
22:06:31.333 [info]  ThreeDReconstruction::authenticate | end
22:06:31.333 [info]  ThreeDReconstruction::run | begin
22:06:31.334 [info]  JobManager::loadJob | begin - job id: 90769
22:06:31.382 [info]  JobManager::loadJob | end - job id: 90769
22:06:31.783 [info]  ThreeDReconstruction::loadVideos | video count: 1
22:06:31.841 [info]  ThreeDReconstruction::loadClusters | cluster count: 1
22:06:48.167 [info]  ThreeDReconstruction::runThreeDReconstruction environments | domain: innovo, envName: production, launchMode: CUPIXWORKS, region: us-west-2, userEmail: undefined
23:13:59.288 [error] Process terminated with non-zero code: 1
23:13:59.289 [error] ThreeDReconstruction::runThreeDReconstruction | Capture ID: 33815 | Error: {}
23:13:59.290 [error] Failed to execute densemapper for capture ID: 33815 - error: {}

22:06:48부터 23:13:59까지 약 67분간의 공백 — densemapper 바이너리가 실행 중이었으며 중간 로그 없음. 바이너리 stdout/stderr 출력은 child process의 this.log() 호출로 전달되지만, 해당 시간대에 로그가 없어 densemapper가 silent하게 크래시한 것으로 확인.

동일 패턴의 densemapper 실패 (다른 capture):

text
service:cupixworks-capture-3dreconstruction-instance status:error "densemapper"
text
21:14:10.349 [error] Failed to execute densemapper for capture ID: 683809 - error: {}  (lemartec, us-west-2, job 1024963)
23:13:59.290 [error] Failed to execute densemapper for capture ID: 33815 - error: {}   (innovo, eu-central-1, job 90769)

두 건 모두 동일한 stack trace (ThreeDReconstruction.runThreeDReconstruction at app.cjs:8318)와 동일한 빈 에러 객체 패턴.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 densemapper 바이너리 내부 크래시 (메모리, 입력 데이터 문제 등) 67분 실행 후 exit code 1 종료, 다른 capture(683809)에서도 동일 패턴 발생 densemapper 바이너리 소스 접근 불가, stderr 출력 없음으로 구체적 원인 특정 불가 Confirmed
H2 JSON.stringify(error) 직렬화 버그로 에러 정보 손실 three-d-reconstruction-service.ts:322에서 JSON.stringify(error) 사용 확인, JS Error 객체의 non-enumerable 특성상 {} 출력 Confirmed
H3 네트워크/API 타임아웃으로 인한 실패 같은 시간대 capture 33814에서 ETIMEDOUT 발생 densemapper는 로컬 바이너리 실행이므로 네트워크 의존성 낮음, exit code 1은 프로세스 자체 실패를 의미 Rejected
H4 리전 미스매치 (eu-central-1 인스턴스에서 us-west-2 설정) 로그에서 region: us-west-2 확인, 실제 인스턴스는 eu-central-1 region은 환경 변수(AWS_REGION)에서 읽는 값으로 API endpoint 설정에 사용됨, densemapper 바이너리 실행 자체에는 영향 없을 가능성 높음 Inconclusive

Fix Recommendation#

즉시 조치 (Critical)#

  • three-d-reconstruction-service.ts:322 에서 JSON.stringify(error) 대신 error instanceof Error ? error.message : String(error) 또는 error?.message ?? JSON.stringify(error) 패턴으로 변경하여 에러 메시지가 정상적으로 로깅되도록 수정
  • 동일 파일 324번 줄의 throw 문에서도 같은 패턴 적용

단기 개선 (1주 이내)#

  • ThreeDReconstructionProcess.execute() (three-d-reconstruction.process.ts:59-68)에서 densemapper 프로세스의 stderr 출력을 캡처하여 에러 메시지에 포함하도록 개선. 현재 stderr는 this.log()로 전달되지만 exit 이벤트의 에러 메시지에는 포함되지 않음
  • densemapper 프로세스 stdout/stderr를 별도 버퍼에 수집하여 exit code가 0이 아닐 때 마지막 N줄을 에러 메시지에 첨부하는 방식 검토

장기 개선 (재발 방지)#

  • 코드베이스 전체에서 JSON.stringify(error) 패턴 사용처를 검색하여 동일한 직렬화 버그가 있는 곳을 일괄 수정
  • 공통 에러 직렬화 유틸리티 함수를 @agents/utils에 추가하여 Error 객체를 안전하게 문자열화하는 표준 패턴 제공

Monitoring#

  • densemapper 실패율 모니터링:
text
service:cupixworks-capture-3dreconstruction-instance status:error "Failed to execute densemapper"
  • 빈 에러 객체 패턴 감지:
text
service:cupixworks-capture-3dreconstruction-instance "error: {}"

Risk Assessment#

  • Risk level: medium
  • 예상 복잡도: trivial (에러 직렬화 수정은 1-2줄 변경, 단 densemapper 바이너리 자체의 크래시 원인은 별도 조사 필요)