PanoPostprocessorService::run | infer pano id:82155068 | error {"stack":"HttpError: HTTP request fai
RCA: PanoPostprocessorService infer 504 Gateway Timeout
Overview#
What Happened#
2026-04-23 08:13~08:34 UTC 사이에 Tesla API(api-tesla.cupix.internal) 백엔드에서 MySQL lock wait timeout(Mysql2::Error::TimeoutError: Lock wait timeout exceeded) 이 발생하며 Rails 요청 처리가 지연되었고, nginx 프록시가 504 Gateway Timeout을 반환했다. 이 에러 클러스터의 4건은 pano-postprocessor의 blurriness 메타데이터 업데이트(updateMetaByKey) 실패이며, 동일 시간대에 총 34건 이상의 504가 pano-postprocessor, 3d-reconstruction agent, mask 처리 등 다수 서비스/캡처에서 동시 발생했다. pano 82155068은 08:19:56에 이미 lock wait timeout으로 502를 받았고, 08:24:28에 504를 받았으며, 08:35 이후 정상 복구되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | HttpError |
| exception.message | HTTP request failed |
| top_frame | panoApi.js:2884 (@tesla/typescript-node-sdk) |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| qatest3 (pano-postprocessor) | 4 | pano blurriness 메타데이터 업데이트 실패, pano 상태가 error로 전환 |
| pano-postprocessor (mask) | 8+ | mask 처리 후 pano 상태 업데이트 504 실패 (다수 capture 영향) |
| 3d-reconstruction agent | 2+ | pointcloud 업로드 메타 업데이트 실패 (동일 시간대 504) |
Timeline#
- ~08:13 UTC — 504 에러 시작 (다수 서비스에서 최초 504 관측)
- 08:19:56 UTC — pano 82155068에 대한
PUT /api/v1/panos/82155068/meta/blurriness에서 502 + Mysql2::Error::TimeoutError: Lock wait timeout exceeded 발생 - 08:24:28 UTC — 이 클러스터의 최초 504 에러 (pano id:82155065, 82155068 동시)
- 08:28:06 UTC — pano 82155068에 대한
PUT /api/v1/panos/82155068에서 다시 502 + Lock wait timeout 발생 - 08:28:31 UTC — pano id:82155066에서 504 에러
- 08:30:56~08:30:57 UTC — pano id:82155056에서 504 에러, pointcloud 에이전트에서도 504 발생
- 08:30:59~08:31:00 UTC —
updatePanoState에러 복구 시도도 504 실패 (pano 82155065, 82155068) - 08:34:27 UTC —
updateJobState호출도 504 실패 (job 1031171, 1031164), PanoPostprocessorService::run 종료 - ~08:35 UTC — API 응답 정상화, pano 82155068 tile upload/stitching 성공 (200)
Error Log#
PanoPostprocessorService::run | infer pano id:82155068 | error {"stack":"HttpError: HTTP request failed
at Request._callback (/tmp/agent/dist/node_modules/@tesla/typescript-node-sdk/api/panoApi.js:2884: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\n<head><title>504 Gateway Time-out</title></head>\r\n<body>\r\n<center><h1>504 Gateway Time-out</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n","statusCode":504},"body":{},"statusCode":504,"name":"HttpError"}
Impact#
- Service:
cupixworks-pano-postprocessor-instance - Team: qatest3
- 발생 횟수: 4
- 최초 발생: 2026-04-23T08:24:28.653Z
- 최근 발생: 2026-04-23T08:30:57.174Z
Root Cause Summary#
Tesla Rails API 백엔드에서 MySQL lock wait timeout(Mysql2::Error::TimeoutError: Lock wait timeout exceeded) 이 발생하며 요청 처리가 지연되었고, nginx 프록시가 upstream 응답 대기 시간 초과로 504 Gateway Timeout을 반환했다. pano 82155068의 경우 08:19:56에 blurriness 메타 업데이트에서 lock wait timeout + 502를, 08:24:28에 504를, 08:28:06에 다시 lock wait timeout + 502를 받았다. 동일 시간대(08:13~08:34)에 총 34건 이상의 504가 pano-postprocessor, 3d-reconstruction agent 등 다수 서비스에서 동시 발생하여, DB lock contention에 의한 Rails API 전체 응답 지연이 근본 원인이다. @tesla/typescript-node-sdk에 내장된 retry(5회, exponential backoff)가 작동했으나, 각 시도가 nginx timeout(~60s)까지 대기하고 DB lock contention이 ~20분간 지속되어 retry 윈도우를 초과, 최종 실패로 확정되었다. 08:35 이후 API가 자연 복구되어 후속 처리(tile upload, stitching)는 정상 완료되었다.
Technical Analysis#
Code Path#
- Entry point:
pano-postprocessor-service.ts:46—this.run()호출 run()메서드에서 infer 단계 시작:pano-postprocessor-service.ts:148-160- 각 pano에 대해
p-limit(10)병렬 제한으로inferWork.inferBlurriness(cpPano)+inferWork.updateBlurriness(cpPano)실행 - Failure point:
infer-work.ts:25—this.cupixApi.pano.updateMetaByKey()호출
// infer
start = Date.now();
const inferTasks = cpPanos.map((cpPano) => {
return PARALLEL_TASK_LIMIT(async () => {
try {
this.inferWork.inferBlurriness(cpPano);
await this.inferWork.updateBlurriness(cpPano);
} catch (error) {
logger.error('PanoPostprocessorService::run | infer pano id:%d | error %s', cpPano.panoId, stringifyError(error));
await this.panoPostprocessorManager.updatePanoState(cpPano.panoId!, TESLA.UpdatePanoRequest.StateEnum.Error);
erroredPanoIds.add(cpPano.panoId!);
}
});
});
await Promise.all(inferTasks);
inferBlurriness()는 로컬 native 모듈(scenemapperutils_api.node)로 이미지 blurriness를 계산하고, updateBlurriness()가 결과를 API에 저장한다:
async updateBlurriness(cpPano: CPPano) {
if (!cpPano.panoId || !cpPano.blurriness) return;
await this.cupixApi.pano.updateMetaByKey(cpPano.panoId!, Constants.PanoMetaBlurrinessName, cpPano.blurriness);
await this.cupixApi.pano.updateMetaByKey(cpPano.panoId!, Constants.PanoMetaVerName, Constants.PanoMetaVersion);
logger.debug('InferWork::updateBlurriness pano id:%d');
}
API 클라이언트는 @tesla/typescript-node-sdk의 PanoApi.updateMetaByKey()를 래핑한다:
updateMetaByKey = async (panoId: number, metaKey: string, body: any): Promise<any> => {
const api = await this.api();
const res = await api.updateMetaByKey(panoId, metaKey, Fields.MetaFields, body);
return unwrapResult(res);
};
@tesla/typescript-node-sdk(panoApi.js)에는 retry 로직이 내장되어 있다. 504는 retriable status code로 분류되어 최대 5회 exponential backoff(1s, 2s, 4s, 8s, 16s)로 재시도된다:
// retriable status codes
this.retriableStatusCodes = new Set([408, 429, 500, 502, 503, 504]);
// retry with exponential backoff
this.cupixRetriableRequest = (func, retries) => new Promise((resolve, reject) => {
const MaxRetries = 5;
const _retries = retries != undefined ? retries : 0;
const _retryInterval = Math.pow(2, _retries) * 1000;
func()
.then(response => resolve(response))
.catch(e => {
if (_retries < MaxRetries && e && this.isCupixRetriableRequest(e)) {
setTimeout(() => {
this.cupixRetriableRequest(func, _retries + 1).then(resolve).catch(reject);
}, _retryInterval);
} else {
reject(e);
}
});
});
즉, 이 에러는 5회 retry 후에도 실패한 것이다. 단, HTTP 요청의 timeout은 설정되어 있지 않아 각 시도가 nginx의 upstream timeout(~60s)까지 대기한 후 504를 받고, 총 대기 시간은 retry 포함 5분 이상이 될 수 있다. 이는 infer_panos elapsed time이 1,002,888ms(~16.7분)로 비정상적으로 높은 것과 일치한다.
SDK의 5회 retry가 모두 실패한 후 에러가 catch 블록으로 전달되어 updatePanoState(error)를 호출하지만, 이 호출 역시 동일한 504 상태의 API를 대상으로 하므로 함께 실패했다 (warn 레벨로 로그됨).
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-pano-postprocessor-instance status:error "infer pano"
4건의 infer 에러가 모두 504 Gateway Timeout:
[08:24:28 UTC] PanoPostprocessorService::run | infer pano id:82155065 | error {...,"statusCode":504,"name":"HttpError"}
[08:24:28 UTC] PanoPostprocessorService::run | infer pano id:82155068 | error {...,"statusCode":504,"name":"HttpError"}
[08:28:31 UTC] PanoPostprocessorService::run | infer pano id:82155066 | error {...,"statusCode":504,"name":"HttpError"}
[08:30:57 UTC] PanoPostprocessorService::run | infer pano id:82155056 | error {...,"statusCode":504,"name":"HttpError"}
에러 복구 시도(pano 상태 → error 업데이트)도 504로 실패:
service:cupixworks-pano-postprocessor-instance "82155068"
{
"timestamp": "2026-04-23 17:31:00 KST",
"status": "warn",
"message": "PanoPostprocessorManager::updatePanoState | pano state update failed - pano id: 82155068, state: error, reason {\"response\":{\"statusCode\":504,...}}"
}
API 호출 대상 URL 확인 (로그에서 추출):
PUT http://api-tesla.cupix.internal/api/v1/panos/82155068?fields[0]=id&fields[1]=name&...
동일 시간대 다른 서비스에서도 504 발생 확인:
"504 Gateway" OR "504 Gateway Time-out"
[08:30:56 UTC] UploadNewPointcloudTask:init | done - cpPointcloud target: ... - {...,"statusCode":504,...}
[08:32:13 UTC] TransferManager::uploadNewPointclouds | {...,"statusCode":504,...}
[08:32:57 UTC] TransferManager::uploadNewPointclouds | {...,"statusCode":504,...}
[08:34:27 UTC] PanoPostprocessorManager::updateJobState | job state update failed - job id: 1031171 ... 504
[08:34:27 UTC] PanoPostprocessorManager::updateJobState | job state update failed - job id: 1031164 ... 504
MySQL lock wait timeout (504의 근본 원인):
pano 82155068에 대한 API 측 로그 (service:cupixworks-api "82155068"):
[08:19:56 UTC] [502] PUT /api/v1/panos/82155068/meta/blurriness
ActiveRecord::LockWaitTimeout - Mysql2::Error::TimeoutError: Lock wait timeout exceeded
DURATION: 117,414ms DB: 119,254ms HOST: ip-10-1-19-190
[08:20:10 UTC] [200] PUT /api/v1/panos/82155068/meta/blurriness DURATION: 70,504ms DB: 68,082ms
[08:21:40 UTC] [200] PUT /api/v1/panos/82155068/meta/blurriness DURATION: 98,596ms DB: 96,930ms
[08:24:21 UTC] [200] PUT /api/v1/panos/82155068/meta/blurriness DURATION: 196,315ms DB: 200,529ms
[08:24:27 UTC] [200] PUT /api/v1/panos/82155068/meta/blurriness DURATION: 130,593ms DB: 128,882ms
[08:28:06 UTC] [502] PUT /api/v1/panos/82155068 (state=error)
ActiveRecord::LockWaitTimeout - Mysql2::Error::TimeoutError: Lock wait timeout exceeded
DURATION: 217,039ms DB: 215,658ms HOST: ip-10-1-19-190
성공한 요청도 DB 시간이 70~200초로 극도로 느려, lock contention이 시스템 전체에 영향을 미쳤음을 보여준다.
시스템 전체 lock wait timeout 규모 (service:cupixworks-api "Lock wait timeout" 08:15-08:40Z):
100건 이상 (limit hit). 영향받은 호스트 3대 전부:
- ip-10-1-19-190, ip-10-1-80-134, ip-10-1-144-228 (all us-west-2)
영향받은 엔드포인트:
- PanosController#create (500), #update (502), #update_meta_by_key (502)
- PointcloudsController#create (500)
- ClustersController#create (500)
- JobsController#update (502), #complete_action (502)
- CapturesController#update_meta_by_key (502)
- EditingsController#update (502)
DB times: 60,000ms ~ 255,000ms (1~4분+ 순수 DB 대기)
nginx는 upstream 응답 대기 시간 초과 시 504를 반환했고, Rails가 502를 먼저 반환한 경우도 있었다. 에이전트가 받은 504는 nginx가 Rails 응답을 기다리다가 timeout된 결과이다.
에이전트 측 pano 82155068 전체 타임라인:
[08:24:28 UTC] THE ERROR: infer pano id:82155068 — 504 Gateway Timeout (에이전트 수신)
[08:25:58 UTC] pano state: resource_uploaded → error
[08:31:00 UTC] updatePanoState(82155068, error) — 504 실패 (에이전트 수신)
[08:35:20~08:37:31 UTC] tile upload, stitching — 200 OK (정상 복구)
infer 단계 소요 시간 (일부 인스턴스에서 매우 높음):
[08:34:37 UTC] PanoPostprocessorService Elapsed Time - dd_step:infer_panos&dd_elapsed_time:1002888ms
이는 약 16.7분으로, 정상보다 훨씬 긴 시간이다. 504 타임아웃 대기 시간이 포함된 것으로 보인다.
전체 504 규모 (pano-postprocessor만):
service:cupixworks-pano-postprocessor-instance 504
34건의 504 로그 확인. infer(4건), mask(8건+), updatePanoState/updateJobState/updatePanoPostprocessorState(다수) 등 세 가지 유형의 API 호출에서 모두 504 발생. 영향받은 capture: 685885, 685847, 685931, 685932, 685883 등 다수.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | MySQL lock contention으로 Rails API 응답 지연 → nginx 504 | pano 82155068에서 08:19:56, 08:28:06에 Mysql2::Error::TimeoutError: Lock wait timeout exceeded 확인 (502). 동일 시간대 다수 서비스에서 504 동시 발생. 08:35 이후 자연 복구 |
— | Confirmed |
| H2 | 특정 pano ID의 메타데이터 크기가 과도하여 API 처리 지연 | 504는 PUT /api/v1/panos/{id} 요청에서 발생 | 4개의 서로 다른 pano ID + pointcloud API에서도 동일 504. content-length: 17바이트. lock wait timeout은 데이터 크기와 무관 | Rejected |
| H3 | 에이전트의 p-limit(10) 병렬 호출로 인한 API 과부하 |
최대 10개 동시 API 호출 가능 | 504 원인은 DB lock contention이며 에이전트 요청량과 직접적 관계 없음. 다른 에이전트(3d-reconstruction)에서도 동시 504 | Rejected |
| H4 | SDK retry(5회)가 부족하여 장기 outage에 대응 못함 | SDK에 retry 내장(5회, exponential backoff ~31s). 그러나 각 시도가 nginx timeout(~60s)까지 대기하므로 총 5분+. DB lock contention이 ~20분 지속되어 retry 윈도우 초과 | retry 자체는 작동했으나 outage 기간이 retry 윈도우보다 길었음 | Contributing factor |
Fix Recommendation#
즉시 조치 (Critical)#
없음. 이 에러는 Tesla Rails API의 MySQL lock contention에 의한 일시적 인프라 이슈이며, 에이전트 코드의 버그가 아니다. 08:13~08:34 약 20분간 발생 후 자연 복구되었다.
단기 개선 (1주 이내)#
- HTTP 요청 timeout 설정:
@tesla/typescript-node-sdk의request라이브러리에 명시적 timeout을 설정. 현재 각 retry 시도가 nginx upstream timeout(~60s)까지 무한 대기하므로, 30초 timeout을 설정하면 retry가 더 빠르게 순환되어 outage 복구 시점에 성공할 확률이 높아진다. - 수정 대상: SDK 빌드 시 timeout 옵션 추가, 또는
packages/api/src/utils/tesla-api.ts의getApi()에서 생성된 API 인스턴스에 timeout 설정. - 에러 로그 레벨 검토: SDK의 5회 retry 실패 후
error로깅은 적절하나,updatePanoState실패를 별도로error가 아닌warn으로 유지하는 현재 방식은 적절.
장기 개선 (재발 방지)#
- DB lock contention 근본 원인 조사: Tesla Rails API에서 pano 메타 업데이트 시 발생하는
Lock wait timeout exceeded의 원인 파악. 다수 에이전트가 동시에 동일 테이블/행에 대해 UPDATE를 수행할 때 경합이 발생할 수 있음.MetableController#update_meta_by_key(metable_controller.rb:42-69)의@model.save호출 시 lock scope 최소화 검토. - 인프라 모니터링 강화:
api-tesla.cupix.internalnginx의 504 발생률 및 MySQL lock wait timeout 빈도에 대한 알림 추가. - Circuit breaker 패턴 도입: 연속 5xx 발생 시 일정 시간 대기 후 재시도하는 패턴을 API 클라이언트에 적용.
Monitoring#
- MySQL lock wait timeout 모니터링 (근본 원인 추적):
service:cupixworks-api "Lock wait timeout"
- 504 발생률 추적:
service:cupixworks-pano-postprocessor-instance "504" status:(error OR warn)
"504 Gateway" service:cupixworks-*
- API 응답 지연 추적: DB 시간이 60초를 초과하는 요청 모니터링
- infer 단계 소요 시간 이상 탐지:
dd_step:infer_panos메트릭에서dd_elapsed_time값이 비정상적으로 높은 경우 알림
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard