ES /docs

PanoPostprocessorService::run | resize pano id:92617986 | error {"stack":"HttpError: HTTP request fa

RCA: PanoPostprocessorService::run resize pano id:92617986 — 502 Bad Gateway

Overview#

What Happened#

2026-07-09 09:39:34 KST, cupixworks-pix-genie-preprocessor-instance (whitingturner tenant, us-west-2) 에서 pano id 92617986 의 resize 태스크가 tesla API 호출 중 nginx 로부터 502 Bad Gateway 를 받아 실패했습니다. Datadog 상 14일 내 동일 에러는 1건뿐인 일회성 이벤트이며, 해당 pano 는 Error state 로 마킹되었으나 capture 단위 postprocess 는 정상 종료되었습니다.

Quick Facts#

Field Value
exception.class HttpError
exception.message HTTP request failed
top_frame panoApi.js:2020:40 (getPano 응답 처리)
runtime Node.js (agent Docker image, @tesla/typescript-node-sdk@1.13.3-SNAPSHOT.202605110829)
env production, us-west-2
tenant cupix / whitingturner

Affected Teams#

Team / Domain Error Count Impact
whitingturner (pano postprocess) 1 pano id 92617986 1건이 Error state 로 마킹, capture 자체는 Done 처리

Timeline#

  1. 2026-07-09 09:07:09 KSTPanoPostprocessorService::run | begin (whitingturner capture 처리 시작)
  2. 2026-07-09 09:38:45 KSTPOST /api/v1/panos/92617986/tile_upload_credentials 200 (resize 태스크 진행 중)
  3. 2026-07-09 09:38:59 KSTPUT /api/v1/panos/92617986/check_tile_uploading 200 (uploadResizedImage 완료)
  4. 2026-07-09 ~09:39:03 KSTResizeWork.checkStitchedpano.get(92617986) 최초 시도, nginx 가 502 반환 (추정: 마지막 성공 API 시각과 에러 로그 시각 간 ~35s 차이가 retry 지수 백오프(1+2+4+8+16=31s)와 부합)
  5. 2026-07-09 09:39:34 KSTcupixRetriableRequest 5회 재시도 모두 502 → HttpError 최종 reject → resize 태스크 catch 에서 logger.errorupdatePanoState(Error) 실행 (첫 에러 발생)
  6. 2026-07-09 09:39:49 KSTPUT /api/v1/panos/92617986 200 (updatePanoState 성공, API/nginx 는 이미 회복)
  7. 2026-07-09 09:49:02 KSTPanoPostprocessorService::run | end (outer catch 미발생, capture postprocess state=Done)

Error Log#

Datadog Logs

text
PanoPostprocessorService::run | resize pano id:92617986 | error {"stack":"HttpError: HTTP request failed
    at Request._callback (/tmp/agent/dist/node_modules/.pnpm/@tesla+typescript-node-sdk@1.13.3-SNAPSHOT.202605110829_c0aaa38c9a9fff396d4124c32a1f39fa/node_modules/@tesla/typescript-node-sdk/api/panoApi.js:2020:40)
    at self.callback (/tmp/agent/dist/node_modules/.pnpm/request@2.88.2/node_modules/request/request.js:185:22)
    at Request.emit (node:events:524:28)
    ...
","message":"HTTP request failed","response":{"body":"<html>\r
<head><title>502 Bad Gateway</title></head>\r
<body>\r
<center><h1>502 Bad Gateway</h1></center>\r
<hr><center>nginx</center>\r
</body>\r
</html>\r
","statusCode":502},"body":{},"statusCode":502,"name":"HttpError"}

Impact#

  • Service: cupixworks-pix-genie-preprocessor-instance
  • Team: whitingturner
  • 발생 횟수: 1
  • 최초 발생: 2026-07-09 09:39 KST
  • 최근 발생: 2026-07-09 09:39 KST

pano 1건이 Error state 로 마킹되었으나, capture 단위 후처리 파이프라인은 outer try/catch 를 트리거하지 않고 정상 종료. 사용자 시점에서는 해당 capture 의 특정 pano 만 재처리 필요.

Root Cause Summary#

ResizeWork.checkStitched 에서 tesla API 의 GET /api/v1/panos/{id} 를 호출하는 도중 nginx 프록시가 502 Bad Gateway 를 반환했습니다. SDK 의 cupixRetriableRequest 는 502 를 retriable status 로 인식해 지수 백오프로 최대 5회 재시도(1s → 2s → 4s → 8s → 16s, 총 ~31s)를 수행했지만, 5회 모두 502 를 받았습니다. 결과적으로 HttpError 가 상위로 던져지고 resize 태스크의 catch 블록에서 pano state 가 Error 로 마킹되었습니다. Rails 백엔드 access log 에는 해당 요청이 5xx 로 남지 않아, 요청이 upstream(Rails pod)까지 도달하지 못하고 nginx-Rails 사이에서 실패한 upstream 오류로 판단됩니다.

동 fingerprint 로는 14일 내 1건이지만, nginx↔Rails 사이 5xx 자체는 이번 사건 시점(2026-07-09 00:28-01:03 UTC)에 여러 agent 서비스에서 동시다발적으로 발생한 incident 창의 일부입니다. 이 revision 에서 재조사한 결과, 같은 창에서 502(20건)와 504(10건)가 cupixworks-pano-postprocessor-instance, cupixworks-capture-postprocessor-agent, cupixworks-capture-preprocessor-agent, cupixworks-pix-genie-preprocessor-instance 등 여러 agent 서비스에서 관측되며, 같은 시간에 error-sweeper 의 active incident 2026-07-08-svc-cupixworks-api--resource-exhaustion-2 가 열려 있었습니다. 즉 본 클러스터는 순수한 일회성 upstream 실패가 아니라 Rails 백엔드의 resource exhaustion 이 nginx↔Rails 경로에서 502/504 로 나타난 창의 tail event 로 해석하는 것이 정확합니다. 다만 이 특정 fingerprint 는 pano id 92617986 이 SDK 의 5회 재시도까지 소진한 유일한 케이스라 재발 카운트가 1 로 잡힌 것입니다.

Technical Analysis#

Code Path#

  • Entry point: pano-postprocessor-service.ts:79 (PanoPostprocessorService.run)
  • Resize 병렬 처리: pano-postprocessor-service.ts:165-178 에서 각 pano 별로 resizePano → uploadResizedImage → checkStitched 를 실행
  • Failure point: resize-work.ts:112this.cupixApi.pano.get(cpPano.panoId) 호출 중 502 반환
  • Retry & error surface: panoApi.js:2020 에서 reject(new HttpError(response, body, 502))cupixRetriableRequest 가 5회 재시도 후 최종 reject
applications/agents/packages/cupix-pano-postprocessor/src/pano-postprocessor-service.ts:165-177typescript
const resizeTasks = cpPanos.map((cpPano) => {
    return PARALLEL_TASK_LIMIT(async () => {
        try {
            await this.resizeWork.resizePano(cpPano);
            await this.resizeWork.uploadResizedImage(cpPano);
            await this.resizeWork.checkStitched(cpPano);
        } catch (error) {
            logger.error('PanoPostprocessorService::run | resize pano id:%d | error %s', cpPano.panoId, stringifyError(error));
            await this.panoPostprocessorManager.updatePanoState(cpPano.panoId!, TESLA.UpdatePanoRequest.StateEnum.Error);
            erroredPanoIds.add(cpPano.panoId!);
        }
    });
});
applications/agents/packages/cupix-pano-postprocessor/src/work/resize-work.ts:108-119typescript
checkStitched = async (cpPano: CPPano): Promise<void> => {
    if (!cpPano.panoId) return;

    // Check if pano is already stitched
    const pano = await this.cupixApi.pano.get(cpPano.panoId);   // ← 502 발생 지점

    if (pano.stitched) {
        logger.debug('ResizeWork::checkStitched | pano %d is already stitched, skipping update ...');
        return;
    }
    ...
};
node_modules/@tesla/typescript-node-sdk/api/panoApi.js:2009-2025 (compiled)javascript
const requestPromise = () => new Promise((resolve, reject) => {
    request_1.default(localVarRequestOptions, (error, response, body) => {
        if (error) {
            reject(error);
        }
        else {
            body = models_1.ObjectSerializer.deserialize(body, "PanoResponse");
            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 2020, stack top
            }
        }
    });
});
return this.cupixRetriableRequest(requestPromise);
cupix-api templates/typescript-node/api-single.mustache:148-204 (retry policy)typescript
private retriableStatusCodes = new Set<number>([
    500, // Internal Server Error
    502, // Bad Gateway
    503, // Service Unavailable
    504  // Gateway Timeout
]);
...
private cupixRetriableRequest = <T>(func: () => Promise<T>, retries?: number): Promise<T> => 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);
        }
    });
});

기대 동작: 502 는 retriable 이므로 5회 재시도 안에 회복된 nginx/upstream 에서 200 을 받아 성공. 실제 동작: 5회 모두 502 → HttpError 상위 전파 → resize 태스크 catch → pano state=Error 마킹.

Log Evidence#

Datadog 쿼리 (재현용, cluster 파일 URL 그대로):

text
service:cupixworks-pix-genie-preprocessor-instance status:error @environment:production "PanoPostprocessorService::run"

같은 pano id 로 넓혀본 쿼리:

text
service:cupixworks-pix-genie-preprocessor-instance 92617986

핵심 로그 (KST):

text
2026-07-09 09:38:45 [200] POST /api/v1/panos/92617986/tile_upload_credentials
2026-07-09 09:38:59 [200] PUT  /api/v1/panos/92617986/check_tile_uploading
2026-07-09 09:39:34 ERROR PanoPostprocessorService::run | resize pano id:92617986 | error ... statusCode:502
2026-07-09 09:39:49 [200] PUT  /api/v1/panos/92617986  (updatePanoState=Error 반영)

14일 재발 검증:

text
service:cupixworks-pix-genie-preprocessor-instance "502 Bad Gateway"

Found 1 logs (해당 이벤트 1건만).

Rails 백엔드 access log 조회 (동시간대 cupixworks-api): 문제 요청에 해당하는 502/5xx access log 없음. 즉 요청이 Rails 프로세스까지 도달하지 못하고 nginx 상단에서 502 를 응답한 것으로 판단.

nginx↔Rails 5xx 최근 빈도 (revision 1 재조사, now-14d):

  • 쿼리 "502 Bad Gateway" "nginx" — 총 70건. 그 중 51건은 cupixworks-capture-zip-production Lambda(별도 fingerprint), 나머지 19건이 agent Node 서비스.
  • 쿼리 "HttpError" ("500" OR "502" OR "503" OR "504") — 총 39건 (502: 23, 504: 14, 503: 2, 500: 0).
  • 서비스별 502 (14일): cupixworks-pano-postprocessor-instance 16, cupixworks-capture-postprocessor-agent 2, cupixworks-capture-intelligence-agent 2, cupixworks-pix-genie-preprocessor-instance 1(본 클러스터), cupixworks-capture-preprocessor-agent 1, cupixworks-any-potree-agent 1.
  • 일자별 5xx (HttpError 기준): 06-26 (504×1), 07-01 (504×1), 07-03 (502×2, 504×2), 07-06 (502×1), 07-07 (503×1), 07-08 (503×1), 07-09 (502×20, 504×10).
  • 2026-07-09 세부 분포(UTC): 00:29-00:41 사이 502 20건 중 18건이 09:39(UTC 00:39) 창에 집중, 504 는 00:28-01:03 창에 분산.
text
service:(cupixworks-pano-postprocessor-instance OR cupixworks-pix-genie-preprocessor-instance OR cupixworks-capture-postprocessor-agent OR cupixworks-capture-preprocessor-agent) "HttpError" ("502" OR "504")

Status board 교차 확인: 동시간에 error-sweeper active incident 2026-07-08-svc-cupixworks-api--resource-exhaustion-2 (started 2026-07-08T12:33:46Z, last_event 2026-07-09T06:27:57Z) 가 열려 있음. Rails 백엔드 resource exhaustion 이 upstream 5xx 로 노출된 것으로 판단.

해석: nginx↔Rails 사이 5xx 는 평상시에는 하루 0-2건의 baseline. 이번 event 창(2026-07-09 00:28-01:03 UTC)에는 30분 안에 30건이 여러 agent 서비스에서 관측되어 baseline 대비 최소 10배 이상 spike. 본 클러스터는 그 spike 의 tail 에 속한 이벤트.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 nginx-upstream 사이의 순간적 upstream 실패 (pod 재시작, keepalive reset, worker timeout 등) 응답 body 가 nginx 기본 502 HTML, cupixworks-api Rails 로그에 대응 요청 부재, 인접 요청은 200 동일 창(2026-07-09 00:28-01:03 UTC)에 5xx 30건이 여러 agent 서비스에서 관측 — 순간적이 아니라 지속된 upstream 실패 창 Partial
H2 tesla API 전역 장애 동일 시각(09:38-09:41) 다른 pano GET /panos/{id} 다수가 200 응답, cupixworks-api status:error 에도 관련 500 없음 다른 pano 호출 모두 성공 Rejected
H6 Rails 백엔드 resource exhaustion 로 인한 nginx 상단 5xx spike Status board 상 2026-07-08-svc-cupixworks-api--resource-exhaustion-2 가 동시간에 active, 같은 창 5xx 가 여러 agent 서비스에서 30건 spike (baseline 대비 10배+) 개별 요청에 대한 Rails access log 없음(nginx 상단에서 502) — direct code path 는 확인 불가 Confirmed (revision 1)
H3 SDK retry 미동작 재시도 총합 시간(~31s) 이 마지막 성공(09:38:59) ~ 에러 로그(09:39:34) 사이 35s 와 부합 cupixRetriableRequest 코드 상 502 는 retriable set 에 포함, 정상 동작 Rejected
H4 애플리케이션 코드 버그 (예: pano id 오류, 파라미터 누락) 동일 pano 가 앞뒤 API 호출(200)에서 정상 처리됨, 스택 최상단이 SDK 응답 dispatch 라인이며 클라이언트 검증 실패 아님 Rejected
H5 checkStitched catch 누락으로 노이즈성 실패 resize 태스크 catch 는 HttpError 도 무차별 catch → pano state=Error, checkStitched 내부는 updateStitched 실패만 warn 처리, pano.get 실패는 상위로 전파 이번 이벤트는 실제 upstream 실패라서 노이즈로 단정하기 어려움 Inconclusive

Fix Recommendation#

즉시 조치 (Critical)#

  • 코드 변경 불필요. 다만 이 클러스터는 순수 일회성이 아니라 active incident 2026-07-08-svc-cupixworks-api--resource-exhaustion-2 의 tail event 이므로 해당 incident 의 진행 상황을 우선 추적. 대상 pano 92617986 은 상위 incident 해결(또는 창 종료) 확인 후 재처리(재큐잉).

단기 개선 (1주 이내)#

  • applications/agents/packages/cupix-pano-postprocessor/src/work/resize-work.ts:108-132 checkStitched 관찰: 현재 pano.get 실패는 상위(catch → pano state=Error)로 전파되지만, updateStitched 실패는 warn 만 남기고 무시. 정책 정합성을 위해 pano.get 실패도 warn 후 continue(=stitched 이미 처리로 간주하지 않고 skip) 로 다루는 것이 재발 시 영향 최소화에 유리한지 검토. 근거: 동일 SDK 가 이미 500/502/503/504 를 5회 재시도하므로 catch 시점의 실패는 대체로 pod 레벨 장애이고, 이 pano 만 error 로 격리하는 지금 동작이 오히려 안전할 수도 있음 — 팀 판단 필요.
  • pano id 92617986 재처리 확인 후, 재처리 성공하면 클러스터를 무시 대상(운영 이벤트) 로 태깅.

장기 개선 (재발 방지)#

  • nginx ↔ Rails(unicorn/puma) 사이 502 발생 원인 관측성 강화: cupixworks-api 서비스에 nginx access log(또는 ALB access log) 를 Datadog 으로 수집해 5xx 원인(upstream timeout, connection reset by peer, no live upstreams 등) 을 분류. 현재는 Rails 로그만 있어 nginx-only 실패가 관측 사각지대.
  • pano 단위 실패 재큐잉 자동화: 현재 pano state=Error 로 마킹 후에는 별도 재실행 파이프라인이 없어 보임 (applications/agents/packages/cupix-pano-postprocessor 전역 검색 시 재큐잉 로직 미검출). "SDK 재시도 소진 → 단일 pano error" 케이스가 반복될 경우 자동 재큐잉 워크플로우가 유리.

