ES /docs

PanoPostprocessorService::run | end - {"stack":"Error: not found panos

RCA: PanoPostprocessorService::run | not found panos

Error Log#

Datadog Logs

text
PanoPostprocessorService::run | end - {"stack":"Error: not found panos
    at PanoPostprocessorService.run (/tmp/agent/dist/app.cjs:6828:17)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async PanoPostprocessorService.init (/tmp/agent/dist/app.cjs:6784:7)","message":"not found panos"}

Impact#

  • Service: cupixworks-pano-postprocessor-instance
  • Team: ebcinc
  • 발생 횟수: 3
  • 최초 발생: 2026-04-17T17:23:30.879Z
  • 최근 발생: 2026-04-17T17:23:30.879Z

Root Cause Summary#

동일한 capture(ID: 682964)에 대해 pano postprocessor job이 두 번 연속 실행되었다. 1차 실행(17:18:37Z)이 정상 완료되어 모든 pano의 state를 done으로 변경한 후, 2차 실행(17:23:29Z)이 3개 인스턴스로 시작되었다. getCPPanosByCaptureId는 API에서 받은 pano 목록을 state !== 'done'으로 필터링하는데, 1차 실행이 이미 모든 pano를 done 상태로 처리했으므로 필터 결과가 빈 배열이 되어 not found panos 에러가 발생했다. 근본 원인은 동일 capture에 대한 중복 job 발행(SQS 메시지 중복 또는 재시도)이며, postprocessor 서비스가 이미 처리 완료된 capture에 대한 재실행을 정상적으로 처리하지 못하는 것이다.

Technical Analysis#

Code Path#

  • Entry point: pano-postprocessor-service.ts:43init() 메서드가 authenticate → run → terminateService 순서로 실행
  • pano-postprocessor-service.ts:96getCPPanosByCaptureId(captureId)로 pano 목록 조회
  • Failure point: pano-postprocessor-service.ts:98-100 — 빈 배열 체크 후 throw
typescript
// packages/cupix-pano-postprocessor/src/pano-postprocessor-service.ts:96-101
const cpPanos = await this.panoPostprocessorManager.getCPPanosByCaptureId(captureId);

if (cpPanos.length == 0) {
    logger.error('PanoPostprocessorService::run | not found panos');
    throw new Error('not found panos');
}

getCPPanosByCaptureId는 Tesla API에서 전체 pano를 가져온 뒤 state !== 'done'으로 필터링한다:

typescript
// packages/cupix-pano-postprocessor/src/manager/pano-postprocessor-manager.ts:29-32
async getCPPanosByCaptureId(captureId: number): Promise<CPPano[]> {
    const srvPanos = await this.cupixApi.pano.getAll(captureId);
    return srvPanos.filter((srvPano) => srvPano.state !== 'done').map((srvPano) => new CPPano(srvPano));
};

API의 pano.getAll은 pagination으로 해당 capture의 모든 pano를 조회한다:

typescript
// packages/api/src/api/pano.api.ts:32-46
getAll = async (captureId: number): Promise<Array<TESLA.Pano>> => {
    const api = await this.api();
    return paginateAll<TESLA.Pano>(async (page, perPage) => {
        const res = await api.getPanos(
            Fields.PanoFields,
            captureId,
            undefined, undefined, undefined,
            page, perPage
        );
        return unwrapPaginatedResponse(res);
    });
};

기대 동작: pano가 아직 미처리(done이 아닌) 상태이므로 필터 결과가 비어있지 않아야 한다. 실제 동작: 1차 실행이 이미 모든 pano를 done으로 변경했으므로 필터 결과가 빈 배열이었다.

Log Evidence#

타임라인 확인에 사용한 쿼리:

text
service:cupixworks-pano-postprocessor-instance @capture.id:682964
Time range: 2026-04-17T16:00:00Z to 2026-04-17T18:30:00Z
Sort: timestamp ascending

1차 실행 (성공) — 3개 호스트 중 2개 인스턴스가 처리

text
02:18:37 KST | info  | PanoPostprocessorService::init
02:18:37 KST | info  | PanoPostprocessorService::authenticate | begin
02:18:37 KST | info  | CupixAuth::setSession | session_id: 31baf8cea58937aec7c67e911484e605af811d89
02:18:37 KST | info  | PanoPostprocessorService::run | begin
02:18:39 KST | info  | PanoPostprocessorService Elapsed Time - dd_step:download_panos&dd_elapsed_time:969ms
02:19:38 KST | info  | PanoPostprocessorService Elapsed Time - dd_step:blur_panos&dd_elapsed_time:58816ms
02:19:40 KST | info  | PanoPostprocessorService Elapsed Time - dd_step:change_masks&dd_elapsed_time:1820ms
02:19:49 KST | info  | PanoPostprocessorService Elapsed Time - dd_step:infer_panos&dd_elapsed_time:9124ms
02:20:10 KST | info  | PanoPostprocessorService Elapsed Time - dd_step:resize_panos&dd_elapsed_time:21405ms
02:20:17 KST | info  | PanoPostprocessorService::run | end
02:20:17 KST | info  | PanoPostprocessorService::terminateService | force shutdown after 10 seconds

2차 실행 (실패) — 3개 인스턴스 모두 동일 에러

text
02:23:29 KST | info  | PanoPostprocessorService::init                    (x3 instances)
02:23:29 KST | info  | PanoPostprocessorService::authenticate | begin    (x3)
02:23:29 KST | info  | CupixAuth::setSession | session_id: 31baf8cea58937aec7c67e911484e605af811d89  (x3)
02:23:29 KST | info  | PanoPostprocessorService::run | begin             (x3)
02:23:30 KST | error | PanoPostprocessorService::run | not found panos   (x3)
02:23:30 KST | error | PanoPostprocessorService::run | end - {"message":"not found panos"}  (x3)
02:23:31 KST | info  | PanoPostprocessorService::terminateService | force shutdown after 10 seconds  (x3)

3개 인스턴스의 호스트가 서로 다름을 raw 로그에서 확인:

text
Host 1: 9888910e315e
Host 2: 3f7c312c133c
Host 3: e07fd4a698d4

모든 에러 로그가 동일한 capture.id: 682964, job.id: 1020997, user.id: 44642, team: ebcinc을 공유한다.

Tesla worker 쪽 로그에서 job 1020997의 task stopped callback 확인:

text
02:24:02 KST | info | UploadCpcLogWorker::perform | begins on job 1020997
02:24:02 KST | info | UploadCpcLogWorker::perform | done on job 1020997
02:24:02 KST | info | job_id: 1020997, task_id: arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/2c9608a31d164d6daf6d8b06c0b3387c

Fix Recommendation#

즉시 조치 (Critical)#

  • pano-postprocessor-service.ts:98-100 — 모든 pano가 이미 done 상태인 경우를 에러가 아닌 정상 종료(early return)로 처리해야 한다. 이미 처리 완료된 capture에 대한 재실행은 no-op으로 끝나야 하며, capture 상태를 Error로 변경해서는 안 된다.
  • pano-postprocessor-manager.ts:29-32 — API에서 받은 전체 pano 수와 필터 후 pano 수를 로깅하여, done 필터링 때문에 빈 배열이 된 것인지 아니면 실제로 pano가 없는 것인지 구분할 수 있도록 해야 한다.

단기 개선 (1주 이내)#

  • PanoPostprocessorService::run 시작 시 capture의 pano_postprocessor_state를 먼저 확인하여 이미 Done 상태이면 중복 처리를 건너뛰는 가드를 추가해야 한다.
  • getCPPanosByCaptureId에서 전체 pano 수 vs 필터 후 pano 수를 info 레벨로 로깅하여 디버깅 가시성을 높여야 한다.

장기 개선 (재발 방지)#

  • SQS 메시지 중복 수신 또는 job 재시도 로직을 점검하여 동일 capture에 대한 중복 job 발행 원인을 파악해야 한다.
  • compute-agent의 JobManager에서 이미 완료된 job/capture에 대한 task 실행 전 상태 확인 로직 추가를 검토해야 한다.
  • ECS task를 3개 인스턴스로 실행하는 것이 의도된 동작인지 확인 필요 — 동일 job에 대해 복수 인스턴스가 뜨면 동시 처리 충돌 가능성이 있다.

Monitoring#

  • 이미 done 상태의 capture에 대한 pano postprocessor 재실행 감지:
text
service:cupixworks-pano-postprocessor-instance "not found panos"
  • 동일 capture에 대한 중복 실행 감지 (짧은 간격 내 동일 capture.id로 init이 2회 이상):
text
service:cupixworks-pano-postprocessor-instance "PanoPostprocessorService::init"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial — 에러 핸들링 분기를 early return으로 변경하는 수준. 실제 pano 처리 로직에는 영향 없음. 다만 중복 job 발행 원인 조사는 별도 작업 필요.