ES /docs

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#

  1. ~08:13 UTC — 504 에러 시작 (다수 서비스에서 최초 504 관측)
  2. 08:19:56 UTC — pano 82155068에 대한 PUT /api/v1/panos/82155068/meta/blurriness에서 502 + Mysql2::Error::TimeoutError: Lock wait timeout exceeded 발생
  3. 08:24:28 UTC — 이 클러스터의 최초 504 에러 (pano id:82155065, 82155068 동시)
  4. 08:28:06 UTC — pano 82155068에 대한 PUT /api/v1/panos/82155068에서 다시 502 + Lock wait timeout 발생
  5. 08:28:31 UTC — pano id:82155066에서 504 에러
  6. 08:30:56~08:30:57 UTC — pano id:82155056에서 504 에러, pointcloud 에이전트에서도 504 발생
  7. 08:30:59~08:31:00 UTCupdatePanoState 에러 복구 시도도 504 실패 (pano 82155065, 82155068)
  8. 08:34:27 UTCupdateJobState 호출도 504 실패 (job 1031171, 1031164), PanoPostprocessorService::run 종료
  9. ~08:35 UTC — API 응답 정상화, pano 82155068 tile upload/stitching 성공 (200)

Error Log#

Datadog Logs

text
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:46this.run() 호출
  • run() 메서드에서 infer 단계 시작: pano-postprocessor-service.ts:148-160
  • 각 pano에 대해 p-limit(10) 병렬 제한으로 inferWork.inferBlurriness(cpPano) + inferWork.updateBlurriness(cpPano) 실행
  • Failure point: infer-work.ts:25this.cupixApi.pano.updateMetaByKey() 호출
packages/cupix-pano-postprocessor/src/pano-postprocessor-service.ts:147-160typescript
// 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에 저장한다:

packages/cupix-pano-postprocessor/src/work/infer-work.ts:23-28typescript
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-sdkPanoApi.updateMetaByKey()를 래핑한다:

packages/api/src/api/pano.api.ts:104-108typescript
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)로 재시도된다:

node_modules/@tesla/typescript-node-sdk/api/panoApi.js:47-53,89-103javascript
// 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 쿼리:

text
service:cupixworks-pano-postprocessor-instance status:error "infer pano"

4건의 infer 에러가 모두 504 Gateway Timeout:

text
[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로 실패:

text
service:cupixworks-pano-postprocessor-instance "82155068"
json
{
  "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 확인 (로그에서 추출):

text
PUT http://api-tesla.cupix.internal/api/v1/panos/82155068?fields[0]=id&fields[1]=name&...

동일 시간대 다른 서비스에서도 504 발생 확인:

text
"504 Gateway" OR "504 Gateway Time-out"
text
[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"):

text
[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):

text
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 전체 타임라인:

text
[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 단계 소요 시간 (일부 인스턴스에서 매우 높음):

text
[08:34:37 UTC] PanoPostprocessorService Elapsed Time - dd_step:infer_panos&dd_elapsed_time:1002888ms

이는 약 16.7분으로, 정상보다 훨씬 긴 시간이다. 504 타임아웃 대기 시간이 포함된 것으로 보인다.

전체 504 규모 (pano-postprocessor만):

text
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-sdkrequest 라이브러리에 명시적 timeout을 설정. 현재 각 retry 시도가 nginx upstream timeout(~60s)까지 무한 대기하므로, 30초 timeout을 설정하면 retry가 더 빠르게 순환되어 outage 복구 시점에 성공할 확률이 높아진다.
  • 수정 대상: SDK 빌드 시 timeout 옵션 추가, 또는 packages/api/src/utils/tesla-api.tsgetApi() 에서 생성된 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.internal nginx의 504 발생률 및 MySQL lock wait timeout 빈도에 대한 알림 추가.
  • Circuit breaker 패턴 도입: 연속 5xx 발생 시 일정 시간 대기 후 재시도하는 패턴을 API 클라이언트에 적용.

Monitoring#

  • MySQL lock wait timeout 모니터링 (근본 원인 추적):
text
service:cupixworks-api "Lock wait timeout"
  • 504 발생률 추적:
text
service:cupixworks-pano-postprocessor-instance "504" status:(error OR warn)
text
"504 Gateway" service:cupixworks-*
  • API 응답 지연 추적: DB 시간이 60초를 초과하는 요청 모니터링
  • infer 단계 소요 시간 이상 탐지: dd_step:infer_panos 메트릭에서 dd_elapsed_time 값이 비정상적으로 높은 경우 알림

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard