TransferManager::failTask - path: /tmp/workspace/678511/original_panos/80668710.jpg, url: http://api
RCA: TransferManager::failTask - pano download 404
Error Log#
TransferManager::failTask - path: /tmp/workspace/678511/original_panos/80668710.jpg, url: http://api-tesla.cupix.internal/api/v1/panos/80668710/download?original=true, count: 5/5
Impact#
- Service:
cupixworks-capture-singleshot-agent - 발생 횟수: 1
- 최초 발생: 2026-04-09T21:20:43.279Z
- 최근 발생: 2026-04-09T21:20:43.279Z
Root Cause Summary#
Pano 80668710의 원본 이미지가 S3에 업로드되지 않은 상태(resource_state: "created", filesize: null)에서 singleshot agent가 해당 pano의 원본 이미지를 다운로드하려고 시도했다. Tesla API(PanosController#download_single_resource)는 resource의 revision을 기반으로 S3 presigned URL을 생성하여 302 redirect를 반환했으나, 실제 S3 객체가 존재하지 않아 404가 반환되었다. CupixAuth::handleError가 S3 404 응답을 구조화되지 않은 에러 객체로 감싸면서 checkStatusCode에서 statusCode를 인식하지 못해, 4xx 에러임에도 5회 재시도 후 최종 실패했다.
Technical Analysis#
Code Path#
- Entry point:
SingleshotService::run(singleshot-service.ts:37) →runAlignScript(singleshot-service.ts:67) runAlignScript은runAlign을 호출 (align/align.module.ts:24)runAlign내에서 각 pano에 대해downloadFile을 호출 (align.module.ts:61-62)- 다운로드 URL:
${cupixAuth.apiUrl}/panos/${panoId}/download?original=true
참고: 현재 develop 브랜치의 align.module.ts는 자체 downloadFile 함수 (line 347-373)를 사용하지만, 프로덕션 배포 버전은 TransferManager를 사용하는 것으로 확인됨 (로그에 TransferManager::failTask, TransferManager::downloadFile 메시지 존재, original_panos 디렉토리 사용). 현재 코드에서는 InputPanoDirName = 'input_panos'를 사용하지만, OriginalPanoDirName = 'original_panos'가 상수로 정의되어 있어 배포된 코드가 이 상수를 사용하는 것으로 판단.
API download flow — Tesla API가 302 redirect를 통해 S3 presigned URL로 전달:
# app/controllers/concerns/single_resourcable/panos_controller.rb:7-11
def download_single_resource
stitched, original = parse_download_params
redirect_to @resource.download_url(ver: @model.get_revision_by_download_option([stitched, original])), allow_other_host: true
end
original=true일 때 revision 계산:
# app/models/concerns/resourcable/pano.rb:106-118
def get_revision_by_download_option(options = [])
stitched = options[0]
original = options[1]
# ...
if original == true
self.initial_revision + 1 # initial_revision=0 → revision=1 → S3 key .../v1
else
nil
end
end
S3 presigned URL 생성:
# app/models/concerns/storagable/resource.rb:185-205
def download_url(opts = {})
ver = opts[:ver] || self.revision
# ...
self.object(ver).presigned_url(:get, expires_in: exp, response_content_disposition: "filename=#{CGI.escape(filename) rescue nil}")
end
TransferManager retry flow (배포된 버전, 현 코드의 preprocessor agent와 동일 패턴):
// transfer.manager.ts:170-172 (preprocessor agent 기준)
private checkStatusCode = (error: any): boolean => {
if (error?.statusCode != undefined && error.statusCode > 400 && error.statusCode < 500) return false;
return true; // statusCode가 undefined이면 retry 허용
};
CupixAuth::handleError가 S3 404 응답을 문자열화된 JSON으로 감싸 에러 객체에 statusCode 프로퍼티가 직접 설정되지 않아 checkStatusCode가 true를 반환, 404임에도 재시도가 진행됨.
Log Evidence#
Datadog 쿼리:
service:cupixworks-capture-singleshot-agent status:error 80668710
Time: 2026-04-09T20:20:43Z to 2026-04-09T21:50:43Z
다운로드 시도 타임라인 (6회 시도, ~10초 간격으로 재시도):
06:19:51 KST - TransferManager::downloadFile - response path: /tmp/workspace/678511/original_panos/80668710.jpg, code: 404, message: Not Found
06:20:02 KST - TransferManager::downloadFile - response path: /tmp/workspace/678511/original_panos/80668710.jpg, code: 404, message: Not Found
06:20:12 KST - TransferManager::downloadFile - response path: /tmp/workspace/678511/original_panos/80668710.jpg, code: 404, message: Not Found
06:20:22 KST - TransferManager::downloadFile - response path: /tmp/workspace/678511/original_panos/80668710.jpg, code: 404, message: Not Found
06:20:33 KST - TransferManager::downloadFile - response path: /tmp/workspace/678511/original_panos/80668710.jpg, code: 404, message: Not Found
06:20:43 KST - TransferManager::downloadFile - response path: /tmp/workspace/678511/original_panos/80668710.jpg, code: 404, message: Not Found
06:20:43 KST - TransferManager::failTask - path: /tmp/workspace/678511/original_panos/80668710.jpg, url: http://api-tesla.cupix.internal/api/v1/panos/80668710/download?original=true, count: 5/5
API 측 로그 — 모든 요청에 302 redirect 정상 반환:
Datadog query: service:cupixworks-api "80668710"
Time: 2026-04-09T20:20:43Z to 2026-04-09T21:50:43Z
06:19:52 KST - [302] GET /api/v1/panos/80668710/download (Api::V1::PanosController#download_single_resource)
06:20:03 KST - [302] GET /api/v1/panos/80668710/download (Api::V1::PanosController#download_single_resource)
06:20:12 KST - [302] GET /api/v1/panos/80668710/download (Api::V1::PanosController#download_single_resource)
06:20:24 KST - [302] GET /api/v1/panos/80668710/download (Api::V1::PanosController#download_single_resource)
06:20:33 KST - [302] GET /api/v1/panos/80668710/download (Api::V1::PanosController#download_single_resource)
06:20:43 KST - [302] GET /api/v1/panos/80668710/download (Api::V1::PanosController#download_single_resource)
S3 404 에러 상세 (CupixAuth::handleError 로그에서):
{
"statusCode": 404,
"headers": {
"server": "AmazonS3",
"x-amz-request-id": "308Q51CF0EVPZ140"
},
"request": {
"uri": {
"pathname": "/cupixworks-source-dc9dcff32488-usea1/resources/oty8x8/usea1/v1",
"hostname": "s3.amazonaws.com"
},
"method": "GET",
"headers": {
"referer": "http://api-tesla.cupix.internal/api/v1/panos/80668710/download?original=true"
}
}
}
S3 경로 resources/oty8x8/usea1/v1에 객체가 존재하지 않음.
Kibana 데이터베이스 확인:
- Pano 80668710:
state: "created",resource_state: "created",filesize: null— 원본 이미지가 S3에 업로드되지 않음 - Capture 678511:
state: "done",singleshot_state: "stopped",creation_platform: "app",stat.size.pano_original_images: 0 - Capture의
editing_state: "escalated"— 문제로 인해 에스컬레이션됨
Fix Recommendation#
즉시 조치 (Critical)#
- 파일:
app/controllers/concerns/single_resourcable/panos_controller.rb:7-11(tesla) download_single_resource메서드에서@resource의 상태를 확인하여, resource가 아직 업로드되지 않은 상태(created)일 때 404 또는 적절한 에러 응답을 반환하도록 변경. 현재는revision == 0일 때만 체크하지만 (single_resourcable_controller.rb:11-13), revision이 계산된 값(initial_revision + 1)일 때 실제 S3 객체 존재 여부와 무관하게 302 redirect를 반환함.- 파일: TransferManager의
checkStatusCode로직 (배포된 singleshot agent 버전) CupixAuth::handleError가 반환하는 에러 객체에서statusCode를 올바르게 추출하도록 수정. 현재 S3 404 응답이 문자열로 감싸져statusCode가 인식되지 않아 불필요한 재시도가 발생.
단기 개선 (1주 이내)#
- Singleshot agent의 pano 다운로드 전에 pano의
resource_state를 API를 통해 확인하고, 업로드 완료되지 않은 pano는 건너뛰거나 적절한 에러 처리를 하도록 로직 추가. download_single_resource에서 S3 presigned URL을 생성하기 전에 해당 revision의 객체가 실제로 존재하는지 확인하는 방어 로직 추가 (또는 resource state가done인지 검증).
장기 개선 (재발 방지)#
- Pano 리소스 업로드 파이프라인에서 업로드 완료 여부를 보장하는 상태 머신 검증 강화.
resource_state: "created"인 pano가 processing pipeline에 진입하지 않도록 capture의 processing 시작 조건에 리소스 상태 체크 추가. - Agent의 에러 핸들링 통합 —
CupixAuth::handleError가 반환하는 에러 객체에 항상statusCode를 포함하도록 표준화하여,checkStatusCode가 4xx 에러를 올바르게 식별하고 불필요한 재시도를 방지.
Monitoring#
resource_state: "created"인 pano에 대한 다운로드 시도를 감지하는 모니터 추가:
service:cupixworks-capture-singleshot-agent status:error "TransferManager::failTask" "404"
- Pano 리소스 상태 이상 감지:
service:cupixworks-api "download_single_resource" status:error
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard