CPCapture video key mismatch — preprocessing vs decoding results
RCA: FinalizationService::start | end - empty panos and frame
Error Log#
FinalizationService::start | end - empty panos and frame
Impact#
- Service:
cupixworks-capture-postprocessor-agent - Team: pclconstruction
- 발생 횟수: 1
- 최초 발생: 2026-04-11T21:24:14.291Z
- 최근 발생: 2026-04-11T21:24:14.291Z
- Capture ID: 679407
- Job ID: 1009575
- User: enortey@pcl.com (id: 43062)
Root Cause Summary#
Preprocessor 단계에서 생성한 results.json의 video key(g27UrlX5i9, p1ZT3sNghA)와 SKAT 디코딩 단계에서 생성한 decoding_results.json의 video key(rx7onuC_Y, xr6qNT0FbG)가 일치하지 않아, CPCapture::updateVideosfromMeta에서 디코딩된 2,409개의 pano를 기존 CPVideo 객체에 매칭하지 못했다. 결과적으로 cpCapture.cpPanos가 비어 있는 상태에서 FinalizationService::start가 호출되어 panoCount === 0 && videoFrameCount === 0 조건에 의해 즉시 에러가 발생했다. 이 capture의 reconstruction 품질도 좋지 않았으며(alignment drift, outlier ratio 0.49), 최종 error code는 AGT1203으로 기록되었다.
Technical Analysis#
Code Path#
- Entry point:
postprocessor-service.ts:64—PostprocessorService.run()메서드에서 SQS 메시지를 받아 처리 시작 postprocessor-service.ts:80—createCPCaptureByJob(srvJob)로 capture 679407 로드postprocessor-service.ts:89—fileSystemManager.loadCPCaptureModel(cpCapture)— preprocessor의results.json로드
// file-system.manager.ts:103-136 (loadCPCaptureModel)
loadCPCaptureModel = (cpCapture: CPCapture): Promise<void> => new Promise((resolve, reject) => {
// ...
const cpCaptureMeta: CPCaptureMeta = JSON.parse(fs.readFileSync(localPreProcessorFilePath).toString('utf8'));
if (cpCapture.fromMeta(cpCaptureMeta)) {
// fromMeta에서 cpVideos 2개 생성 (key: g27UrlX5i9, p1ZT3sNghA)
return resolve();
}
});
postprocessor-service.ts:90—fileSystemManager.loadCPCaptureModelFromVideoDecodingResult(cpCapture)— SKAT decoding 결과 로드. 여기서 key mismatch 발생.
// file-system.manager.ts:138-176 (loadCPCaptureModelFromVideoDecodingResult)
// decoding_results.json의 videosMeta를 기존 cpCapture에 merge
if (cpCapture.updateVideosfromMeta(cpCaptureMeta.videosMeta)) {
// updateVideosfromMeta는 key로 매칭 — key가 다르면 pano가 추가되지 않음
return resolve();
}
// cpcapture.ts:672-707 (updateVideosfromMeta) — key 기반 매칭 로직
updateVideosfromMeta = (meta: Array<CPVideoMeta>): boolean => {
for (let index = 0; index < meta.length; index++) {
const cpVideoMeta = meta[index];
const cpVideo = this.getVideoFromKey(cpVideoMeta.key); // key로 검색
if (cpVideo) {
// key 일치 시 pano 추가
for (let pindex = 0; pindex < cpVideoMeta.panosMeta.length; pindex++) {
// ...
_cpPano.setFromVideo(cpVideo, _panoMeta.localFilePath, _panoMeta.frameIndex);
}
} else {
logger.warn('CPCapture::updateVideosfromMeta | cpVideo not found - key: %s', cpVideoMeta.key);
}
}
return true; // key mismatch여도 true 반환 — 에러가 전파되지 않음
};
-
postprocessor-service.ts:99—fileSystemManager.loadSkatResults(cpCapture)— SKAT alignment 결과 로드, 1개 cluster에 818 panos 할당 -
postprocessor-service.ts:109-116—isRefinementRequired(cpCapture)체크 후FinalizationService::start호출
// postprocessor-service.ts:109-116
if (this.isRefinementRequired(cpCapture)) {
// ...
} else {
const _finalizationService = new FinalizationService(this.cupixApi, this.jobManager, this.transferManager);
await _finalizationService.start(cpCapture); // cpPanos가 비어있는 상태로 호출
}
- Failure point:
finalization-service.ts:29-32— panoCount와 videoFrameCount 모두 0이므로 에러 throw
// finalization-service.ts:25-33 (start)
start = async (cpCapture: CPCapture) => {
logger.debug('FinalizationService::start | begin');
const panoCount = cpCapture.cpPanos.length; // 0
const videoFrameCount = cpCapture.cpVideos.reduce( // 0 (video에 pano 미연결)
(count: number, current: CPVideo) => count + current.cpPanos.length, 0);
if (panoCount === 0 && videoFrameCount === 0) {
logger.error('FinalizationService::start | end - empty panos and frame');
this._jobManager.setErrorCode(ErrorCode.Agent.EmptyPanosInScenemapperResults);
throw new Error('empty panos and frame');
}
};
기대 동작: Preprocessor와 SKAT decoding이 동일한 video key를 사용하여, updateVideosfromMeta에서 디코딩된 pano 프레임이 올바르게 CPVideo에 연결되고, 이후 fromSkatResults에서 cluster pano가 해당 프레임과 매칭된다.
실제 동작: Preprocessor의 video key(g27UrlX5i9, p1ZT3sNghA)와 decoding result의 video key(rx7onuC_Y, xr6qNT0FbG)가 완전히 불일치하여 매칭 실패. updateVideosfromMeta가 경고 로그만 남기고 true를 반환하므로, postprocessor 파이프라인이 이 문제를 인지하지 못하고 계속 진행하다가 finalization 단계에서 실패한다.
Log Evidence#
Datadog에서 에러 로그 확인에 사용한 쿼리:
service:cupixworks-capture-postprocessor-agent "empty panos and frame"
From: 2026-04-11T21:23:00Z To: 2026-04-11T21:25:00Z
에러 로그 원문 (raw JSON):
{
"timestamp": "2026-04-11T21:24:14.291Z",
"status": "error",
"message": "FinalizationService::start | end - empty panos and frame",
"attributes": {
"capture": { "id": 679407 },
"job": { "id": 1009575 },
"team": { "domain": "pclconstruction", "id": 739 },
"user": { "id": 43062, "email": "enortey@pcl.com" },
"session": { "id": "ff40611a6dff8df4e1dc27e05a7d6cfeeb2f49e0" }
}
}
스택 트레이스:
Error: empty panos and frame
at FinalizationService.start (/tmp/agent/dist/app.cjs:9049:15)
at PostprocessorService.run (/tmp/agent/dist/app.cjs:9625:38)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async BaseService2.runByMessage (/tmp/agent/dist/app.cjs:5997:15)
Kibana debug 로그에서 확인된 video key mismatch:
[21:18:59.448] CPVideo::fromMeta | key: g27UrlX5i9, version: 1
[21:18:59.449] CPVideo::fromMeta | key: p1ZT3sNghA, version: 1
[21:18:59.484] CPCapture::updateVideosfromMeta | cpVideoMeta key: rx7onuC_Y, panoMeta size: 2409
[21:18:59.484] CPCapture::updateVideosfromMeta | cpVideoMeta key: xr6qNT0FbG, panoMeta size: 0
Preprocessor video key(g27UrlX5i9, p1ZT3sNghA)와 decoding result video key(rx7onuC_Y, xr6qNT0FbG)가 완전히 불일치한다.
Reconstruction 품질 경고:
[21:24:12.700] PostprocessorService::updateSkatInfo | reconstructionErrorCode: AGT5202,
reason: alignment has too much drift. mean landmark count: 523.91, outlier ratio: 0.49
에러 처리 후 최종 상태:
[21:24:14.327] JobManager::updateErrorActionJob | begin - jobId: 1009575, errorCode: AGT1203
[21:24:18.578] JobManager::updateErrorActionJob | end - progress: 75, status: postprocessor
이벤트 타임라인 요약:
| 시각 (UTC) | 이벤트 |
|---|---|
| 21:18:58 | Job 1009575 시작, capture 679407 로드 |
| 21:18:59 | loadCPCaptureModel — video 2개 로드 (key: g27UrlX5i9, p1ZT3sNghA) |
| 21:18:59 | loadCPCaptureModelFromVideoDecodingResult — decoding video key 불일치 (rx7onuC_Y, xr6qNT0FbG), pano 매칭 실패 |
| 21:19:03 | loadSkatResults — cluster 1개, 818 panos (그러나 video frame과 미연결) |
| 21:19:04 | Reconstruction check — status_code: 202, drift 경고 |
| 21:20:16-21:24:09 | File copy (233초) |
| 21:24:09-21:24:14 | Upload alignment, updateSkatInfo, updateEditingInfo |
| 21:24:14.289 | isRefinementRequired → false (refinementState: refined) |
| 21:24:14.291 | FinalizationService::start — cpPanos=0, videoFrameCount=0 → 에러 |
| 21:24:14.327 | Error code AGT1203 설정, job error 상태 업데이트 |
Fix Recommendation#
즉시 조치 (Critical)#
cpcapture.ts:672-707(updateVideosfromMeta): video key 매칭 실패 시true를 반환하는 대신, 매칭에 실패한 video가 pano를 가지고 있었는데 연결하지 못한 경우에는false를 반환하도록 변경. 이렇게 하면loadCPCaptureModelFromVideoDecodingResult에서 reject되어, 빈 pano 상태로 5분 이상 진행한 후 finalization에서 실패하는 것을 방지할 수 있다.file-system.manager.ts:167:updateVideosfromMeta가false를 반환할 때의 에러 처리 경로에서 명확한 에러 메시지와 error code를 설정하여, key mismatch 문제를 즉시 식별할 수 있도록 한다.
단기 개선 (1주 이내)#
updateVideosfromMeta에서 매칭 실패한 video key를 warn이 아닌 error 레벨로 기록하고, 매칭된 video 수와 총 video 수를 함께 로깅하여 문제 진단을 용이하게 한다.- Video key mismatch의 원인을 추적하기 위해, preprocessor와 SKAT master가 동일한 capture에 대해 서로 다른 key를 생성하는 시나리오를 조사한다. 특히 capture가 re-process되거나 branch capture인 경우 key 재생성 로직을 확인해야 한다.
장기 개선 (재발 방지)#
- Preprocessor → SKAT Master → Postprocessor 파이프라인 간 video key 일관성을 보장하는 validation 단계를 추가한다. 각 단계에서 이전 단계의 key를 검증하고, 불일치 시 early fail하도록 한다.
updateVideosfromMeta의 "silent success on mismatch" 패턴을 제거하고, 데이터 무결성 검증 실패 시 명확하게 실패하는 fail-fast 정책을 적용한다.
Monitoring#
cpPanos=0 && videoFrameCount=0발생 빈도 추적:
service:cupixworks-capture-postprocessor-agent "empty panos and frame" status:error
- Video key mismatch 감지를 위한 쿼리 (현재 warn 레벨):
service:cupixworks-capture-postprocessor-agent "cpVideo not found - key"
- Error code AGT1203 발생 빈도:
service:cupixworks-capture-postprocessor-agent "AGT1203"
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard — video key 매칭 실패 시 에러를 전파하는 것은 간단하나, key mismatch의 근본 원인(preprocessor vs SKAT master 간 key 불일치)을 해결하려면 파이프라인 전체를 조사해야 한다.