RealityCaptureManager::loadRealityCapture | end - "Request failed with status code 504"
RCA: RealityCaptureManager::loadRealityCapture | 504 Gateway Timeout
Overview#
What Happened#
2026-04-23 17:10 KST 경, cupixworks-any-voxel-agent 서비스에서 Tesla API(api-tesla.cupix.internal)에 대한 HTTP 요청이 504 Gateway Timeout으로 실패했다. loadRealityCapture 호출뿐 아니라 동일 시간대에 panos 조회, voxel upload credentials 생성, updateVoxelState 등 다수의 API 호출이 동시에 504를 반환하여, 내부 로드 밸런서/게이트웨이의 일시적 장애로 판단된다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Error (axios) |
| exception.message | Request failed with status code 504 |
| top_frame | reality_capture.manager.ts:63 (catch block) |
| env | production, us-west-2 |
Timeline#
- 2026-04-23 17:08:05 KST — 최초 504 에러 발생 (
VoxelService::run— upload credentials 실패) - 2026-04-23 17:09:05 KST — capture 685941에 대한 504 발생
- 2026-04-23 17:10:00 KST — 다수 capture에서 동시 504 에러 (685941, 685954 등).
loadRealityCapture504 에러 포함 - 2026-04-23 17:10:00 KST —
updateVoxelStatecascade 에러 (realityCaptureId가 undefined) - 2026-04-23 17:11:53 KST — 마지막 504 에러 (capture 685176 PUT 요청)
- 2026-04-23 17:54:10 KST — 정상 동작 회복 (
loadEntityParametersinfo 로그 재개)
Error Log#
RealityCaptureManager::loadRealityCapture | end - "Request failed with status code 504"
Impact#
- Service:
cupixworks-any-voxel-agent - 발생 횟수: 1 (클러스터 기준, 실제 504 에러는 동일 시간대에 최소 10건 이상)
- 최초 발생: 2026-04-23T08:10:00.543Z
- 최근 발생: 2026-04-23T08:10:00.543Z
- 영향 범위: 다수 capture ID (685176, 685930, 685941, 685954 등)에 대한 voxel 처리가 실패.
updateVoxelStatecascade 에러로 인해 voxel 상태가 error로 업데이트되지 않은 채 남았을 가능성 있음.
Root Cause Summary#
내부 로드 밸런서/게이트웨이(api-tesla.cupix.internal)가 약 4분간(17:08~17:12 KST) 일시적으로 504 Gateway Timeout을 반환했다. Voxel agent의 API 호출 계층(CaptureApiModule.get(), PanoApi.getAll() 등)에는 retry 로직이 적용되어 있지 않아, 단일 504 응답에 즉시 실패하고 전체 voxel 처리 파이프라인이 중단되었다. CupixAuth에 retryable() 메서드가 구현되어 있으나, 어떤 API 모듈에서도 호출하지 않아 dead code 상태이다. 또한 loadRealityCapture 실패 후 catch 블록에서 updateVoxelState(Error) 호출 시, _realityCaptureId가 아직 할당되지 않은 상태여서 "undefined reality capture id or type" cascade 에러가 발생했다.
Technical Analysis#
Code Path#
1. Entry point — VoxelService::run (voxel-service.ts:45-67)
SQS 메시지에서 targetId와 targetType을 추출한 후 loadRealityCapture를 호출한다.
run = async (targetId: number, msgObject?: any): Promise<void> => {
const targetType = msgObject.type ?? 'capture';
try {
const serverRealityCapture = await this.realityCaptureManager.loadRealityCapture(targetId, targetType);
// ... 후속 처리
} catch (error: any) {
logger.error('VoxelService::run | error', error);
if (!DEBUG_MODE) await this.realityCaptureManager.updateVoxelState(TESLA.VoxelState.Error);
}
};
2. API call — CaptureApiModule.get() (capture.api.ts:69-73)
loadRealityCapture가 내부적으로 this.cupixApi.capture.get(realityCaptureId)를 호출한다. 이 메서드에는 retry 래핑이 없다.
get = async (id: number): Promise<TESLA.Capture> => {
const api = await this.api();
const res = await api.getCapture(id, Fields.CaptureFields);
return unwrapAttributes(res);
};
3. Error handling — loadRealityCapture catch (reality_capture.manager.ts:63-66)
504 에러가 catch 블록에서 포착되어 JSON.stringify(ec.message) 형태로 로깅 후 reject된다.
.catch(ec => {
logger.error('RealityCaptureManager::loadRealityCapture | end - %s', JSON.stringify(ec.message));
reject(ec);
});
4. Cascade failure — updateVoxelState (reality_capture.manager.ts:81-86)
loadRealityCapture가 실패하면 _realityCaptureId가 할당되지 않은 상태에서 VoxelService::run의 catch 블록이 updateVoxelState(Error)를 호출한다. _realityCaptureId가 undefined이므로 guard 조건에 걸려 별도 에러가 발생한다.
updateVoxelState = async (state: TESLA.VoxelState): Promise<void> => {
if (!this.realityCaptureId || !this.realityCaptureType) {
logger.error('RealityCaptureManager::updateVoxelState | end - undefined reality capture id or type');
throw new Error('Invalid realityCaptureId or realityCaptureType');
}
// ...
};
5. Unused retry logic — CupixAuth::retryable (cupix-auth.ts:59-77)
5xx 에러에 대해 최대 5회까지 exponential backoff(1s, 2s, 4s, 8s, 16s)로 재시도하는 retryable() 메서드가 존재하지만, API 모듈 어디서도 호출하지 않는 dead code이다.
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: ${pathName}, code: ${e.statusCode}, try: ${_retries + 1} - run after ${delay / 1000} seconds`);
setTimeout(() => {
this.retryable(f, _retries + 1).then(resolve).catch(reject);
}, delay);
} else {
reject(e);
}
});
});
6. No timeout — getApi (tesla-api.ts:5-17)
API 인스턴스 생성 시 timeout 설정이 없다. axios의 timeout: 0(무제한)이 기본값으로 사용되어, 게이트웨이가 504를 반환할 때까지 무한 대기한다.
export const getApi = async <TApi extends { defaultHeaders?: any; setApiKey?: (k: any, v: string) => void }>(
auth: CupixAuth,
ApiCtor: new (basePath: string) => TApi,
apiKeyEnum: any
): Promise<TApi> => {
await auth.checkToken();
const api = new ApiCtor((auth as any).apiUrl);
(api as any).defaultHeaders = { 'User-Agent': CPX_AGENT_NAME, ...auth.customHeaders };
// timeout 설정 없음
return api;
};
Log Evidence#
Datadog 검색 쿼리:
service:cupixworks-any-voxel-agent status:warn "CupixAuth"
Time range: 2026-04-23T08:05:00Z to 2026-04-23T08:15:00Z
504 에러를 받은 API 엔드포인트 목록 (handleError 로그):
{"timestamp": "2026-04-23 17:09:05 KST", "endpoint": "PUT /api/v1/captures/685941", "status": 504}
{"timestamp": "2026-04-23 17:10:00 KST", "endpoint": "GET /api/v1/captures/685941", "status": 504}
{"timestamp": "2026-04-23 17:10:00 KST", "endpoint": "GET /api/v1/captures/685954", "status": 504}
{"timestamp": "2026-04-23 17:10:21 KST", "endpoint": "GET /api/v1/panos?capture_id=685930", "status": 504}
{"timestamp": "2026-04-23 17:10:23 KST", "endpoint": "GET /api/v1/panos?capture_id=685930", "status": 504}
{"timestamp": "2026-04-23 17:10:53 KST", "endpoint": "POST /api/v1/captures/685176/voxels_upload_credentials", "status": 504}
{"timestamp": "2026-04-23 17:11:21 KST", "endpoint": "PUT /api/v1/captures/685930", "status": 504}
{"timestamp": "2026-04-23 17:11:23 KST", "endpoint": "PUT /api/v1/captures/685930", "status": 504}
{"timestamp": "2026-04-23 17:11:53 KST", "endpoint": "PUT /api/v1/captures/685176", "status": 504}
BaseService::handlingMessageErrors 상세 로그 (504 PUT 요청 실패 — voxel_state 업데이트 시도):
{
"error": {
"message": "Request failed with status code 504",
"config": {
"timeout": 0,
"method": "put",
"data": "{\"voxel_state\":\"error\"}",
"url": "http://api-tesla.cupix.internal/api/v1/captures/685176?fields=id,name,state,meta,record,level,bim_icp_tm,use_bim_icp_tm,voxel_state,voxels_result_urls,created_at"
},
"status": 504
}
}
cupixworks-api 측 504 에러 검색 결과:
service:cupixworks-api status:error "504"
Time range: 2026-04-23T08:05:00Z to 2026-04-23T08:15:00Z
Result: 0 logs
API 서비스 자체에는 504 에러 로그가 없으므로, 504는 API 서버가 아닌 중간 로드 밸런서/게이트웨이에서 발생한 것으로 확인된다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 내부 로드 밸런서/게이트웨이(api-tesla.cupix.internal)의 일시적 장애로 504 발생 |
동일 시간대에 다수 capture ID 및 다양한 API 엔드포인트(GET captures, GET panos, POST upload_credentials, PUT captures)에서 동시에 504 발생. cupixworks-api 서비스 자체에는 504 에러 로그 없음. 약 4분 후 자동 회복 |
— | Confirmed |
| H2 | Tesla API 서버의 과부하 또는 다운타임으로 인한 504 | 504 응답이 다수 발생한 시간대와 일치 | cupixworks-api 서비스에서 504 관련 에러 로그가 전혀 없음. API 서버가 직접 504를 반환했다면 해당 서비스 로그에 에러가 기록됐을 것 |
Rejected |
| H3 | 네트워크 이슈 (voxel agent와 API 간) | 동일 시간대 다수 요청 실패 | 504 Gateway Timeout은 네트워크 연결 실패와 다름 (connection refused, timeout은 다른 에러 메시지 생성). 또한 모든 요청이 동일한 api-tesla.cupix.internal 호스트를 통해 실패 |
Rejected |
| H4 | updateVoxelState cascade 에러가 root cause |
updateVoxelState 에러 로그 존재 |
updateVoxelState는 loadRealityCapture 실패 후 catch 블록에서 호출됨. _realityCaptureId가 설정 전이라 발생한 secondary failure임 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
-
VoxelService::runcatch 블록에서updateVoxelState호출 전 guard 추가 (voxel-service.ts:65)loadRealityCapture가 실패한 경우_realityCaptureId가 undefined이므로,updateVoxelState호출이 불필요한 cascade 에러를 발생시킨다. catch 블록에서realityCaptureManager.realityCaptureId가 존재하는지 확인 후에만updateVoxelState를 호출해야 한다.
-
CaptureApiModule.get()및 주요 API 호출에retryable()적용 (capture.api.ts:69-73)- 이미
CupixAuth에 구현된retryable()메서드를get(),update()등 주요 API 호출에 래핑하여 5xx 에러에 대한 자동 재시도를 활성화해야 한다.
- 이미
단기 개선 (1주 이내)#
-
모든 API 모듈(
CaptureApiModule,PanoApiModule,PointcloudApiModule등)에 일괄적으로retryable()적용BaseApiModule에 protected 래퍼 메서드를 추가하여 하위 모듈에서 일관되게 retry를 적용할 수 있도록 한다.
-
HTTP client에 timeout 설정 추가 (
tesla-api.ts:11)- 현재 axios
timeout: 0(무제한)이 기본값이다. 30~60초 정도의 적절한 timeout을 설정하여 게이트웨이 장애 시 빠른 실패를 유도해야 한다.
- 현재 axios
장기 개선 (재발 방지)#
-
retryable()로직의statusCode검사 방식 개선 (cupix-auth.ts:64)- 현재
e.statusCode > 500조건은 axios 에러 객체의response.status와 다른 경로이다. axios는e.response.status에 상태 코드를 저장하므로,e.statusCode가 undefined일 경우 retry가 동작하지 않을 수 있다. axios 에러 구조에 맞게e.response?.status도 확인하도록 수정 필요.
- 현재
-
SQS 메시지 재처리 전략 검토
- 504 같은 일시적 장애 시 SQS 메시지가 삭제되지 않고 visibility timeout 후 자동 재처리되도록
getApiErrorToDeleteMessage의 로직을 확인. 현재 504는statusCode >= 400 && statusCode <= 500범위 밖이므로 메시지가 삭제되지 않아 재처리되지만, 이 동작이 의도된 것인지 명시적 문서화 필요.
- 504 같은 일시적 장애 시 SQS 메시지가 삭제되지 않고 visibility timeout 후 자동 재처리되도록
Monitoring#
- 504 에러 발생 빈도 모니터링:
service:cupixworks-any-voxel-agent status:error "504"
retryable()사용 후 retry 횟수 추적:
service:cupixworks-any-voxel-agent status:warn "CupixAuth::retryable"
- cascade
updateVoxelState에러 모니터링:
service:cupixworks-any-voxel-agent "updateVoxelState" "undefined reality capture id or type"
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard —
retryable()래핑은 기존 코드 활용, guard 추가는 1-2줄 변경