[CupixAerialMap] preprocess fail {"detail":"Request was throttled. Expected available in 1 second."}
RCA: [CupixAerialMap] preprocess fail - Request was throttled
Overview#
What Happened#
2026-04-24 08:05:45 UTC에 aerial-map-service의 preprocess Fargate 컨테이너에서 Pix4D 외부 API 호출 중 HTTP 429 (rate limit) 응답을 수신하여 aerial_map 356 전처리가 실패했다. 동일 시간대에 여러 aerial map이 동시에 처리되고 있었으며, 356만 throttling에 의해 실패하고 나머지(353-355, 357, 359-361)는 정상 완료되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.message | Request was throttled. Expected available in 1 second. |
| top_frame | preprocess/index.ts:148 |
| runtime | Node.js (ECS Fargate) |
| env | production, us-west-2 |
Timeline#
- 08:03:41Z -- Preprocess Fargate 컨테이너 시작 (aerial_map 356, 메모리 2048 MB)
- 08:03:46Z -- Pix4D 프로젝트 생성:
cupix-production-qatest3-356-1777017826383 - 08:05:45.248Z -- Pix4D API 호출 시 HTTP 429 응답 수신, preprocess 실패
- 08:05:45.331Z -- 동일 에러의 재포맷 로그 출력 (catch 블록의 두 번째 로그)
- 08:06:42Z -- Step Functions가
States.TaskFailed감지 (ECS 컨테이너 exit code 1) - 08:06:43Z -- API 콜백:
/api/v1/aerial_maps/356/processing상태 업데이트
Error Log#
[CupixAerialMap] preprocess fail {"detail":"Request was throttled. Expected available in 1 second."}
Impact#
- Service:
aerial-map-service - 발생 횟수: 1 (관련 클러스터 0a3a347a 포함 시 2건)
- 최초 발생: 2026-04-24T08:05:45.248Z
- 최근 발생: 2026-04-24T08:05:45.248Z
Root Cause Summary#
Pix4D 외부 API가 HTTP 429 (Too Many Requests) 응답을 반환했으나, aerial-map-service의 axios retry interceptor가 429 상태 코드를 retryable 조건에 포함하지 않아 즉시 실패로 처리되었다. retry interceptor(axios.ts:18)는 네트워크 에러(ECONNRESET, ETIMEDOUT 등)와 5xx 서버 에러만 재시도하며, 4xx 응답인 429는 재시도 없이 에러를 throw한다. Pix4D API가 "1초 후 사용 가능"이라고 명시했음에도 1초 대기 후 재시도하는 로직이 없어 전처리가 불필요하게 실패했다.
Technical Analysis#
Code Path#
- Entry point:
preprocess/index.ts:18--app()함수 시작 - Pix4D API 클라이언트 초기화:
preprocess/index.ts:38-42 - 사진 업로드 후 Pix4D API 호출 시퀀스 (
registerPix4dImages,configProcessing,startProcessing):preprocess/index.ts:107-135 - 이 API 호출들은 모두
common/api.ts의Pix4dApi클래스를 통해common/axios.ts의 커스텀 axios 인스턴스를 사용 - Failure point:
common/axios.ts:18-21-- retry interceptor가 429를 retryable로 인식하지 못함
axios retry interceptor에서 retryable 조건을 확인하는 코드:
const RETRY_CODES = ['ECONNRESET', 'ETIMEDOUT', 'ECONNABORTED', 'EAI_AGAIN'];
const MAX_RETRIES = 3;
const BASE_DELAY_MS = 1000;
const REQUEST_TIMEOUT_MS = 60000;
axios.interceptors.response.use(undefined, async (error) => {
const config = error.config;
if (!config) throw error;
config.__retryCount = config.__retryCount || 0;
const retryable = RETRY_CODES.includes(error.code) || (error.response?.status && error.response.status >= 500);
if (!retryable || config.__retryCount >= MAX_RETRIES) {
throw error;
}
HTTP 429는 4xx 범위이므로 error.response.status >= 500 조건에 해당하지 않고, error.code는 HTTP 응답에는 설정되지 않으므로 RETRY_CODES에도 매칭되지 않는다. 결과적으로 retryable = false가 되어 즉시 throw error된다.
에러가 Pix4dApi의 catch 블록에서 error.response.data를 JSON으로 감싸 재throw:
} catch (error: any) {
console.error(`[CupixApi-Pix4d] createPix4DProject - /project/api/v3/projects - ${error.message}`);
throw new Error(JSON.stringify(error.response?.data || error.message));
}
이 에러가 preprocess/index.ts의 catch 블록에 전달되어 로그 출력:
} catch (error: any) {
if (error instanceof AerialMapError) {
await cupixApi.saveError(aerialMapId, error.code, error.reason);
} else {
logger.error(`[CupixAerialMap] preprocess fail ${error.message}`);
await cupixApi.saveError(aerialMapId, ERROR_CODE['AMB710'].code, ERROR_CODE['AMB710'].reason);
}
throw error;
}
최종적으로 최상위 catch에서 두 번째 로그를 출력하고 process.exit(1):
} catch (error: any) {
logger.error(`[CupixAerialMap] preprocess fail - error:(${JSON.stringify(error)}) / message:(${error.message})`);
await sleep(30000); // sleep for log push
process.exit(1);
}
Log Evidence#
사용한 Datadog 쿼리:
service:aerial-map-service status:error
시간 범위: 2026-04-24T07:05:00Z ~ 2026-04-24T08:35:00Z
service:aerial-map-service status:info
시간 범위: 2026-04-24T07:05:00Z ~ 2026-04-24T08:35:00Z
"throttled" OR "Request was throttled"
시간 범위: 2026-04-24T07:05:00Z ~ 2026-04-24T08:35:00Z
에러 로그 2건 (동일 컨테이너, 83ms 간격):
2026-04-24T08:05:45.248Z [CupixAerialMap] preprocess fail {"detail":"Request was throttled. Expected available in 1 second."}
2026-04-24T08:05:45.331Z [CupixAerialMap] preprocess fail - error:({}) / message:({"detail":"Request was throttled. Expected available in 1 second."})
호스트: ip-10-1-98-120.us-west-2.compute.internal, 로그 파일: /tmp/workspace/preprocess-json-2026.04.24.log
동시간대 다른 aerial map의 성공 로그:
aerial_map 353: 성공 (07:55:43, 176초 소요)
aerial_map 354: 성공 (07:57:54, 158초 소요)
aerial_map 355: 성공 (08:03:31, 161초 소요)
aerial_map 357: 성공 (08:05:47, 129초 소요) -- 356 실패 2초 후 성공
aerial_map 359: 성공 (08:09:22, 128초 소요)
aerial_map 360: 성공 (08:09:23, 121초 소요)
"throttled" 키워드 전체 서비스 검색 결과: aerial-map-service의 2건만 발견. 다른 서비스에서는 throttling 미발생.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Pix4D API rate limit에 의한 429 응답을 axios retry interceptor가 처리하지 못해 실패 | 에러 메시지에 "Request was throttled. Expected available in 1 second." 명시; axios.ts:18에서 429는 retryable 조건에 미포함; 동시간대 7개 aerial map이 병렬 처리 중이어서 API rate limit 트리거 가능 |
-- | Confirmed |
| H2 | Pix4D 서비스 자체의 장애 (5xx 에러) | -- | 에러 메시지가 명확히 429 throttling 응답 형식; 동시간대 다른 aerial map들(353-355, 357, 359-361)은 정상 처리 완료; "throttled" 검색에서 다른 서비스 에러 없음 | Rejected |
| H3 | 동시 처리 컨테이너 수 과다로 인한 Cupix 내부 API 병목 | 8개 aerial map이 동시 처리 중 | 에러가 Pix4D API에서 발생 (Cupix API가 아님); Cupix API 관련 에러 로그 없음; 에러 메시지의 detail 필드는 Pix4D API 응답 형식 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
common/axios.ts:18의retryable조건에 HTTP 429 상태 코드를 추가해야 한다.- 429 응답 시
Retry-After헤더가 있으면 해당 값을, 없으면 기본 backoff를 사용하여 재시도하도록 한다. - 이 에러의 심각도를 재평가해야 한다. 외부 API의 일시적 rate limit은
error레벨보다warn레벨이 적절할 수 있다 (retry 성공 시).
단기 개선 (1주 이내)#
- Pix4D API 호출에 대한 rate limiting 로직을 추가한다. 현재 사진 업로드에는
pLimit(10)동시성 제한이 있지만, Pix4D REST API 호출(registerPix4dImages,configProcessing,startProcessing등)에는 rate limiting이 없다. - 여러 aerial map이 동시에 처리될 때 Pix4D API 호출이 겹치지 않도록 서비스 레벨의 요청 큐 또는 throttle을 고려한다.
장기 개선 (재발 방지)#
- 외부 API 의존성에 대한 circuit breaker 패턴 도입을 검토한다.
- Pix4D API rate limit 정책을 확인하여 동시 처리 수의 상한을 설정하거나, Step Functions 레벨에서 동시 실행 수를 제어한다.
Monitoring#
- Pix4D API 429 응답 빈도 모니터링:
service:aerial-map-service "Request was throttled"
- aerial-map-service preprocess 실패율 추적:
service:aerial-map-service status:error "preprocess fail"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial --
axios.tsretry 조건에error.response?.status === 429추가가 핵심 변경이며, 영향 범위가 해당 서비스의 HTTP 클라이언트에 한정된다.