ES /docs

BaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMe

RCA: BaseService::handlingMessageErrors | undefined response (empty panos and frame)

Error Log#

Datadog Logs

json
{"error":"undefined response","sqsMessage":{"MessageId":"a4894d22-1716-43ed-a228-ab0cb86270a1","Attributes":{"ApproximateReceiveCount":"1"}}}

Impact#

  • Service: cupixworks-capture-postprocessor-agent
  • Team: solarturbines
  • 발생 횟수: 1 (이 클러스터 기준). 동일 패턴 "empty panos and frame" 에러는 지난 7일간 4건 발생.
  • 최초 발생: 2026-04-20T05:49:10.523Z
  • 최근 발생: 2026-04-20T05:49:10.523Z

Root Cause Summary#

FinalizationService::start에서 skat(scenemapper) 처리 결과의 pano 수와 video frame 수가 모두 0인 상태로 진입하여 throw new Error('empty panos and frame')이 발생했다. 이 Error 객체가 BaseService::handlingMessageErrors로 전파된 후, getApiErrorToDeleteMessage에서 에러 객체의 .response 속성이 없고 JSON 문자열도 아닌 것을 감지하여 "undefined response"로 분류했다. 즉, 에러 메시지 "undefined response"는 실제 API 응답 누락이 아닌, skat 처리 결과에 pano/frame 데이터가 비어 있는 상태에서 발생한 내부 에러를 BaseService가 API 에러로 잘못 분류한 결과이다.

Technical Analysis#

Code Path#

  1. Entry point: BaseService::checkingQueue — SQS 큐에서 메시지를 수신하고 runByMessages()를 호출한다.
typescript
// base-service.ts:105-114
} else {
    this._countWaitedToStopTask = 0;
    try {
        await this.runByMessages();
    } catch (error) {
        await this.handlingMessageErrors(error);
    }
    this.resetMessages();
    await CPUtils.sleep(500);
    await this.checkingQueue();
}
  1. Processing: PostprocessorService::run — Job을 로드하고, capture 모델을 생성한 후, EFS에서 skat 결과 파일들을 로드한다. 이후 refinement 필요 여부에 따라 FinalizationService::start를 호출한다.
typescript
// postprocessor-service.ts:109-116
if (this.isRefinementRequired(cpCapture)) {
    const _preparationRefinementService = new PreparationRefinementService(this.cupixApi, this.jobManager, this.transferManager);
    await _preparationRefinementService.start(cpCapture);
} else {
    const _finalizationService = new FinalizationService(this.cupixApi, this.jobManager, this.transferManager);
    await _finalizationService.start(cpCapture);
    await this.uploadCaptureLandmarkData(cpCapture);
}
  1. Failure point: FinalizationService::startcpCapture.cpPanos (standalone pano 수)와 cpCapture.cpVideos의 video frame 합계가 모두 0이면 에러를 throw한다.
