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#
- 08:24 UTC — 최초 504 에러 발생 (panoApi.js:2884, infer 작업)
- 08:27:56 UTC — 이 클러스터의
getPanos호출 4건 504 실패 (panoApi.js:2128) - 08:34:17 UTC — cupixworks-api에서 MySQL Lock wait timeout 발생
- 08:34:27 UTC —
updateJobState호출도 504 실패 (job 1031164, 1031171) - 08:37 UTC — 504 에러 소멸, API 응답 정상화
- 08:50 UTC — VoxelService 503 에러 지속 (별도 이슈)
Error Log#
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-sdk의 cupixRetriableRequest가 504를 최대 5회까지 지수 백오프로 재시도하지만, API 서버의 성능 저하가 약 13분간(08:24~08:37) 지속되어 재시도도 모두 실패했다.
Technical Analysis#
Code Path#
- Entry point:
app.ts:55—PanoPostprocessorService().init()호출 - Service init:
pano-postprocessor-service.ts:43-48— authenticate 후run()호출
init = async (): Promise<void> => {
logger.info('PanoPostprocessorService::init');
await this.authenticate();
await this.run();
await this.terminateService();
};
- run() 시작:
pano-postprocessor-service.ts:79-96— job 상태를 Running으로 업데이트 후getCPPanosByCaptureId호출
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);
- getCPPanosByCaptureId:
pano-postprocessor-manager.ts:29-32—cupixApi.pano.getAll(captureId)호출, 이것이 SDK의getPanosHTTP 요청을 트리거
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));
};
- Failure point:
panoApi.js:2128— SDK의getPanos메서드가 HTTP 응답 504를 수신하여HttpErrorthrow
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회 재시도
- Retry logic:
panoApi.js:47-54,89-105— 504는retriableStatusCodes에 포함되어 있어 5회까지 지수 백오프(1s, 2s, 4s, 8s, 16s)로 재시도하지만, API 서버 성능 저하가 지속되어 모든 재시도 실패
this.retriableStatusCodes = new Set([
408,
429,
500,
502,
503,
504 // Gateway Timeout
]);
- Error propagation:
pano-postprocessor-service.ts:188-191— catch 블록에서 capture 상태를 Error로 업데이트하고 에러 로깅
} 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);
- Finally 블록 문제:
pano-postprocessor-service.ts:196— finally에서updateJobState(Stopped)호출 시에도 API가 504를 반환(로그에서 08:34:27에 job 1031164, 1031171 확인). 이 호출은updateJobState내부에서 warn 레벨로 catch하므로 추가 에러는 발생하지 않음.
Log Evidence#
Datadog에서 사용한 검색 쿼리:
service:cupixworks-pano-postprocessor-instance status:error "HTTP request failed"
동일 시간대 504 에러 분포 (panoApi.js 라인별):
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 실패 로그:
service:cupixworks-pano-postprocessor-instance "504" status:warn
{
"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:
service:cupixworks-api status:error "Lock wait timeout"
2026-04-23T08:34:17.839Z — Mysql2::Error::TimeoutError: Lock wait timeout exceeded; try restarting transaction (Record::flush_geo_coordinate)
VoxelService 503 에러 (동일 시간대 지속):
service:cupixworks-api status:error "503 Service Unavailable"
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-api의Record::flush_geo_coordinate에서 발생한 MySQL Lock wait timeout이 API 전체 응답 지연의 원인이다. 이 트랜잭션의 잠금 범위와 실행 시간을 분석하여 DB 레벨에서 개선이 필요하다. - Circuit breaker 패턴 도입: pano-postprocessor가 API 장애 시 빠르게 실패하고, 재처리 큐에 자동으로 재등록되는 메커니즘을 고려할 수 있다. 현재는 한 번 실패하면 capture 상태가
Error로 남아 수동 개입이 필요할 수 있다.
Monitoring#
- API 백엔드 504 비율 모니터링:
service:cupixworks-pano-postprocessor-instance status:error "504 Gateway Time-out" | count by @timestamp(5m)
- MySQL Lock wait timeout 빈도:
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 잠금 경합이 근본 원인으로, 별도 조사가 필요하지만 이 서비스의 코드 변경은 불필요.