ES /docs

PanoPostprocessorService::run | end - {"stack":"HttpError: HTTP request failed

RCA: PanoPostprocessorService::run HttpError 504 Gateway Time-out

Overview#

What Happened#

2026-04-23 08:24~08:37 UTC 사이에 cupixworks-pano-postprocessor-instance 서비스가 내부 API(api-tesla.cupix.internal)에 대한 HTTP 요청에서 nginx 504 Gateway Time-out 응답을 반복적으로 수신했다. 동일 시간대에 상위 서비스(cupixworks-api)에서 MySQL Lock wait timeout과 VoxelService 503 에러가 동시 다발적으로 발생하여, API 백엔드의 응답 지연이 nginx timeout을 초과한 것으로 확인된다. 이 클러스터의 4건은 getPanos API 호출(panoApi.js:2128)이 5회 재시도 후에도 504를 반환하여 서비스 run() 메서드 전체가 실패한 사례이다.

Quick Facts#

Field Value
exception.class HttpError
exception.message HTTP request failed
top_frame panoApi.js:2128
runtime Node.js (TypeScript agent, ECS)
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
qatest3 4 pano postprocessor 작업 실패 — capture의 pano 후처리(mask, infer, resize) 미완료

Timeline#

  1. 08:24 UTC — 최초 504 에러 발생 (panoApi.js:2884, infer 작업)
  2. 08:27:56 UTC — 이 클러스터의 getPanos 호출 4건 504 실패 (panoApi.js:2128)
  3. 08:34:17 UTC — cupixworks-api에서 MySQL Lock wait timeout 발생
  4. 08:34:27 UTCupdateJobState 호출도 504 실패 (job 1031164, 1031171)
  5. 08:37 UTC — 504 에러 소멸, API 응답 정상화
  6. 08:50 UTC — VoxelService 503 에러 지속 (별도 이슈)

Error Log#

Datadog Logs

text
PanoPostprocessorService::run | end - {"stack":"HttpError: HTTP request failed
    at Request._callback (/tmp/agent/dist/node_modules/@tesla/typescript-node-sdk/api/panoApi.js:2128:40)
    at self.callback (/tmp/agent/dist/node_modules/request/request.js:185:22)
    at Request.emit (node:events:524:28)
    at Request.emit (node:domain:489:12)
    at Request.<anonymous> (/tmp/agent/dist/node_modules/request/request.js:1154:10)
    at Request.emit (node:events:524:28)
    at Request.emit (node:domain:489:12)
    at IncomingMessage.<anonymous> (/tmp/agent/dist/node_modules/request/request.js:1076:12)
    at Object.onceWrapper (node:events:638:28)
    at IncomingMessage.emit (node:events:536:35)","message":"HTTP request failed","response":{"body":"<html>\r
<head><title>504 Gateway Time-out</title></head>\r
<body>\r
<center><h1>504 Gateway Time-out</h1></center>\r
<hr><center>nginx</center>\r
</body>\r
</html>\r
","statusCode":504},"body":{},"statusCode":504,"name":"HttpError"}

Impact#

  • Service: cupixworks-pano-postprocessor-instance
  • Team: qatest3
  • 발생 횟수: 4 (이 클러스터), ~15-20건 (동일 시간대 504 전체)
  • 최초 발생: 2026-04-23T08:27:56.127Z
  • 최근 발생: 2026-04-23T08:27:56.228Z

Root Cause Summary#

내부 API 서버(api-tesla.cupix.internal)의 일시적 성능 저하가 근본 원인이다. 동일 시간대에 cupixworks-api에서 MySQL Lock wait timeout(Mysql2::Error::TimeoutError)이 발생했으며, VoxelService도 503 에러를 반환하고 있었다. 이는 데이터베이스 레벨의 잠금 경합이 API 서버의 응답 시간을 nginx의 upstream timeout(기본 60초)을 초과하게 만들어, nginx가 504 Gateway Time-out을 반환한 것이다. @tesla/typescript-node-sdkcupixRetriableRequest가 504를 최대 5회까지 지수 백오프로 재시도하지만, API 서버의 성능 저하가 약 13분간(08:24~08:37) 지속되어 재시도도 모두 실패했다.

Technical Analysis#

Code Path#

  1. Entry point: app.ts:55PanoPostprocessorService().init() 호출
  2. Service init: pano-postprocessor-service.ts:43-48 — authenticate 후 run() 호출
pano-postprocessor-service.ts:43-48typescript
init = async (): Promise<void> => {
    logger.info('PanoPostprocessorService::init');
    await this.authenticate();
    await this.run();
    await this.terminateService();
};
  1. run() 시작: pano-postprocessor-service.ts:79-96 — job 상태를 Running으로 업데이트 후 getCPPanosByCaptureId 호출
pano-postprocessor-service.ts:92-96typescript
await this.panoPostprocessorManager.updatePanoPostprocessorState(captureId, TESLA.UpdateCaptureRequest.PanoPostprocessorStateEnum.Processing);
await this.panoPostprocessorManager.updateJobState(jobId, TESLA.UpdateJobRequest.StateEnum.Running);

const cpPanos = await this.panoPostprocessorManager.getCPPanosByCaptureId(captureId);
  1. getCPPanosByCaptureId: pano-postprocessor-manager.ts:29-32cupixApi.pano.getAll(captureId) 호출, 이것이 SDK의 getPanos HTTP 요청을 트리거
pano-postprocessor-manager.ts:29-32typescript
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));
};
  1. Failure point: panoApi.js:2128 — SDK의 getPanos 메서드가 HTTP 응답 504를 수신하여 HttpError throw
panoApi.js:2117-2134javascript
const requestPromise = () => new Promise((resolve, reject) => {
    request_1.default(localVarRequestOptions, (error, response, body) => {
        if (error) {
            reject(error);
        }
        else {
            body = models_1.ObjectSerializer.deserialize(body, "PanoListResponse");
            if (response.statusCode && response.statusCode >= 200 && response.statusCode <= 299) {
                resolve({ response: response, body: body });
            }
            else {
                reject(new apis_1.HttpError(response, body, response.statusCode)); // line 2128
            }
        }
    });
});
return this.cupixRetriableRequest(requestPromise); // 504는 retriable — 최대 5회 재시도
  1. Retry logic: panoApi.js:47-54,89-105 — 504는 retriableStatusCodes에 포함되어 있어 5회까지 지수 백오프(1s, 2s, 4s, 8s, 16s)로 재시도하지만, API 서버 성능 저하가 지속되어 모든 재시도 실패
panoApi.js:47-54javascript
this.retriableStatusCodes = new Set([
    408,
    429,
    500,
    502,
    503,
    504 // Gateway Timeout
]);
  1. Error propagation: pano-postprocessor-service.ts:188-191 — catch 블록에서 capture 상태를 Error로 업데이트하고 에러 로깅
pano-postprocessor-service.ts:188-196typescript
} catch (error) {
    hasError = true;
    await this.panoPostprocessorManager.updatePanoPostprocessorState(captureId!, TESLA.UpdateCaptureRequest.PanoPostprocessorStateEnum.Error);
    logger.error('PanoPostprocessorService::run | end - %s', stringifyError(error));
} finally {
    if (!hasError) {
        await this.panoPostprocessorManager.updatePanoPostprocessorState(captureId!, TESLA.UpdateCaptureRequest.PanoPostprocessorStateEnum.Done);
    }
    await this.panoPostprocessorManager.updateJobState(jobId!, TESLA.UpdateJobRequest.StateEnum.Stopped);
  1. Finally 블록 문제: pano-postprocessor-service.ts:196 — finally에서 updateJobState(Stopped) 호출 시에도 API가 504를 반환(로그에서 08:34:27에 job 1031164, 1031171 확인). 이 호출은 updateJobState 내부에서 warn 레벨로 catch하므로 추가 에러는 발생하지 않음.

Log Evidence#

Datadog에서 사용한 검색 쿼리:

text
service:cupixworks-pano-postprocessor-instance status:error "HTTP request failed"

동일 시간대 504 에러 분포 (panoApi.js 라인별):

text
panoApi.js:2128 (getPanos)     — 4건, 08:27:56 UTC
panoApi.js:966  (mask 관련)    — 4건, 08:27:34 UTC (pano ids: 82155476, 82155477, 82155500, 82155502)
panoApi.js:2974 (mask 관련)    — 4건, 08:26:40~08:27:34 UTC (pano ids: 82155110, 82155307, 82155486, 82155507)
panoApi.js:2884 (infer 관련)   — 3건, 08:24:28~08:30:57 UTC (pano ids: 82155056, 82155065, 82155066, 82155068)

updateJobState 504 실패 로그:

text
service:cupixworks-pano-postprocessor-instance "504" status:warn
json
{
  "message": "PanoPostprocessorManager::updateJobState | job state update failed - job id: 1031171, state: stopped",
  "timestamp": "2026-04-23T08:34:27.288Z",
  "response": {"body": "<html>...<title>504 Gateway Time-out</title>...</html>", "statusCode": 504}
}

상위 서비스 MySQL Lock wait timeout:

text
service:cupixworks-api status:error "Lock wait timeout"
text
2026-04-23T08:34:17.839Z — Mysql2::Error::TimeoutError: Lock wait timeout exceeded; try restarting transaction (Record::flush_geo_coordinate)

VoxelService 503 에러 (동일 시간대 지속):

text
service:cupixworks-api status:error "503 Service Unavailable"
text
2026-04-23T08:33~08:50 UTC — Cupix::VoxelService: failed to merge voxels, failed to get captured area
  — 영향 Facility IDs: 13, 10216, 11996, 12482, 13087, 13168, 13169, 13171, 13410, 13647, 13760, 13933, 14426

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 API 백엔드 DB 잠금 경합으로 nginx 504 발생 MySQL Lock wait timeout 08:34:17에 확인 (Record::flush_geo_coordinate), 동일 시간대 VoxelService 503 다수, 504가 08:24~08:37 시간대에만 집중 발생 후 자연 소멸 Confirmed
H2 pano-postprocessor의 과도한 병렬 요청이 API 서버 과부하 유발 p-limit(10)으로 병렬 제한, 다수 pano에 대해 동시 API 호출 504는 getPanos(단일 호출)에서도 발생, 다른 서비스(VoxelService)도 동시에 장애 — pano-postprocessor만의 문제가 아님 Rejected
H3 SDK retry 로직 부재 또는 미작동 504는 retriableStatusCodes에 포함(panoApi.js:53), 최대 5회 지수 백오프 재시도 구현 확인. API 장애가 재시도 시간(~31초)을 초과하여 지속된 것이 원인 Rejected
H4 nginx upstream timeout 설정이 너무 짧음 nginx 기본 60초 timeout, DB 잠금이 이를 초과할 수 있음 timeout 자체보다 DB 잠금이 근본 원인 — timeout 증가는 증상 완화에 불과 Inconclusive

Fix Recommendation#

즉시 조치 (Critical)#

  • 조치 불필요: 이 에러는 API 백엔드의 일시적 성능 저하로 인한 transient 에러이며, 08:37 UTC 이후 자연 소멸했다. pano-postprocessor 서비스 자체에는 코드 결함이 없다.
  • 확인 필요: 실패한 capture들의 pano postprocessor 상태가 Error로 남아있을 가능성이 있다. 해당 capture들의 재처리 여부를 확인해야 한다.

단기 개선 (1주 이내)#

  • 에러 로깅 개선: pano-postprocessor-service.ts:191에서 stringifyError(error)는 전체 HTTP response body(nginx HTML)까지 포함하여 로그가 불필요하게 길어진다. 504/503 등 transient error의 경우 상태 코드와 메시지만 로깅하도록 개선하면 로그 가독성이 높아진다.
  • Retry 횟수/시간 검토: 현재 SDK의 5회 재시도(총 ~31초)로는 수분간 지속되는 API 장애를 커버하지 못한다. 서비스 레벨에서 getCPPanosByCaptureId 등 critical 호출에 대한 상위 레벨 재시도(예: 1-2분 대기 후 1회 추가 재시도)를 고려할 수 있다.

장기 개선 (재발 방지)#

  • DB 잠금 경합 근본 원인 조사: cupixworks-apiRecord::flush_geo_coordinate에서 발생한 MySQL Lock wait timeout이 API 전체 응답 지연의 원인이다. 이 트랜잭션의 잠금 범위와 실행 시간을 분석하여 DB 레벨에서 개선이 필요하다.
  • Circuit breaker 패턴 도입: pano-postprocessor가 API 장애 시 빠르게 실패하고, 재처리 큐에 자동으로 재등록되는 메커니즘을 고려할 수 있다. 현재는 한 번 실패하면 capture 상태가 Error로 남아 수동 개입이 필요할 수 있다.

Monitoring#

  • API 백엔드 504 비율 모니터링:
text
service:cupixworks-pano-postprocessor-instance status:error "504 Gateway Time-out" | count by @timestamp(5m)
  • MySQL Lock wait timeout 빈도:
text
service:cupixworks-api status:error "Lock wait timeout exceeded"
  • pano-postprocessor capture Error 상태 잔류 모니터링 (capture가 Error 상태로 장기간 남아있는 경우 알림)

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial — transient infrastructure 이슈이며, pano-postprocessor 서비스 코드 자체에 결함 없음. API 백엔드의 DB 잠금 경합이 근본 원인으로, 별도 조사가 필요하지만 이 서비스의 코드 변경은 불필요.