VoxelService::run | end - error: "failed to create upload credentials - \"Request failed with status
RCA: VoxelService::run | failed to create upload credentials - 504
Overview#
What Happened#
2026-04-23 08:08~08:10 UTC 사이에 cupixworks-any-voxel-agent 서비스에서 voxel 계산 완료 후 결과 업로드를 위한 S3 credential 요청이 cupixworks-api (tesla) 내부 API로부터 504 Gateway Timeout을 받아 실패했다. 동일 시간대에 cupixworks-api 서비스가 배포/재시작 중이었으며(Sidekiq::Shutdown 로그 확인), 이로 인해 API 전반에 걸쳐 504 응답이 발생했다. 영향받은 capture ID는 685941, 685176이며, 총 2건의 "failed to create upload credentials" 에러가 기록되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Error |
| exception.message | failed to create upload credentials - "Request failed with status code 504" |
| top_frame | voxel.manager.ts:171 (createUploadCredentials) |
| env | production, us-west-2 |
Timeline#
- 08:05:26Z --
cupixworks-apiSidekiq::Shutdown 발생 (ExportWorker, 서비스 재시작 시작) - 08:06:26Z -- Voxel agent가 capture 685941 작업 시작 (entity parameters 로드)
- 08:07:05Z -- Capture 685941 voxel 계산 완료, raw CSV 업로드 시작
- 08:08:05Z -- Capture 685941
voxels_upload_credentials요청 -> 504 응답 -> "failed to create upload credentials" 에러 (1차) - 08:09:53Z -- Capture 685176 voxel 계산 완료, raw CSV 업로드 시작
- 08:10:00Z -- 다수 capture (685954, 685941, 685930)에서 API 504 에러 연쇄 발생
- 08:10:26Z --
cupixworks-api두 번째 Sidekiq::Shutdown 로그 - 08:10:53Z -- Capture 685176
voxels_upload_credentials요청 -> 504 응답 -> "failed to create upload credentials" 에러 (2차) - 08:12:27Z -- API 복구 후 capture 69503 정상 처리 (setSession -> calculateVoxels -> uploadXYPlaneVoxels 성공)
Error Log#
VoxelService::run | end - error: "failed to create upload credentials - \"Request failed with status code 504\""
Impact#
- Service:
cupixworks-any-voxel-agent - 발생 횟수: 2 (upload credentials 에러 기준), 동일 시간대 총 14건의 504 관련 에러
- 최초 발생: 2026-04-23T08:08:05.107Z
- 최근 발생: 2026-04-23T08:10:53.781Z
- 영향 범위: Capture 685941, 685176의 voxel 처리 실패 (upload credentials 504). 추가로 capture 685930, 685954에서도 pano 조회 및 loadRealityCapture 단계에서 504 발생. 총 4개 capture, 4명의 사용자(qatest3 팀 3명, devcon 팀 1명), 6개 호스트에서 에러 발생. Voxel 계산은 완료되었으나 결과물이 S3에 업로드되지 못함.
voxel_state가error로 전환 시도되었으나, 이 상태 업데이트 API 호출도 504로 실패한 경우 있음. CloudFront 레벨에서도OriginCommError504가 다수 tenant에서 동시 발생하여 플랫폼 전반 영향 확인.
Root Cause Summary#
cupixworks-api (tesla) 서비스의 배포 또는 재시작이 08:05~08:11 UTC 시간대에 진행되면서, 내부 ALB(api-tesla.cupix.internal)가 backend 인스턴스 교체 과정에서 504 Gateway Timeout을 반환했다. Voxel agent는 voxel 계산 완료 후 POST /api/v1/captures/{id}/voxels_upload_credentials 엔드포인트를 호출하여 S3 업로드 credential을 발급받는데, 이 시점에 API가 응답 불가 상태여서 504를 받았다. Voxel agent의 axios HTTP 클라이언트는 timeout: 0 (무제한)으로 설정되어 있어 자체 timeout은 없으며, ALB의 기본 60초 idle timeout이 504를 반환한 것으로 판단된다.
agents 공통 모듈에 CupixAuth::retryable (cupix-auth.ts:59-77)이라는 retry 메커니즘이 존재하지만(statusCode > 500 시 최대 5회, exponential backoff), CaptureApi::createVoxelsUploadCredentials (capture.api.ts:178)는 이 retryable wrapper를 사용하지 않는다. 따라서 504 에러 시 retry 없이 즉시 실패하며, VoxelManager::createUploadCredentials의 catch 블록에서 에러를 new Error()로 재포장하여 throw하므로 단일 실패로 전체 작업이 에러 상태로 전환되었다.
Technical Analysis#
Code Path#
Entry point: VoxelService::run -- SQS 메시지를 받아 voxel 계산 및 업로드를 수행하는 메인 메서드.
run = async (targetId: number, msgObject?: any): Promise<void> => {
const targetType = msgObject.type ?? 'capture';
try {
const serverRealityCapture = await this.realityCaptureManager.loadRealityCapture(targetId, targetType);
const cpRealityCapture = this.realityCaptureManager.createCPRealityCapture(serverRealityCapture, targetType);
if (!DEBUG_MODE) await this.realityCaptureManager.updateVoxelState(TESLA.VoxelState.Aggregating);
await this.realityCaptureManager.loadEntityParameters(cpRealityCapture);
await this.realityCaptureManager.loadSubModels(cpRealityCapture);
const result = await this.voxelManager.calculateVoxels(cpRealityCapture);
await this.voxelManager.saveRawVoxels(result, cpRealityCapture.rawVoxelFilePath);
await this.voxelManager.saveXYPlaneVoxels(result, cpRealityCapture.voxelFilePath);
if (!DEBUG_MODE) await this.voxelManager.uploadRawVoxels(cpRealityCapture);
if (!DEBUG_MODE) await this.voxelManager.uploadXYPlaneVoxels(cpRealityCapture); // <- 여기서 실패
} catch (error: any) {
logger.error('VoxelService::run | error', error);
if (!DEBUG_MODE) await this.realityCaptureManager.updateVoxelState(TESLA.VoxelState.Error);
}
};
uploadXYPlaneVoxels 내에서 createUploadCredentials를 호출한다:
uploadXYPlaneVoxels = async (cpRealityCapture: CPRealityCapture): Promise<void> => {
this.awsS3Manager.resetS3Info();
const s3Credentials = await this.createUploadCredentials(cpRealityCapture); // <- 504 발생 지점
if (s3Credentials == undefined
|| s3Credentials.aws_access_key_id == undefined
|| s3Credentials.aws_secret_access_key == undefined
|| s3Credentials.bucket_region == undefined
|| s3Credentials.bucket_name == undefined
|| s3Credentials.basepath == undefined
) {
throw new Error('Invalid S3Credentials');
}
// ...
};
Failure point: createUploadCredentials -- API 호출이 504로 실패하면 에러 메시지를 재포장하여 throw:
private createUploadCredentials = async (cpRealityCapture: CPRealityCapture): Promise<TESLA.UploadCredentials> => {
let s3Credentials: TESLA.UploadCredentials = {};
try {
if (cpRealityCapture.isCapture) {
s3Credentials = await this.cupixApi.capture.createVoxelsUploadCredentials(cpRealityCapture.id);
} else if (cpRealityCapture.isPointcloud) {
s3Credentials = await this.cupixApi.pointcloud.createVoxelsUploadCredentials(cpRealityCapture.id);
} else {
throw new Error('unknown target type');
}
} catch (error: any) {
throw new Error('failed to create upload credentials - ' + JSON.stringify(error.message));
}
return s3Credentials;
};
Downstream API (tesla): voxels_upload_credentials 엔드포인트는 모델의 voxel_state를 uploading으로 전환한 후 STS credential을 발급한다:
def voxels_upload_credentials
self.uploading_voxel_state
Cupix::StorageService.upload_credentials(
storage_option: storage_option,
id: id,
bucket_name: storage_option.s3_hosting_bucket_name,
key: voxels_basepath(voxels_upload_revision)
)
end
이 코드 자체에는 문제가 없으며, API 서버가 정상 가동 중이면 STS credential을 생성하여 반환한다. 문제는 API 서버가 재시작 중이어서 요청이 도달하지 못한 것이다.
Retry 메커니즘 부재: agents 공통 모듈에 retry 로직이 존재하지만, createVoxelsUploadCredentials에는 적용되지 않음:
retryable = <T>(f: () => Promise<T>, retries?: number): Promise<T> => new Promise((resolve, reject) => {
const _retries = retries != undefined ? retries : 0;
f()
.then(response => resolve(response))
.catch(e => {
if (e && e.statusCode > 500 && _retries < Constants.MaxRetries) {
const delay = Math.pow(2, _retries) * 1000;
logger.warn(`CupixAuth::retryable | pathname: ..., code: ${e.statusCode}, try: ${_retries + 1}`);
setTimeout(() => {
this.retryable(f, _retries + 1).then(resolve).catch(reject);
}, delay);
} else {
reject(e);
}
});
});
@SkipInDebug()
async createVoxelsUploadCredentials(captureId: number): Promise<TESLA.UploadCredentials> {
const api = await this.api();
const res = await api.createVoxelsUploadCredentials(captureId, Fields.VoxelFields);
return unwrapResult(res); // retryable()로 감싸지 않음
}
retryable은 e.statusCode > 500을 체크하지만, axios 에러 객체는 e.response.status에 상태 코드를 가지므로 e.statusCode는 undefined가 된다. 설사 retryable로 감싸더라도 현재 구현에서는 retry가 동작하지 않을 수 있다.
Log Evidence#
Datadog 쿼리 1: voxel-agent 에러 로그
service:cupixworks-any-voxel-agent status:error
Time: 2026-04-23T07:08:00Z to 2026-04-23T08:15:00Z
두 건의 upload credentials 504 에러:
{"timestamp": "2026-04-23T08:08:05.107Z", "message": "VoxelService::run | end - error: \"failed to create upload credentials - \\\"Request failed with status code 504\\\"\""}
{"timestamp": "2026-04-23T08:10:53.781Z", "message": "VoxelService::run | end - error: \"failed to create upload credentials - \\\"Request failed with status code 504\\\"\""}
504가 발생한 실제 API URL (warn 로그에서 확인):
CupixAuth::handleError | Response statusCode: 504, requestUriHref: http://api-tesla.cupix.internal/api/v1/captures/685941/voxels_upload_credentials?fields=id%2Cname%2Cvoxel_state%2Cvoxel_state_updated_at, body.result: undefined
CupixAuth::handleError | Response statusCode: 504, requestUriHref: http://api-tesla.cupix.internal/api/v1/captures/685176/voxels_upload_credentials?fields=id%2Cname%2Cvoxel_state%2Cvoxel_state_updated_at, body.result: undefined
동일 시간대에 pano 조회, capture update 등 다양한 API 엔드포인트에서도 504 발생:
CupixAuth::handleError | Response statusCode: 504, requestUriHref: http://api-tesla.cupix.internal/api/v1/panos?...&capture_id=685930
CupixAuth::handleError | Response statusCode: 504, requestUriHref: http://api-tesla.cupix.internal/api/v1/captures/685930?...
CupixAuth::handleError | Response statusCode: 504, requestUriHref: http://api-tesla.cupix.internal/api/v1/captures/685954?...
CupixAuth::handleError | Response statusCode: 504, requestUriHref: http://api-tesla.cupix.internal/api/v1/captures/685941?...
Datadog 쿼리 2: cupixworks-api 서비스 에러
service:cupixworks-api status:error
Time: 2026-04-23T08:05:00Z to 2026-04-23T08:15:00Z
API 서비스에서 Sidekiq::Shutdown 에러 2건 확인 -- 서비스 재시작의 직접적 증거:
[08:05:26Z] Database export model failed: migration id(1252) last step(read_capture) - Sidekiq::Shutdown
[08:10:26Z] Database export model failed: migration id(1252) last step(read_capture) - Sidekiq::Shutdown
복구 증거: 08:12:27Z부터 voxel agent가 정상적으로 API 호출 성공 (capture 69503):
[08:12:27Z] CupixAuth::setSession | session_id: 0a2633095a57a032448298310a5ac088301a1458
[08:12:30Z] VoxelManager::uploadXYPlaneVoxels | s3Credentials bucket_region: ap-southeast-2, bucket_name: cupixworks-hosting-a63f5f96341d-apse2, basepath: c1d779b31b/voxels/apse2/capture/69503/v2
[08:12:30Z] AwsS3Manager::uploadDirectoryToS3 | end
Axios 설정 (에러 로그에서 추출):
{"timeout": 0, "maxContentLength": -1, "maxBodyLength": -1, "method": "put"}
timeout: 0은 axios에서 "무제한"을 의미하며, 자체 timeout 없이 ALB 504를 그대로 수신한다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | API 서비스 배포/재시작으로 인한 일시적 504 | Sidekiq::Shutdown 로그 2건 (08:05, 08:10), 동일 시간대 다수 엔드포인트에서 504, 08:12Z 이후 정상 복구 | -- | Confirmed |
| H2 | voxels_upload_credentials 엔드포인트 자체 버그 (STS 발급 실패) |
-- | 에러가 upload credentials뿐 아니라 pano 조회, capture update 등 모든 API 호출에서 발생; API 서비스 에러 로그에 해당 엔드포인트 관련 에러 없음; 복구 후 정상 작동 | Rejected |
| H3 | Voxel agent의 인증 토큰 만료로 인한 401→504 | -- | 에러 응답이 명확히 504이며, 에러 발생 전후로 다른 API 호출(entity parameters, panos)은 성공; CupixAuth::setSession 로그에서 세션이 정상 설정됨 |
Rejected |
| H4 | 네트워크 인프라 문제 (ALB 장애) | 504는 ALB가 backend로부터 응답을 받지 못할 때 반환하는 코드; CloudFront OriginCommError 동시 발생 | Sidekiq::Shutdown이 원인을 더 구체적으로 설명; 특정 시간대에만 발생하고 자연 복구됨 | Rejected (ALB 자체 장애가 아닌, backend 비가용이 원인) |
| H5 | 기존 CupixAuth::retryable이 retry했으나 5회 모두 실패 |
retryable이 statusCode > 500 체크 시 retry 동작 가능 |
CaptureApi::createVoxelsUploadCredentials가 retryable로 감싸지 않음 (capture.api.ts:178); Datadog warn 로그에 CupixAuth::retryable 경고 메시지 없음 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 없음. 이 에러는 API 배포/재시작 중 발생한 일시적 504이며, 이미 자연 복구되었다. Capture 685941, 685176의 voxel 처리는 SQS 메시지 재시도를 통해 자동 복구 가능한지 확인 필요.
단기 개선 (1주 이내)#
CaptureApi::createVoxelsUploadCredentials(capture.api.ts:178)를 기존CupixAuth::retryablewrapper로 감싸거나,VoxelManager::createUploadCredentials(voxel.manager.ts:160) 레벨에서 retry 로직 추가. 504, 502, 503 등 일시적 서버 에러에 대해 exponential backoff로 재시도하면 배포 중 발생하는 일시적 장애를 자동 복구할 수 있다.CupixAuth::retryable(cupix-auth.ts:64)의e.statusCode > 500조건이 axios 에러 객체의e.response.status와 불일치하는 문제 검토. 다른 API 호출에서도 retry가 의도대로 동작하지 않을 가능성 있음.VoxelService::run의 catch 블록에서updateVoxelState(Error)호출이 역시 504로 실패하는 케이스가 확인됨 (08:10:00Z 로그). Error state 업데이트 실패 시에도 retry 또는 최소 warn 로그를 남기도록 개선 필요.
장기 개선 (재발 방지)#
- Voxel agent의 axios 클라이언트에 적절한 timeout 설정 (
timeout: 30000등). 현재timeout: 0이므로 ALB의 60초 idle timeout까지 무한 대기하며, 장애 감지가 늦어진다. cupixworks-api배포 시 rolling update / graceful shutdown 전략 검토.Sidekiq::Shutdown이 반복 발생하는 것은 배포 과정에서 worker가 강제 종료되고 있음을 의미하며, API 서버(Puma/Unicorn)도 동시에 비가용 상태가 되고 있다.- SQS 메시지의
ApproximateReceiveCount: 1로 보아 재시도가 이루어지지 않았을 수 있음. SQS Dead Letter Queue 설정과 재시도 정책을 확인하여, 일시적 실패 시 메시지가 재처리되도록 보장.
Monitoring#
- Voxel agent의 upload credentials 504 에러 빈도 모니터링:
service:cupixworks-any-voxel-agent "failed to create upload credentials" status:error
- API 서비스 504 발생률 모니터링 (배포 시 스파이크 감지):
service:cupixworks-any-voxel-agent "504" status:(error OR warn)
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial
- API 배포 중 발생한 일시적 장애로, 자연 복구됨. Retry 로직 추가로 재발 시 자동 복구 가능.