Monitoring#

  • 추가할 메트릭/알림
    • cupixworks-pix-genie-preprocessor-instance 서비스의 5xx-driven pano error 발생률
    • resize 태스크 실패율 (총 pano 대비 erroredPanoIds 비율) 이 임계치 초과 시 알림

Datadog timeseries widget 쿼리 예시:

text
count:service:cupixworks-pix-genie-preprocessor-instance status:error "resize pano id"
text
count:service:cupixworks-pix-genie-preprocessor-instance status:error "502 Bad Gateway"
text
count:service:cupixworks-pix-genie-preprocessor-instance status:error "PanoPostprocessorService::run"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (재처리만 필요, 코드 변경 불필요)

Revision History#

Revision 1#

Feedback: 최근 nginx ↔ Rails 사이 에러가 얼마나 발생하는지 추가 조사.

판정:

피드백 항목 판정 근거
nginx↔Rails 사이 5xx 최근 빈도 재조사 수용 Datadog 쿼리 "502 Bad Gateway" "nginx" (now-14d) → 70건 (Lambda 51 + agent Node 19). "HttpError" ("500" OR "502" OR "503" OR "504") → 39건 (502: 23, 504: 14, 503: 2). 일자별: 06-26/07-01/07-03/07-06/07-07/07-08 각 1-4건 baseline, 07-09 30건 spike. 07-09 UTC 00:28-01:03 창에 cupixworks-pano-postprocessor-instance, cupixworks-capture-postprocessor-agent, cupixworks-capture-preprocessor-agent, cupixworks-pix-genie-preprocessor-instance, cupixworks-capture-intelligence-agent, cupixworks-any-potree-agent 6개 agent 서비스에서 동시다발 발생.
기존 "일회성 이벤트" 프레이밍 재검토 부분 수용 동일 fingerprint 는 여전히 14일 내 1건이 맞음 (GET /api/v1/panos/92617986 재시도 소진 사례는 pano 92617986 하나뿐). 그러나 nginx↔Rails 5xx 자체는 일회성이 아니라 30분 창의 spike 이므로 상위 Root Cause 문구를 "일회성 upstream 실패" → "upstream 5xx spike 창의 tail event" 로 수정. Status board 상 2026-07-08-svc-cupixworks-api--resource-exhaustion-2 (open, started 2026-07-08T12:33:46Z, last_event 2026-07-09T06:27:57Z) 가 동시간에 active — Rails 백엔드 resource exhaustion 이 원인일 가능성이 높음.

변경 사항:

  • ## Root Cause Summary 수정: "일회성 upstream 오류" 표현을 유지하되, 뒤에 nginx↔Rails 5xx spike 창(2026-07-09 00:28-01:03 UTC) 과 active incident svc:cupixworks-api::resource_exhaustion 상관관계 문단 추가.
  • ### Log Evidence 에 "nginx↔Rails 5xx 최근 빈도" 소절 추가 — 쿼리, 서비스별 카운트, 일자별 카운트, status board 교차 확인, baseline 대비 spike 배수 명시.
  • ## Hypotheses Considered — H1 verdict 를 Confirmed → Partial 로 downgrade. H6 (Rails 백엔드 resource exhaustion) 를 Confirmed 로 추가.
  • ## Fix Recommendation 즉시 조치 문구를 "1회성" → "active incident tail event" 로 수정하고, incident 진행 추적 우선 지시.

추가 조사 내용:

  • Datadog 쿼리 실행: "502 Bad Gateway" "nginx" (now-14d, 70건), "HttpError" ("500" OR "502" OR "503" OR "504") (now-14d, 39건).
  • status-board skill 로 active/recent incident 확인 → 2026-07-08-svc-cupixworks-api--resource-exhaustion-2 (open) 매치.
  • 서비스별/시간대별 aggregation 을 통해 baseline (0-2건/day) 대비 spike (30건/30min) 확인.