FloorplanService::uploadTile - createTileUploadCredentials error - HttpError: HTTP request failed
RCA: FloorplanService::uploadTile - createTileUploadCredentials error - HttpError: HTTP request failed
Overview#
What Happened#
2026-06-03 23:24~23:55 KST에 cupixworks-any-floorplan-agent 서비스에서 floorplan 타일 업로드 과정 중 createTileUploadCredentials API 호출이 HTTP 400으로 실패했다. 동일 세션에서 floorplan 87356, 87357 두 건이 영향을 받았으며, 원인은 SQS 메시지 중복 전달로 인한 이중 처리(duplicate processing)이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | HttpError |
| exception.message | FloorplanService::uploadTile - createTileUploadCredentials error - HttpError: HTTP request failed |
| top_frame | floorplan-service.ts:6984 (bundled) → floorplan-service.ts:322 (source) |
| runtime | Node.js (TypeScript, bundled to app.cjs) |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| nestle | 2 | floorplan 타일 업로드 실패 — 사용자가 업로드한 도면의 타일 뷰가 일시적으로 불가 |
Timeline#
- 2026-06-03 22:14 KST — Floorplan 87357 첫 번째 처리 시도 완료 (ip-10-1-45-247, cleanup 로그 확인)
- 2026-06-03 23:23 KST — Floorplan 87356 처리 시작 (ip-10-1-161-78)
- 2026-06-03 23:24 KST — Floorplan 87356 타일링 완료 후
createTileUploadCredentials호출 → HTTP 400 (tile_state is uploaded state) - 2026-06-03 23:54 KST — Floorplan 87357 두 번째 처리 시도 시작 (ip-10-1-109-209, 다른 호스트)
- 2026-06-03 23:55 KST — Floorplan 87357 동일 에러 발생
Error Log#
FloorplanService::uploadTile - createTileUploadCredentials error - HttpError: HTTP request failed
Impact#
- Service:
cupixworks-any-floorplan-agent - Team: nestle
- 발생 횟수: 2
- 최초 발생: 2026-06-03 23:24 KST
- 최근 발생: 2026-06-03 23:55 KST
Root Cause Summary#
SQS 메시지가 중복 전달되어 동일 floorplan에 대해 두 번째 agent 인스턴스가 타일 업로드를 시도했다. 첫 번째 처리에서 이미 타일이 업로드되어 tile_state가 uploaded 상태로 전이되었기 때문에, Rails API의 상태 머신 가드(Cupix::Errors::InvalidState / STAT10000)가 두 번째 create_tile_upload_credentials 요청을 HTTP 400으로 거부했다. Floorplan agent는 이 400 응답을 generic HttpError: HTTP request failed로 감싸서 에러 로그를 남겼다.
Technical Analysis#
Code Path#
- Entry point:
floorplan-service.ts—FloorplanService.run()메서드가 SQS 메시지를 받아 처리 시작 FloorplanService.tileFloorplan()— 타일링 작업 수행 (이 단계는 정상 완료)FloorplanService.uploadTile()— 타일 S3 업로드 시도- Failure point:
floorplan-service.ts:322-326—createTileUploadCredentialsAPI 호출 실패
let s3Credentials: TESLA.UploadCredentials;
try {
s3Credentials = await this.cupixApi.floorplan.createTileUploadCredentials(cpFloorplan.id);
} catch (error) {
throw new Error(`FloorplanService::uploadTile - createTileUploadCredentials error - ${error}`);
}
이 코드에서 error는 Tesla SDK의 HttpError 객체인데, toString()으로 변환 시 "HttpError: HTTP request failed"만 남기고 HTTP status code(400)와 response body(tile_state is uploaded state)를 잃는다.
API 호출 레이어:
createTileUploadCredentials = async (id: number): Promise<TESLA.UploadCredentials> => {
const api = await this.api();
const res = await api.createTileUploadCredentials(id, Fields.UploadCredentialFields);
return unwrapResult(res);
};
SDK retry 로직은 400을 retriable로 간주하지 않기 때문에 즉시 실패한다:
Retriable: 408, 429, 500, 502, 503, 504
Non-retriable (immediate fail): 400, 401, 403, 404
API 서버 측 응답:
{
"reason": "tile_state is uploaded state",
"code": "STAT10000",
"message": "tile_state is uploaded state",
"class": "Cupix::Errors::InvalidState"
}
Rails API의 FloorplansController#create_tile_upload_credentials 액션이 tile_state 상태 머신을 확인하여, 이미 uploaded 상태인 경우 추가 업로드를 거부한다.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-any-floorplan-agent status:error @environment:production "createTileUploadCredentials"
service:cupixworks-api @environment:production "tile_upload_credentials" status:error
Floorplan 87356 처리 타임라인:
14:23:55.676Z INFO BaseService::runByMessage | id: 87356
14:24:41.712Z INFO FloorplanService::tileFloorplan | tiling done and now save to /tmp/workspace/87356/tile
14:24:41.810Z ERROR FloorplanService::uploadTile - createTileUploadCredentials error - HttpError: HTTP request failed
14:24:41.810Z INFO BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/87356
API 서버 로그 (floorplan 87356):
14:24:41.865Z [400] POST /api/v1/floorplans/87356/tile_upload_credentials
Controller: Api::V1::FloorplansController#create_tile_upload_credentials
Error: Cupix::Errors::InvalidState - tile_state is uploaded state (STAT10000)
Floorplan 87357 중복 처리 증거:
13:14:22.799Z INFO BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/87357 (host: ip-10-1-45-247)
14:54:41.764Z INFO BaseService::runByMessage | id: 87357 (host: ip-10-1-109-209, 다른 인스턴스)
14:55:27.738Z ERROR FloorplanService::uploadTile - createTileUploadCredentials error - HttpError: HTTP request failed
Floorplan 87357은 ip-10-1-45-247에서 먼저 처리 완료된 후, 약 1시간 40분 뒤에 ip-10-1-109-209에서 다시 처리를 시도했다. 이는 SQS visibility timeout이 만료되어 메시지가 재전달된 것으로 판단된다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | SQS 메시지 중복 전달로 인한 이중 처리 — 첫 번째 처리에서 타일이 이미 업로드되어 상태가 uploaded로 전이됨 |
Floorplan 87357이 서로 다른 호스트에서 두 번 처리됨 (13:14 ip-10-1-45-247, 14:54 ip-10-1-109-209). API가 tile_state is uploaded state 반환 |
— | Confirmed |
| H2 | API 서버 일시적 장애 (5xx) | 에러 메시지에 "HTTP request failed" 포함 | API 로그에서 HTTP 400 반환 확인, 5xx 아님. SDK retry 로직은 400을 retry하지 않음 | Rejected |
| H3 | 네트워크 연결 문제 (ECONNREFUSED, timeout) | 에러 메시지가 generic함 | API 서버 로그에 요청이 정상 도달 및 처리됨 (400 응답). 네트워크 문제 시 SDK가 retry 후 다른 에러 패턴 | Rejected |
| H4 | Agent의 인증 토큰 만료 | HTTP 에러 발생 | API가 401이 아닌 400 반환. 에러 메시지가 tile_state 관련이지 인증 관련 아님 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
-
에러 로그 개선 (
floorplan-service.ts:322-326):HttpError객체에서 status code와 response body를 추출하여 에러 메시지에 포함시킨다. 현재는${error}로 toString()만 남겨 디버깅이 어렵다. -
Idempotency 처리 (
floorplan-service.ts:320-326):createTileUploadCredentials가STAT10000(tile_state is uploaded state)으로 실패할 경우, 이미 업로드가 완료된 것이므로 에러가 아닌 정상 종료로 처리한다 (early return 또는 skip).
단기 개선 (1주 이내)#
uploadTile시작 전에 floorplan의 현재tile_state를 확인하여, 이미uploaded상태이면 업로드를 건너뛰는 guard를 추가한다. 이렇게 하면 SQS 중복 전달 시에도 불필요한 타일링 + 업로드 시도를 방지할 수 있다.- 에러 심각도를
warn으로 낮춘다 — 이미 처리 완료된 floorplan의 재처리 시도는 시스템 장애가 아닌 예상 가능한 운영 시나리오이다.
장기 개선 (재발 방지)#
- SQS visibility timeout과 실제 처리 시간의 정합성을 검토한다. 처리 시간이 visibility timeout을 초과하면 메시지가 재전달된다.
- Agent 레벨에서 distributed lock 또는 idempotency key를 도입하여, 동일 floorplan에 대한 동시 처리를 방지한다.
BaseService에서 메시지 처리 시작 시 heartbeat 방식으로 SQS visibility timeout을 연장하는 로직을 추가한다.
Monitoring#
tile_state is uploaded state에러를 별도로 추적하여 SQS 중복 전달 빈도를 모니터링:
service:cupixworks-api "tile_state is uploaded state" @environment:production
- Floorplan agent의 중복 처리 빈도 모니터링:
service:cupixworks-any-floorplan-agent "BaseService::runByMessage" @environment:production | stats count by @floorplan_id | filter count > 1
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial — idempotency guard 추가와 에러 메시지 개선은 간단한 변경