ES /docs

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

RCA: PanoPostprocessorService resize pano HttpError 502

Overview#

What Happened#

2026-04-23 11:13:54 UTC에 cupixworks-pano-postprocessor-instance 서비스에서 pano id:82157654의 resize 처리 중 cupixworks-api로의 checkTileUploading API 호출이 HTTP 502 에러로 실패했다. 이는 AWS S3의 일시적인 InternalError로 인해 API 서버가 S3 list_objects_v2 호출에 실패하면서 발생한 것이며, 해당 시간대(11:10~11:14 UTC)에 S3 관련 에러가 집중적으로 발생했다.

Quick Facts#

Field Value
exception.class HttpError
exception.message HTTP request failed
top_frame panoApi.js:545
env production, us-west-2

Timeline#

  1. 11:10:33Z~11:10:45Z — S3 InternalError로 인해 AwsS3Manager::downloadParallel에서 15건 이상의 다운로드 실패 발생
  2. 11:10:58ZMaskWork::maskPanos 에러 (cv2 assertion failed), 이전 처리 실패
  3. 11:11:09Z — S3 업로드 503 (Service Unavailable) 에러 발생
  4. 11:11:34Z~11:13:54Zcupixworks-api에서 pano 82157654 check_tile_uploading 6회 연속 502 응답 (Aws::S3::Errors::InternalError)
  5. 11:13:54.212Z — 본 클러스터 에러: postprocessor가 checkTileUploading 호출 시 502 수신
  6. 11:13:55Z — pano 82157654에 대한 후속 API 호출 200 성공 (S3 복구)
  7. 11:13:56Z — postprocessor terminateService 호출, 강제 종료

Error Log#

Datadog Logs

text
PanoPostprocessorService::run | resize pano id:82157654 | error {"stack":"HttpError: HTTP request failed
    at Request._callback (/tmp/agent/dist/node_modules/@tesla/typescript-node-sdk/api/panoApi.js:545: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":{},"statusCode":502},"body":{},"statusCode":502,"name":"HttpError"}

Impact#

  • Service: cupixworks-pano-postprocessor-instance
  • Team: gad
  • 발생 횟수: 1
  • 최초 발생: 2026-04-23T11:13:54.212Z
  • 최근 발생: 2026-04-23T11:13:54.212Z

pano id:82157654의 resize 처리가 실패했으며, 해당 pano의 상태가 Error로 전환되었다. 단일 pano에 대한 영향이며 전체 capture 작업은 run | end로 종료되었다.

Root Cause Summary#

AWS S3의 일시적인 InternalError가 근본 원인이다. postprocessor의 resize 단계에서 ResizeWork::uploadResizedImage가 resized 이미지를 S3에 업로드한 후 cupixworks-apiPUT /api/v1/panos/82157654/check_tile_uploading 엔드포인트를 호출한다. 이 API는 내부적으로 Cupix::StorageService.object_list를 통해 S3 list_objects_v2를 실행하여 업로드된 타일 객체를 확인하는데, 해당 시점에 S3가 InternalError를 반환하면서 API가 502를 응답했다. postprocessor 측에서는 이 502 에러에 대한 재시도 로직이 없어 즉시 실패 처리되었다.

Technical Analysis#

Code Path#

  • Entry point: pano-postprocessor-service.ts:79run() 메서드 시작
  • Resize 단계 시작: pano-postprocessor-service.ts:164-179cpPanos.map으로 각 pano에 대해 병렬 resize 작업 실행
  • 개별 pano 처리: pano-postprocessor-service.ts:168-170resizePanouploadResizedImagecheckStitched 순차 실행
pano-postprocessor-service.ts:164-179typescript
// resize
start = Date.now();
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!);
        }
    });
});
  • Failure point: resize-work.ts:142checkTileUploading API 호출에서 502 수신
resize-work.ts:138-143typescript
uploadResizedImage = async (cpPano: CPPano): Promise<void> => {
    if (!cpPano.panoId) return;
    const credentials = await this.cupixApi.pano.createTileUploadCredentials(cpPano.panoId);
    await this.awsS3Manager.uploadDirectoryByCredential({ credential: credentials, directory: cpPano.resizeDir!});
    await this.cupixApi.pano.checkTileUploading(cpPano.panoId);
};
  • API 측 호출 체인: pano.api.ts:79-83@tesla/typescript-node-sdkPUT /api/v1/panos/:id/check_tile_uploading
pano.api.ts:79-83typescript
async checkTileUploading(panoId: number, revisionType?: 'enhanced_image'): Promise<TESLA.Pano> {
    const api = await this.api();
    const res = await api.checkTileUploading(panoId, Fields.PanoFields, revisionType as any);
    return unwrapAttributes(res);
}
  • Rails API 처리: tilable_repository/pano.rb:18tile/s3.rb:9-13 — S3 list_objects_v2 호출
app/models/concerns/tile/s3.rb:9-13ruby
def check_tile_uploading!
  self.tile_size = tile_uploading_objects.size if self.has_attribute?(:tile_size)
  raise Cupix::Errors::InvalidState.new(code: 'STAT10000', reason: 'No tile objects found in S3') if tile_size.nil? || tile_size.zero?

  self.uploaded_tile_state!
end
app/models/concerns/tile/s3.rb:16-22ruby
def tile_uploading_objects
  Cupix::StorageService.object_list(
    storage_option: storage_option,
    bucket_name: storage_option.s3_hosting_bucket_name,
    prefix: tile_object_key_base(ver: tile_upload_revision)
  )
end
app/services/cupix/storage_service.rb:64-79ruby
def object_list(storage_option: nil, **kwargs)
  opts = parse_storage_option(storage_option).merge(kwargs)

  check_required_params(opts, %i[region bucket_name prefix])

  client(storage_option: storage_option)
    .list_objects_v2(
      bucket: storage_option.s3_hosting_bucket_name,
      prefix: opts[:prefix],
      delimiter: 'delimiter'
    )
    .contents
    .reject do |object|
      object.key.end_with?('/')
    end
end

실패 흐름 요약: list_objects_v2에서 Aws::S3::Errors::InternalError 발생 → Rails가 500/502 응답 → @tesla/typescript-node-sdkpanoApi.js:545에서 HttpError throw → postprocessor catch 블록에서 pano 상태를 Error로 업데이트

Log Evidence#

postprocessor 서비스 에러 로그 검색:

text
service:cupixworks-pano-postprocessor-instance status:error @environment:production

대상 에러 (11:13:54.212Z):

text
PanoPostprocessorService::run | resize pano id:82157654 | error {"stack":"HttpError: HTTP request failed...","statusCode":502}

S3 다운로드 InternalError (11:10:33Z~11:10:45Z):

text
AwsS3Manager::downloadParallel | begin - "We encountered an internal error. Please try again."

15건 이상 반복 발생 — AWS S3 자체의 일시적 장애 확인.

S3 업로드 503 (11:11:09Z):

text
AwsS3Manager::uploadDirectoryByCredential | upload failed — S3 returned 503 (Service Unavailable)

downstream API (cupixworks-api) 로그 검색:

text
service:cupixworks-api 82157654

API 측 502 응답 6회 연속 (11:11:34Z~11:13:54Z):

text
[502] PUT /api/v1/panos/82157654/check_tile_uploading — Aws::S3::Errors::InternalError
Timestamp Status Endpoint
11:11:34.798Z 502 PUT /api/v1/panos/82157654/check_tile_uploading
11:12:00.823Z 502 PUT /api/v1/panos/82157654/check_tile_uploading
11:12:26.856Z 502 PUT /api/v1/panos/82157654/check_tile_uploading
11:12:54.511Z 502 PUT /api/v1/panos/82157654/check_tile_uploading
11:13:24.572Z 502 PUT /api/v1/panos/82157654/check_tile_uploading
11:13:54.627Z 502 PUT /api/v1/panos/82157654/check_tile_uploading

