ES /docs

[CupixAerialMap] postprocess fail - error:({"name":"Pix4dError","code":"AMB725","reason":"Mesh Outpu

RCA: [CupixAerialMap] postprocess fail — Pix4dError AMB725 (Mesh Output Data Not Found)

Overview#

What Happened#

2026-07-22 19:00 KST에 production aerial-map-service (us-west-2)의 postprocess 단계에서 aerial map id 230 이 Pix4dError / code:AMB725 / reason:Mesh Output Data Not Found 로 종료됐다. 원인은 reprocessing_option.type === 'postprocess' 로 호출된 재실행이 이전 Pix4D 처리에서 생성되지 않은 mesh output 을 요구했기 때문이다. Postprocess 는 Pix4D 를 다시 돌리지 않고 이미 존재하는 output 만 소비하므로, 존재하지 않는 output kind 를 요청하면 즉시 실패한다. 이후 19:08 KST 에 orthomosaic 만 요청하는 재실행이 트리거되어 19:27 KST 에 성공으로 종료됐다.

Quick Facts#

Field Value
exception.class Pix4dError (via AerialMapError)
exception.code AMB725
exception.message Mesh Output Data Not Found
top_frame src/postprocess/index.ts:126
env production, us-west-2, tenant cupix
aerial_map.id 230
pix4d_project_id 2588304

Affected Teams#

Team / Domain Error Count Impact
aerial-map-service (postprocess) 1 aerial map id 230 의 mesh output 을 요구한 reprocess 요청 1건 실패. 후속으로 outputs 를 축소한 재실행이 성공하여 사용자 관점 최종 결과는 복구됨.

Timeline#

  1. 2026-07-22 18:50 KST[CupixAerialMap] aerial map 230 reinvoke for mesh,dsm,pointcloud,orthomosaic (postprocess 재실행 트리거)
  2. 2026-07-22 18:51 KSTgetProcessingOutput 결과 로그: pix4d_log, report_pdf, report_json, orthomosaic, orthomosaic_gdalinfo, flight_path, thumbnail, scene_ref_frame 만 존재. mesh / dsm / pointcloud 키 없음.
  3. 2026-07-22 19:00 KSTpostprocess fail - error:({"name":"Pix4dError","code":"AMB725","reason":"Mesh Output Data Not Found"}) (cluster first/last_seen)
  4. 2026-07-22 19:08 KST[CupixAerialMap] aerial map 230 reinvoke for orthomosaic (축소된 outputs 로 재실행)
  5. 2026-07-22 19:27 KST[CupixAerialMap] postprocess success / postprocess time: 1121901ms

Error Log#

[Datadog Logs](https://app.datadoghq.com/logs?query=service%3Aaerial-map-service%20status%3Aerror%20%40environment%3Aproduction%20%22%5BCupixAerialMap%5D%20postprocess%20fail%20-%20error%3A(%7Bname%3APix4dError%2Ccode%3AAMB725%2Creason%3AMesh%20Outpu%22&from_ts=1784710800000&to_ts=1784718060000&live=false)

text
[CupixAerialMap] postprocess fail - error:({"name":"Pix4dError","code":"AMB725","reason":"Mesh Output Data Not Found"}) / message:(Mesh Output Data Not Found)

Impact#

  • Service: aerial-map-service
  • 발생 횟수: 1
  • 최초 발생: 2026-07-22 19:00 KST
  • 최근 발생: 2026-07-22 19:00 KST
  • Blast radius: aerial map id 230 의 postprocess 재실행 1건. 동일 fingerprint 로 지난 14일간 다른 발생 없음 (Datadog service:aerial-map-service Pix4dError = 1건). 후속 축소 재실행으로 최종적으로 복구됨.

Root Cause Summary#

postprocess 는 Pix4D processing 을 재실행하지 않고 pix4dApi.getProcessingOutput(projectId) 로 이미 존재하는 output metadata 만 조회하여 소비한다. aerialMap.reprocessing_option.type === 'postprocess' 경로에서 reprocessing_option.outputsmesh 가 포함됐지만 이전 Pix4D 처리에서 mesh(result_type: '3d_mesh_obj_zip') 가 생성되지 않아 pix4dOutput['mesh'] 가 undefined 였고, src/postprocess/index.ts:126if (!pix4dOutput['mesh']) throw new AerialMapError('AMB725') 가치기(guard)가 즉시 예외를 던지며 프로세스가 exit code 1 로 종료됐다. 즉 원인은 Pix4D 나 postprocess 로직의 결함이 아니라, 재실행 트리거가 이전 처리에 없었던 output kind 를 요청한 입력 불일치다.

Technical Analysis#

Code Path#

  • Entry point: src/postprocess/index.ts:36 app(input) — postprocess Fargate 컨테이너 진입
  • Outputs 결정: src/postprocess/index.ts:43-48reprocessing_option.type === 'postprocess' 이면 reprocessing_option.outputs 사용, 아니면 processing_option.outputs
  • Pix4D output metadata 조회: src/postprocess/index.ts:70 pix4dApi.getProcessingOutput(projectId)
  • Failure point: src/postprocess/index.ts:126mesh output 존재 확인 실패 시 AMB725 throw
  • Catch/report: src/postprocess/index.ts:148-157AerialMapError 인 경우 cupixApi.saveError(aerialMapId, error.code, error.reason) 후 재-throw, 최상단에서 process.exit(1)
applications/aerial-map-service/src/code/src/postprocess/index.ts:41-48typescript
  let outputs: TOutput[] = [];

  if (aerialMap['reprocessing_option']?.type === 'postprocess') {
    outputs = aerialMap['reprocessing_option'].outputs || [];
    logger.info(`[CupixAerialMap] aerial map ${aerialMapId} reinvoke for ${outputs}`);
  } else {
    outputs = aerialMap['processing_option'].outputs || [];
  }
applications/aerial-map-service/src/code/src/postprocess/index.ts:107-129typescript
    outputs.forEach((output) => {
      if (output === 'orthomosaic') {
        if (!pix4dOutput['orthomosaic'] || !pix4dOutput['orthomosaic_gdalinfo']) throw new AerialMapError('AMB722');
        tasks.push(
          processTiff('orthomosaic', cupixApi, aerialMapId, pix4dOutput, downloadCredential, pix4dApi, projectId),
        );
      }

      if (output === 'dsm') {
        if (!pix4dOutput['dsm'] || !pix4dOutput['dsm_gdalinfo']) throw new AerialMapError('AMB723');
        tasks.push(processTiff('dsm', cupixApi, aerialMapId, pix4dOutput, downloadCredential, pix4dApi, projectId));
      }

      if (output === 'pointcloud') {
        if (!pix4dOutput['pointcloud']) throw new AerialMapError('AMB724');
        tasks.push(processPointcloud(cupixApi, aerialMapId, pix4dOutput, downloadCredential));
      }

      if (output === 'mesh') {
        if (!pix4dOutput['mesh']) throw new AerialMapError('AMB725');
        tasks.push(processMesh(cupixApi, aerialMapId, pix4dOutput, downloadCredential));
      }
    });
applications/aerial-map-service/src/code/src/common/api.ts:368-381typescript
        // textured mesh
        if (
          output['availability'] === 'done' &&
          output['result_type'] === '3d_mesh_obj_zip' &&
          output['output_type'] === '3d_mesh_obj_zip' &&
          output['s3_key']?.endsWith('.zip')
        ) {
          source['mesh'] = {
            region: output['s3_region'],
            bucket: output['s3_bucket'],
            key: output['s3_key'],
            extension: path.extname(output['s3_key']).toLowerCase() || '.zip',
          };
        }

기대 동작: reprocess 트리거 측이 이전 Pix4D 처리에서 실제로 생성된 output kind subset 만 reprocessing_option.outputs 로 요청해야 한다. 실제 동작: mesh 를 포함해 요청되었지만 Pix4D 응답에 3d_mesh_obj_zip output 이 존재하지 않아 (18:51 로그의 postprocess - pix4d output:(...) 페이로드에 mesh 키가 없음) 가치기가 실패했다.

pix4d-process-check 는 초기 처리 파이프라인에서만 mesh 존재 여부를 게이팅하며, 오직 aerialMap['processing_option'].outputs 만 참조한다 — reprocess-only 재실행 경로에는 이 검증이 걸리지 않는다:

applications/aerial-map-service/src/code/src/pix4d-process-check/index.ts:17-51typescript
  const { outputs } = aerialMap['processing_option'];
  ...
      const processingOutputKind: Record<string, IOutputKey[]> = {
        orthomosaic: ['orthomosaic', 'orthomosaic_gdalinfo'],
        dsm: ['dsm', 'dsm_gdalinfo'],
        pointcloud: ['pointcloud'],
        mesh: ['mesh'],
      };
      ...
      const hasProcessingOutputData = outputs.every((output) => {
        const processingOutputData = processingOutputKind[output];
        return processingOutputData ? processingOutputData.every((key) => pix4dOutput[key]) : true;
      });

      result = hasCommonOutputData && hasProcessingOutputData ? 'success' : 'processing';

Navigator 는 reprocessing_option.type === 'postprocess' 요청을 preprocess/process 단계를 건너뛰고 곧바로 postprocess 로 라우팅하므로 Pix4D 는 다시 돌아가지 않는다:

applications/aerial-map-service/src/code/src/navigate/index.ts:8-15typescript
const getRoute = (event: INavigateInput): TRoute => {
  if (event.external_source) return 'import';
  const kind = event.aerial_map?.['reprocessing_option']?.type;
  if (kind === 'preprocess' || kind === 'process') return 'preprocess';
  if (kind === 'postprocess') return 'postprocess';
  if (kind === 'data-collect') return 'data-collect';
  return 'preprocess';
};

Log Evidence#

사용한 Datadog 쿼리:

text
service:aerial-map-service AMB725
text
service:aerial-map-service @aerial_map.id:230
text
service:aerial-map-service reinvoke

핵심 로그 항목 (aerial_map.id: 230, 2026-07-22 KST):

text
18:50:57  info   [CupixAerialMap] aerial map 230 reinvoke for mesh,dsm,pointcloud,orthomosaic
18:51:01  info   [CupixAerialMap] postprocess - pix4d output:({"pix4d_log":{...},"report_pdf":{...},"report_json":{...},"orthomosaic":{...},"orthomosaic_gdalinfo":{...},"flight_path":{...},"thumbnail":{...},"scene_ref_frame":{...}})
19:00:20  info   [CupixAerialMap] pix4d aerial photo post-processed
19:00:21  error  [CupixAerialMap] postprocess fail - error:({"name":"Pix4dError","code":"AMB725","reason":"Mesh Output Data Not Found"}) / message:(Mesh Output Data Not Found)
19:00:22  info   [CupixAerialMap] pix4d thumbnail output post-processed
19:00:23  info   [CupixAerialMap] pix4d log output post-processed
19:08:52  info   [CupixAerialMap] aerial map 230 reinvoke for orthomosaic
19:18:26  info   [CupixAerialMap] pix4d aerial photo post-processed
19:27:34  info   [CupixAerialMap] postprocess success
19:27:34  info   [CupixAerialMap] postprocess time: 1121901ms

18:51:01 UTC 의 Pix4D output payload 에서 mesh (와 dsm, pointcloud) 키가 존재하지 않음을 육안으로 확인:

json
{
  "pix4d_log":       { "key": ".../logs/cupix-production-wgyates-230-1774410027063_processing_task.log" },
  "report_pdf":      { "key": ".../reports/cupix-production-wgyates-230-1774410027063_report.pdf" },
  "report_json":     { "key": ".../reports/cupix-production-wgyates-230-1774410027063_report.json" },
  "orthomosaic":     { "key": ".../reconstructions/cupix-production-wgyates-230-1774410027063_ortho.tiff" },
  "orthomosaic_gdalinfo": { "key": ".../post_processing/ortho/Ortho_gdalinfo.json" },
  "flight_path":     { "key": ".../calibrated_camera_parameters.json" },
  "thumbnail":       { "key": ".../thumb/project_thumb.jpg" },
  "scene_ref_frame": { "key": ".../scene_reference_frame.json" }
}

즉, mesh 키가 페이로드에 없어서 postprocess/index.ts:126 의 undefined 체크가 성립.

Container attributes:

text
label:         postprocess
log.file.path: /tmp/workspace/postprocess-json-2026.07.22.log
host:          ip-10-1-167-201.us-west-2.compute.internal
aerial_map.id: 230

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 reprocessing_option.outputs 에 mesh 가 포함되었으나 이전 Pix4D 처리에 mesh output 이 없어 postprocess 진입 시 pix4dOutput['mesh'] 가 undefined → AMB725 aerial map 230 reinvoke for mesh,dsm,pointcloud,orthomosaic (18:50) 로그와, 이어지는 pix4d output 페이로드에 mesh/dsm/pointcloud 키 부재 (18:51); src/postprocess/index.ts:126 가치기 정확히 일치; 후속 reinvoke for orthomosaic 만으로 성공(19:27) Confirmed
H2 Pix4D 처리는 mesh 를 생성했으나 getProcessingOutput 응답 파싱 조건(result_type === '3d_mesh_obj_zip', s3_key.endsWith('.zip'))이 실제 응답과 미스매치되어 mesh 키가 세팅되지 않았다 api.ts:368-381 은 특정 조건에서만 source['mesh'] 를 채운다 (조건 실패 시 조용히 누락) 동일 project 에 대한 19:08 재실행에서도 payload 에 mesh 키 없음. Pix4D 가 mesh 를 만들었다면 축소 재실행 시에도 나타나야 함. 19:08 요청 자체가 mesh 를 제외했다는 사실은 운영 측에서도 mesh 부재를 이미 인지했음을 시사 Rejected
H3 Pix4D 측 일시적 API 장애로 outputs 응답이 비어옴 응답에 pix4d_log, orthomosaic 등 8개 output 이 정상 포함됨. 부분적으로만 없는(missing) 상태이므로 전면 장애 아님 Rejected
H4 postprocess 컨테이너 재시작/타이밍 race — Pix4D 처리 종료 직전에 postprocess 가 조회하여 mesh 만 lag 됨 Fargate 프로세스이므로 이론적으로 가능 Reprocess 경로(type === 'postprocess')는 pix4d-process-check 게이트를 우회한다 (navigate/index.ts:12). 또한 축소된 19:08 재실행에서도 mesh 는 여전히 없음. 시간 race 라면 결국 나타나야 함 Rejected
H5 광범위한 아웃라이어 클러스터 (여러 aerial_map 에서 동일 오류) 지난 14일 service:aerial-map-service Pix4dError 쿼리에 1건만 매칭 (한 번의 aerial_map 230 재실행) Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • reprocess 트리거 측 검증: reprocessing_option.type === 'postprocess' 를 사용하는 호출 지점(외부 orchestrator / Rails 서버 / 사용자 CLI 등, aerial-map-service 밖)에서 요청 outputs 를 이전 Pix4D 처리에서 실제 생성된 output kind subset 으로 제한해야 한다. 이 aerial-map-service 리포지토리 안에서는 postprocess 실행 자체가 이미 정답 데이터(Pix4D outputs list)를 손에 쥐고 있으므로, src/postprocess/index.ts:70 이후 outputs 필터링을 한 겹 두어 없는 output 은 skip + warn 하도록 방향을 잡을 수 있다. 구현 방향(코드 스니펫 없음):
    • outputs.forEach 앞에서 outputs.filter((o) => pix4dOutput[o] != null) 로 축소하고, 제거된 항목을 logger.warn 로 남겨 관측성 유지.
    • 또는 mesh/dsm/pointcloud 미존재를 error 대신 warn 으로 downgrade 하고, orthomosaic 처럼 사용자 관점 필수 output 만 실패로 유지.
    • 두 접근 모두 "재실행 요청자의 실수/누락" 에 대해 사용자 경험을 보호. 단, 요청된 output 이 하나도 없다면 여전히 실패하도록 최소 안전장치 유지.

단기 개선 (1주 이내)#

  • reprocess 게이팅 대칭화: pix4d-process-check/index.ts:17 에서 aerialMap['processing_option'].outputs 만 참조하는 부분을, reprocess 경로에도 동일한 존재 확인 로직이 적용되도록 리팩터. 최소한 postprocess entrypoint 초입에 "요청된 outputs vs Pix4D 실제 outputs" diff 를 명시적으로 로깅해 사후 조사 시간을 줄인다.
  • 에러 코드 세분화: AMB722–AMB725 는 모두 동일한 "요청한 output kind 가 Pix4D 응답에 없음" 원인을 공유하므로, 로그에서 label: reprocess-outputs-missing 태그를 추가하거나 예외 payload 에 요청/실제 outputs list 를 함께 남겨 재발 시 root cause 판별을 가속.

장기 개선 (재발 방지)#

  • Contract 명세화: aerial-map-service 를 호출하는 상위 시스템(Rails AerialMap 모델 / reprocess 트리거 UI)에서 reprocessing_option.outputs 를 이전 처리 결과와 교차 검증하는 서버 측 validation 도입. 클라이언트가 "mesh 재생성" 을 원한다면 type: 'process' (또는 'preprocess') 로 다시 돌리도록 라우팅하고, type: 'postprocess' 는 "이미 존재하는 output 재-업로드" 에 국한.
  • integration test: reprocess-postprocess 경로에 대해 "요청 outputs 가 Pix4D outputs 의 superset 인 경우" 시나리오를 스텁하여 회귀 방지.

Monitoring#

추가할 Datadog 알림:

text
service:aerial-map-service status:error @attributes.label:postprocess "AMB72"
text
service:aerial-map-service status:error "postprocess fail" ("AMB722" OR "AMB723" OR "AMB724" OR "AMB725")

Reprocess 트리거와 실패 상관관계 확인용 (같은 aerial_map.id 로 reinvoke 후 곧 실패):

text
service:aerial-map-service ("reinvoke for" OR "postprocess fail")

Risk Assessment#

  • Risk level: low — 지난 14일 1건 발생, 사용자 관점 축소 재실행으로 즉시 복구됨. 다만 재실행 UX 가 계속 유효하다면 유사 오류 재발 가능.
  • 예상 복잡도: standard — postprocess entrypoint 내 filter/downgrade 는 국소 변경. contract-level 개선은 상위 시스템 협조 필요.