ES /docs

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#

  1. 2026-06-03 22:14 KST — Floorplan 87357 첫 번째 처리 시도 완료 (ip-10-1-45-247, cleanup 로그 확인)
  2. 2026-06-03 23:23 KST — Floorplan 87356 처리 시작 (ip-10-1-161-78)
  3. 2026-06-03 23:24 KST — Floorplan 87356 타일링 완료 후 createTileUploadCredentials 호출 → HTTP 400 (tile_state is uploaded state)
  4. 2026-06-03 23:54 KST — Floorplan 87357 두 번째 처리 시도 시작 (ip-10-1-109-209, 다른 호스트)
  5. 2026-06-03 23:55 KST — Floorplan 87357 동일 에러 발생

Error Log#

Datadog Logs

text
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_stateuploaded 상태로 전이되었기 때문에, 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.tsFloorplanService.run() 메서드가 SQS 메시지를 받아 처리 시작
  • FloorplanService.tileFloorplan() — 타일링 작업 수행 (이 단계는 정상 완료)
  • FloorplanService.uploadTile() — 타일 S3 업로드 시도
  • Failure point: floorplan-service.ts:322-326createTileUploadCredentials API 호출 실패
applications/agents/packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:320-326typescript
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 호출 레이어:

applications/agents/packages/api/src/api/floorplan.api.ts:10-14typescript
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로 간주하지 않기 때문에 즉시 실패한다:

floorplanApi.js retry logic (retriable status codes)text
Retriable: 408, 429, 500, 502, 503, 504
Non-retriable (immediate fail): 400, 401, 403, 404

API 서버 측 응답:

cupixworks-api response bodyjson
{
  "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 쿼리:

text
service:cupixworks-any-floorplan-agent status:error @environment:production "createTileUploadCredentials"
text
service:cupixworks-api @environment:production "tile_upload_credentials" status:error

Floorplan 87356 처리 타임라인:

text
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):

text
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 중복 처리 증거:

text
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)#

  1. 에러 로그 개선 (floorplan-service.ts:322-326): HttpError 객체에서 status code와 response body를 추출하여 에러 메시지에 포함시킨다. 현재는 ${error}로 toString()만 남겨 디버깅이 어렵다.

  2. Idempotency 처리 (floorplan-service.ts:320-326): createTileUploadCredentialsSTAT10000 (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 중복 전달 빈도를 모니터링:
text
service:cupixworks-api "tile_state is uploaded state" @environment:production
  • Floorplan agent의 중복 처리 빈도 모니터링:
text
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 추가와 에러 메시지 개선은 간단한 변경