S3 복구 확인 (11:13:55Z):

text
[200] PUT /api/v1/panos/82157654 — (정상 업데이트)

에러 발생 1초 후 동일 pano에 대한 API 호출이 정상 응답 — S3 장애가 일시적이었음을 확인.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 AWS S3 일시적 InternalError로 인해 API가 502를 반환하고 postprocessor가 실패 API 로그에서 Aws::S3::Errors::InternalError로 6회 연속 502 확인; S3 다운로드/업로드에서도 동시간대 InternalError/503 다수 발생; 1초 후 정상 복구 Confirmed
H2 postprocessor의 resized 이미지 업로드 실패로 S3에 타일 객체가 없어 check_tile_uploading!InvalidState 에러 발생 업로드 단계(uploadDirectoryByCredential)가 checkTileUploading 이전에 실행됨 에러 메시지가 InvalidState가 아닌 HttpError 502이며, API 로그에서 명확히 Aws::S3::Errors::InternalError 확인 — S3 리스트 자체가 실패한 것이지 객체 부재가 아님 Rejected
H3 API 서버 자체의 과부하 또는 배포 문제로 502 발생 같은 시간대에 다른 504/400 에러도 존재 (08:20-08:37 UTC) API 서버가 아닌 S3가 에러 원인임이 로그에서 확인됨; 다른 pano에 대한 API 호출은 정상 동작; 1초 후 즉시 복구 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • resize-work.ts:142: checkTileUploading 호출에 재시도 로직 추가. 502/503/504 등 서버 에러 응답 시 exponential backoff으로 최대 3회 재시도하도록 변경. 현재는 S3 일시 장애 시 즉시 실패하여 pano 상태가 Error로 전환된다.
  • 동일하게 uploadResizedImage 전체 메서드에 대해 재시도 래핑 적용 검토 필요.

단기 개선 (1주 이내)#

  • pano-postprocessor-service.ts:164-179의 resize 단계에서 개별 pano 실패 시 재시도 후에도 실패하면 erroredPanoIds에 추가하되, 일시적 에러(502/503)와 영구적 에러(400/404)를 구분하여 로깅 레벨을 조정. 일시적 에러는 warn 레벨이 적절할 수 있다.
  • @agents/api 패키지 또는 @tesla/typescript-node-sdk 수준에서 공통 HTTP 재시도 미들웨어 도입 검토. 현재 SDK(request 기반)에는 재시도 기능이 없다.

장기 개선 (재발 방지)#

  • Rails API(tile/s3.rb:16-22)의 tile_uploading_objects에서 Aws::S3::Errors::InternalError를 catch하여 재시도 후 실패 시 명확한 에러 코드와 메시지를 반환하도록 개선. 현재는 S3 에러가 unhandled exception으로 502가 되어 클라이언트가 원인을 파악하기 어렵다.
  • request 라이브러리 의존 제거 및 axios 등 재시도 지원 HTTP 클라이언트로 마이그레이션 검토.

Monitoring#

  • S3 관련 에러 빈도 모니터링:
text
service:cupixworks-api "Aws::S3::Errors::InternalError" status:error
  • postprocessor resize 단계 실패율 모니터링:
text
service:cupixworks-pano-postprocessor-instance "resize pano" status:error
  • S3 InternalError가 특정 시간대에 집중되는 패턴이 있는지 일별 추이 확인.

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • AWS S3 InternalError는 일시적이며 자동 복구되는 인프라 이슈. 재시도 로직 추가로 대부분의 재발을 방지할 수 있다. 발생 빈도가 1회이며 1초 후 정상 복구된 점에서 시스템적 위험은 낮다.