TransferManager::uploadFile | response path: /tmp/workspace/684428/videos/644427/original/644427_000
RCA: TransferManager::uploadFile | 502 Bad Gateway
Overview#
What Happened#
2026-04-21 18:19:34 UTC에 cupixworks-capture-postprocessor-agent 서비스에서 capture 684428의 video 644427 pano 이미지(644427_00033.jpg)를 업로드하는 중 HTTP 502 Bad Gateway 응답을 수신했다. 동일 시간대에 eu-central-1 리전에서도 다수의 upload 실패(500 Internal Server Error, ETIMEDOUT)가 발생하여, 업스트림 스토리지/게이트웨이 계층의 일시적 장애가 의심된다.
Quick Facts#
| Field | Value |
|---|---|
| exception.message | TransferManager::uploadFile | response path: /tmp/workspace/684428/videos/644427/original/644427_00033.jpg, code: 502, message: Bad Gateway |
| top_frame | transfer.manager.ts:99 |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| nhc-sa (capture postprocessor) | 1 | capture 684428 pano 업로드 실패. 재시도 메커니즘(최대 5회)이 존재하나, 최종 성공 여부는 로그에서 확인 불가 |
| innovo (capture postprocessor, eu-central-1) | 12 | 동일 시간대 captures 33890/33892/33893에서 500, ETIMEDOUT 업로드 에러 다수 발생 |
Timeline#
- 18:16:58 UTC — host ip-10-1-37-70에서 workspace 684428 cleanup 실행 (info 로그)
- 18:17:19 UTC — host ip-10-1-109-237에서 workspace 684428 cleanup 실행 (info 로그)
- 18:19:34.996 UTC — host ip-10-1-171-116에서
644427_00033.jpg업로드 시 502 Bad Gateway 수신 (error 로그) - 18:19:34.997 UTC —
CupixAuth::handleError에서 502 PUT 응답 경고 (warn 로그) - 18:21:11 UTC — host ip-10-1-171-116에서 workspace 684428 cleanup 실행 (info 로그)
Error Log#
TransferManager::uploadFile | response path: /tmp/workspace/684428/videos/644427/original/644427_00033.jpg, code: 502, message: Bad Gateway
Impact#
- Service:
cupixworks-capture-postprocessor-agent - Team: nhc-sa
- 발생 횟수: 1
- 최초 발생: 2026-04-21T18:19:34.996Z
- 최근 발생: 2026-04-21T18:19:34.996Z
Root Cause Summary#
업스트림 스토리지 서비스(Tesla API 또는 로드밸런서/리버스 프록시 계층)가 PUT 업로드 요청에 대해 일시적으로 502 Bad Gateway를 반환했다. 동일 시간대에 eu-central-1 리전에서도 다수의 500 Internal Server Error 및 ETIMEDOUT 에러가 발생한 것으로 보아, 업스트림 서비스의 일시적 장애 또는 과부하가 근본 원인으로 판단된다. TransferManager의 uploadFile 메서드는 presigned URL로 HTTP PUT 요청을 전송하며, 502 응답 수신 시 CupixAuth::handleError를 통해 에러를 로깅하고 reject한다. 이후 retryTask에서 재시도가 가능하나(502는 checkStatusCode에서 retryable로 판정), 재시도 성공 여부는 error 레벨 로그에는 기록되지 않아 확인이 불가하다.
Technical Analysis#
Code Path#
- Entry point:
PostprocessorService::run()→FinalizationService::start()→uploadPanosFromVideo()→TransferManager::addTask() - Upload 실행:
TransferManager::transferTask()→uploadFile()(transfer.manager.ts:81) - Failure point:
uploadFile()(transfer.manager.ts:99) — 응답 statusCode가 200이 아닌 502를 수신
uploadFile 메서드는 request 라이브러리의 PUT 요청으로 파일을 스트리밍 업로드한다. 응답이 200이 아니면 error 로그를 출력하고 cupixAuth.handleError(res)를 호출하여 reject한다:
uploadFile = (url: string, path: string, headers: any): Promise<void> => new Promise((resolve, reject) => {
const fileSize = CPUtils.getFileSize(path);
headers = { ...headers, 'Content-Length': fileSize };
logger.debug('TransferManager::uploadFile | begin path: %s, url: %s, size: %d', path, url, fileSize);
const fileStream = fs.createReadStream(path);
const sendReq = request.put(url, {
headers: headers,
body: fileStream
});
sendReq
.on('response', async res => {
if (res.statusCode === 200) {
logger.debug('TransferManager::uploadFile | complete path: %s', path);
await CPUtils.sleep(100);
resolve();
} else {
logger.error('TransferManager::uploadFile | response path: %s, code: %d, message: %s', path, res.statusCode, res.statusMessage);
reject(this.cupixAuth.handleError(res));
}
})
.on('error', err => {
logger.error('TransferManager::uploadFile | path: %s, error: %s', path, JSON.stringify(err));
reject(this.cupixAuth.handleError(err));
});
});
CupixAuth::handleError는 에러 객체의 response 속성을 찾으려 하지만, request 라이브러리의 응답 객체는 response가 아닌 최상위에 statusCode를 가지므로 "Undefined response" 경로로 진입한다:
handleError = (ec: any): any => {
const response = ec && CPUtils.isJsonString(ec) ? JSON.parse(ec) : ec.response;
if (response != undefined) {
const statusCode = response.status || response.statusCode;
const requestUriHref = response.config?.url || response.request?.uri?.href;
const bodyResult = response.data?.result || response.body?.result;
if (statusCode != undefined && this.setErrorCode != undefined)
this.setErrorCode(ErrorCode.Tesla.statusCode(statusCode.toString()));
logger.warn('CupixAuth::handleError | Response statusCode: %d, requestUriHref: %s, body.result: %s',
statusCode, requestUriHref, JSON.stringify(bodyResult));
} else {
logger.warn('CupixAuth::handleError | Undefined response: %s',
JSON.stringify(ec, Object.getOwnPropertyNames(ec)));
}
return ec;
};
재시도 로직: checkStatusCode는 400 < code < 500인 경우만 재시도를 차단하므로, 502는 retryable이다. 최대 5회, 10초 간격으로 재시도한다:
private checkStatusCode = (error: any): boolean => {
if (error?.statusCode != undefined && error.statusCode > 400 && error.statusCode < 500) return false;
return true;
};
retryTask = (task: BaseTask, error: any): void => {
if (this.checkStatusCode(error) && task.isRetry()) {
const idx = this._transferring.indexOf(task);
this._transferring.splice(idx, 1);
setTimeout(() => {
task.retry()
.then(() => {
this.addTask(task);
})
.catch(error => {
this.retryTask(task, error);
});
}, Constants.RetryInterval);
} else {
this.failTask(task, error);
}
};
Cleanup 흐름: cleanUpAnythingRelatedModel은 runByMessage에서 run() 성공 후에만 호출된다. 그러나 동일 capture에 대해 다른 호스트들이 이미 cleanup을 실행한 로그가 관찰되었다 (18:16:58, 18:17:19):
try {
await TraceUtils.activateSpan(span, async () => {
this.setLogMeta(msgObject);
logger.info('BaseService::runByMessage | id: %d', targetId);
await this.authenticateByMessage(msgObject);
await this.run(targetId, msgObject);
await this.cleanUpAnythingRelatedModel(); // run() 성공 후에만 실행
await this.deleteByMessage(message);
Log Evidence#
Datadog에서 다음 쿼리들로 검색하였다:
쿼리 1: TransferManager::uploadFile 에러 로그
service:cupixworks-capture-postprocessor-agent status:error "TransferManager::uploadFile"
14건의 에러 로그가 발견되었다. 대상 인시던트(capture 684428, 502 Bad Gateway) 1건 외에, eu-central-1에서 500 Internal Server Error 10건, ETIMEDOUT 2건이 동일 시간대에 발생하였다.
쿼리 2: 502 관련 로그
service:cupixworks-capture-postprocessor-agent "502"
2건 발견:
[2026-04-21T18:19:34.996Z] [error] TransferManager::uploadFile | response path: /tmp/workspace/684428/videos/644427/original/644427_00033.jpg, code: 502, message: Bad Gateway
[2026-04-21T18:19:34.997Z] [warn] CupixAuth::handleError | Undefined response: {"statusCode":502,"request":{"method":"PUT"}}
쿼리 3: workspace 684428 관련 로그
service:cupixworks-capture-postprocessor-agent (684428 OR 644427)
4건 발견 — cleanup이 3개의 서로 다른 호스트에서 실행됨:
[18:16:58.116Z] [info] ip-10-1-37-70 | BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/684428
[18:17:19.394Z] [info] ip-10-1-109-237 | BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/684428
[18:19:34.996Z] [error] ip-10-1-171-116 | TransferManager::uploadFile | ... code: 502 ...
[18:21:11.708Z] [info] ip-10-1-171-116 | BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/684428
쿼리 4: 다른 서비스의 502 에러
status:error "502 Bad Gateway"
다른 서비스에서 502 에러가 발견되지 않아, 502의 출처는 Datadog에 로그를 전송하지 않는 로드밸런서 또는 스토리지 게이트웨이 계층으로 추정된다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 업스트림 스토리지/게이트웨이 계층의 일시적 장애로 502 반환 | 동일 시간대 eu-central-1에서 500/ETIMEDOUT 다수 발생 (14건); 다른 서비스에서 502 로그 없음 → 로드밸런서/프록시 레벨 장애; us-west-2에서 단발성 1건 | 업스트림 서비스의 직접 로그 확인 불가 | Confirmed |
| H2 | 동일 capture에 대한 다중 agent 처리로 인한 race condition (workspace cleanup 중 업로드 시도) | 3개 호스트에서 workspace 684428 cleanup 로그가 관찰됨; cleanup이 업로드 에러보다 2분 먼저 발생 | cleanup은 각 호스트의 로컬 /tmp/workspace/ 경로를 삭제하므로 다른 호스트의 파일에 영향 없음; 502는 서버 측 에러이지 파일 미존재 에러가 아님 (파일 미존재 시 ENOENT 발생) |
Rejected |
| H3 | presigned URL 만료로 인한 업로드 실패 | presigned URL은 유효기간이 있어 만료 가능 | URL 만료 시 일반적으로 403 Forbidden 반환, 502가 아님; checkStatusCode는 403을 non-retryable로 처리 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 없음. 이 에러는 업스트림 서비스의 일시적 장애로 인한 것이며, 기존 재시도 로직(5회, 10초 간격)이 이미 존재한다. 1건만 발생했으므로 긴급 수정은 불필요하다.
단기 개선 (1주 이내)#
- 재시도 결과 로깅 추가 (
transfer.manager.ts:173-191): 현재 재시도 시작은 debug 레벨로 로깅되지만, 재시도 성공/실패 최종 결과를 info/warn 레벨로 로깅하면 모니터링 가시성이 향상된다. 최종failTask호출 시 총 시도 횟수와 각 시도의 에러 코드를 포함하면 재시도 후에도 실패한 경우를 추적할 수 있다. CupixAuth::handleError개선 (cupix-auth.ts:42-57):request라이브러리의 응답 객체는ec.response가 아닌ec자체에statusCode를 가진다. 현재 "Undefined response" 경로로 빠져 statusCode와 request URL이 구조화되지 않은 JSON dump로만 기록된다.ec.statusCode를 직접 확인하는 분기를 추가하면 보다 유용한 경고 로그를 남길 수 있다.
장기 개선 (재발 방지)#
- Exponential backoff 도입: 현재 고정 10초 간격 재시도 대신, 지수 백오프(예: 10s, 20s, 40s...)를 적용하면 업스트림 과부하 시 재시도 부하를 줄일 수 있다.
- 업로드 메트릭 수집: 업로드 성공률, 재시도 횟수, 에러 코드별 분포를 Datadog 메트릭으로 전송하면 업스트림 서비스 상태를 모니터링할 수 있다.
Monitoring#
- 업로드 실패율 알림:
service:cupixworks-capture-postprocessor-agent status:error "TransferManager::uploadFile"
일정 시간 내 threshold 이상 발생 시 알림 설정 (예: 5분 내 10건 이상)
- 502 에러 전용 모니터:
service:cupixworks-capture-postprocessor-agent "code: 502"
- 멀티 리전 업로드 실패 비교 대시보드: us-west-2, eu-central-1 등 리전별 업로드 에러율을 비교하여 특정 리전의 업스트림 문제를 조기 감지
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial
- 근거: 단발성 502로 기존 재시도 메커니즘이 동작함. 동일 시간대 eu-central-1에서 더 많은 에러가 발생했으나, 업스트림 일시적 장애로 자체 해소된 것으로 판단된다. 재발 시에도 5회 재시도로 대부분 복구 가능하다.