typescript
// finalization-service.ts:25-33
start = async (cpCapture: CPCapture) => {
    logger.debug('FinalizationService::start | begin');
    const panoCount = cpCapture.cpPanos.length;
    const videoFrameCount = cpCapture.cpVideos.reduce((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');
    }
  1. Error classification: BaseService::getApiErrorToDeleteMessage — throw된 Error 객체를 받아 분류한다. 이 객체는 Node.js 시스템 에러가 아니고(errno/code/syscall 없음), JSON 문자열도 아니며, .response 속성도 없으므로 "undefined response"를 반환한다.
typescript
// base-service.ts:240-253
private getApiErrorToDeleteMessage = (error: any): any => {
    if (error == undefined) {
        logger.warn('BaseService::getApiErrorToDeleteMessage | undefined error');
        return 'undefined error';
    }
    if (error.errno != undefined && error.code != undefined && error.syscall != undefined) {
        logger.warn('BaseService::getApiErrorToDeleteMessage | nodejs common system error', error);
        return;
    }
    const response = CPUtils.isJsonString(error) ? JSON.parse(error) : error.response;
    if (response == undefined) {
        logger.warn('BaseService::getApiErrorToDeleteMessage | undefined response', error);
        return 'undefined response';  // <-- Error 객체에 .response 없어서 여기 진입
    }
  1. Message deletion: getApiErrorToDeleteMessage"undefined response"를 반환하면 apiErrorObject != undefined이므로, SQS 메시지를 삭제하고 updateErrorState를 호출한다.
typescript
// base-service.ts:290-313
private handlingMessageErrors = async (error: any): Promise<void> => {
    // ...
    const apiErrorObject = this.getApiErrorToDeleteMessage(error);
    if (apiErrorObject != undefined || this.checkReceiveCountToDeleteMessage()) {
        try {
            errorAndMessage.error = apiErrorObject;
            await this.deleteByMessage(this.messageInProcess);
            if (this._modelInProcess != undefined && this._modelInProcess.id > 0) await this.updateErrorState(this._modelInProcess);
        } catch (error) {
            logger.error('BaseService::handlingMessageErrors | Errors in error handling', error);
        }
    }
    logger.error('BaseService::handlingMessageErrors | Error and message object - %s', JSON.stringify(errorAndMessage));

Log Evidence#

에러 발생 전후 타임라인 (SQS message: a4894d22-1716-43ed-a228-ab0cb86270a1):

text
service:cupixworks-capture-postprocessor-agent "a4894d22-1716-43ed-a228-ab0cb86270a1"
text
14:49:09 KST [error] FinalizationService::start | end - empty panos and frame
14:49:09 KST [warn]  empty panos and frame
14:49:09 KST [info]  AwsQueueManager::deleteMessage | begin - queue url: https://sqs.us-west-2.amazonaws.com/002596530511/cupix-capture-postprocessor-agent-production
14:49:09 KST [info]  AwsQueueManager::deleteMessage | end - message id: a4894d22-1716-43ed-a228-ab0cb86270a1
14:49:10 KST [error] BaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"a4894d22-1716-43ed-a228-ab0cb86270a1","Attributes":{"ApproximateReceiveCount":"1"}}}

동일 패턴 발생 이력 (지난 7일):

text
service:cupixworks-capture-postprocessor-agent "empty panos and frame"
text
2026-04-15 22:36:15 KST [error] FinalizationService::start | end - empty panos and frame
2026-04-17 22:34:24 KST [error] FinalizationService::start | end - empty panos and frame
2026-04-20 13:56:53 KST [error] FinalizationService::start | end - empty panos and frame
2026-04-20 14:49:09 KST [error] FinalizationService::start | end - empty panos and frame

"undefined response"로 기록된 handlingMessageErrors 에러 중 FinalizationService가 원인인 것은 2건이고, 나머지는 네트워크/API 에러(EPIPE, ETIMEDOUT, 500, 502)이다:

text
service:cupixworks-capture-postprocessor-agent "undefined response" status:error
text
2026-04-20 13:56:55 KST - {"error":"undefined response","sqsMessage":{"MessageId":"6e35c423-7e7a-43e4-bfab-63964b6de24a"...}}
2026-04-20 14:49:10 KST - {"error":"undefined response","sqsMessage":{"MessageId":"a4894d22-1716-43ed-a228-ab0cb86270a1"...}}

Fix Recommendation#

즉시 조치 (Critical)#

  • FinalizationService::start (finalization-service.ts:32): throw하는 에러에 더 구체적인 정보를 포함하여 getApiErrorToDeleteMessage에서 올바르게 분류할 수 있도록 해야 한다. 현재 new Error('empty panos and frame')은 plain Error 객체라 API 에러 분류 로직에서 "undefined response"로 잘못 표시된다.

  • BaseService::getApiErrorToDeleteMessage (base-service.ts:240-276): 현재 이 메서드는 API 에러(HTTP response 포함)만 올바르게 분류하고, 내부 로직 에러(Error 객체)는 모두 "undefined response"로 분류한다. 내부 에러와 API 에러를 구분하는 로직을 추가해야 한다. 예를 들어 error instanceof Error인 경우 error.message를 에러 정보로 사용하는 분기를 추가할 수 있다.

단기 개선 (1주 이내)#

  • Pano/frame 데이터가 비어 있는 근본 원인 조사: skat(scenemapper) 처리 결과가 왜 empty인지 확인 필요. FileSystemManager::loadSkatResults에서 skat sampled results 파일은 정상적으로 로드되지만, cpCapture.fromSkatResults()에서 pano 데이터가 0개로 파싱되는 케이스를 분석해야 한다.
  • FinalizationService::start에서 empty panos 에러 발생 시 capture ID, job ID 등 컨텍스트 정보를 에러 로그에 포함하여 디버깅을 용이하게 해야 한다.

장기 개선 (재발 방지)#

  • BaseService::handlingMessageErrors의 에러 분류 체계를 개선하여 내부 비즈니스 로직 에러, API 에러, 네트워크 에러를 명확히 구분해야 한다. 현재 getApiErrorToDeleteMessage는 API 응답 기반 분류만 고려하고 있어 모든 non-API 에러가 "undefined response" 또는 "undefined error"로 뭉뚱그려진다.
  • 커스텀 에러 클래스(예: AgentProcessingError, ApiResponseError)를 도입하여 에러 타입별 처리를 명확하게 할 수 있다.

Monitoring#

  • FinalizationService::start | end - empty panos and frame 에러 발생 빈도 모니터링:
text
service:cupixworks-capture-postprocessor-agent "empty panos and frame" status:error
  • "undefined response" 에러 중 실제 API 에러와 내부 로직 에러를 구분할 수 있는 메트릭 추가 권장.

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard — 에러 분류 로직 개선은 base-service.tsgetApiErrorToDeleteMessage 수정으로 가능하지만, empty pano 근본 원인은 skat 처리 파이프라인 전반 조사가 필요할 수 있다.