ES /docs

BaseService::handlingMessageErrors | Error and message object - {"error":{"statusCode":403,"requestU

RCA: BaseService::handlingMessageErrors — Floorplan not found (403)

Overview#

What Happened#

cupix-tesla-floorplan-agent (Datadog service cupixworks-any-floorplan-agent)가 SQS 메시지로 floorplan 89318을 정상적으로 처리하던 중, 마지막 단계인 PUT /api/v1/floorplans/89318/check_uploading 호출에서 HTTP 403 (code:"ARG10002", Cupix::Errors::NotFound, reason:"Floorplan not found") 응답을 받고 종료했다. Kibana로 확인한 결과 해당 floorplan은 처리 중간(15:36:38Z)에 사용자에 의해 trash 처리된 상태였고, agent의 마지막 API 호출 시점(15:36:57Z)에는 이미 untrashed scope 에 없었던 것이 원인이다. 1건의 단발성 이벤트이며 데이터 손상 없음.

Quick Facts#

Field Value
exception.class Cupix::Errors::NotFound (server side)
exception.message Floorplan not found
top_frame packages/base/src/base-service.ts:311 (BaseService::handlingMessageErrors)
upstream_call PUT http://api-tesla.cupix.internal/api/v1/floorplans/89318/check_uploading
http_status 403
body.code ARG10002
env production, region us-west-2
sqs_message_id 6529504f-96d4-43c4-a332-5a0fac07aa02 (ApproximateReceiveCount: 1)
affected_model_id floorplan 89318

Affected Teams#

Team / Domain Error Count Impact
sellen workspace (workspace.id 5266, facility 21088 — "8330 - PNB Seattle Sodo School") 1 1건의 floorplan 업로드 처리 미완료. 사용자가 직접 trash 한 floorplan이므로 실사용자 영향 없음.

Timeline#

  1. 2026-06-25 00:35 KST — Floorplan 89318 created in production (workspace 5266, facility 21088). created_at: 2026-06-24T15:35:34.067Z
  2. 2026-06-25 00:36 KST — agent가 SQS 메시지 수신, BaseService::runByMessage | id: 89318 로깅 (Datadog log @ 2026-06-24T15:36:27Z)
  3. 2026-06-25 00:36 KSTFloorplanService::translateFloorplan | source: /tmp/workspace/89318/source/36283.pdf, frame: 1, output: /tmp/workspace/89318/89318.png, width: 16384, height: 16384, dpi: 300, LOD: 6 (Datadog log @ 2026-06-24T15:36:29Z)
  4. 2026-06-25 00:36 KST — Floorplan 89318 trashed: cycle_state: deleted, cycle_state_updated_at: 2026-06-24T15:36:38.000Z (Kibana floorplans index)
  5. 2026-06-25 00:36 KSTcheck_uploading 호출 → 403 + Cupix::Errors::NotFound 응답, BaseService::handlingMessageErrors 에러 로깅 (Datadog log @ 2026-06-24T15:36:57Z). SQS 메시지는 동일 핸들러에서 삭제됨.

Error Log#

Datadog Logs

text
BaseService::handlingMessageErrors | Error and message object - {"error":{"statusCode":403,"requestUriHref":"http://api-tesla.cupix.internal/api/v1/floorplans/89318/check_uploading?fields%5B0%5D=id&fields%5B1%5D=name&fields%5B2%5D=state&fields%5B3%5D=resource_state&fields%5B4%5D=file_extension&fields%5B5%5D=page&fields%5B6%5D=resolution&fields%5B7%5D=cover_state&fields%5B8%5D=thumbnail_urls&fields%5B9%5D=cover_upload_url&fields%5B10%5D=floorplan_source&fields%5B11%5D=meta&fields%5B12%5D=creation_from&fields%5B13%5D=real_distance_in_millimeters&fields%5B14%5D=created_at&fields%5B15%5D=updated_at&fields%5B16%5D=upload_url&fields%5B17%5D=floorplan_type&fields%5B18%5D=published_at","bodyResult":{"code":"ARG10002","type":"Cupix::Errors::NotFound","reason":"Floorplan not found","message":"Floorplan not found"},"modelId":89318},"sqsMessage":{"MessageId":"6529504f-96d4-43c4-a332-5a0fac07aa02","Attributes":{"ApproximateReceiveCount":"1"}}}

Impact#

  • Service: cupixworks-any-floorplan-agent
  • Team: sellen
  • 발생 횟수: 1
  • 최초 발생: 2026-06-25 00:36 KST
  • 최근 발생: 2026-06-25 00:36 KST

Root Cause Summary#

사용자가 floorplan 처리 중간에 floorplan 89318을 trash 한 결과, agent의 마지막 단계인 check_uploading API가 untrashed scope (Cyclable.visibility[:UNTRASHED]) 에서 floorplan을 찾지 못해 Cupix::Errors::NotFound (HTTP 403, ARG10002)를 반환했다. agent 측 BaseService::run 흐름은 전체 작업을 하나의 try/catch로 감싸기 때문에 translateFloorplanuploadFile 이 성공한 뒤 check_uploading 만 실패해도 SQS 핸들러 전체가 에러로 떨어진다. agent의 getApiErrorToDeleteMessage 가 4xx 응답을 "delete-eligible" 로 분류해 SQS 메시지를 삭제하므로 재시도되지 않으며, 단일 사용자 행동(직접 삭제)에 의한 정상 race로 판단된다. 코드 결함보다는 "사용자 trash 가 처리 중에 발생한" 운영적 시나리오에 해당한다.

Technical Analysis#

Code Path#

  • Entry point (agent): packages/base/src/base-service.ts:108-110await this.runByMessages() 를 try/catch 로 감싸고, 어떤 단계든 throw 하면 handlingMessageErrors(error) 로 위임.
  • Step (agent): packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:75-83 — translate → upload → checkUploading 순차 호출.
  • Failure point (agent): packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:83 에서 호출한 this.cupixApi.floorplan.checkUploading(cpFloorplan.id) 가 내부 HTTP 클라이언트 (CupixAuth::handleError) 에서 403 을 받아 reject.
  • Server failure point (tesla): app/repositories/base_repository.rb:351-356 — show 가 untrashed 모델을 못 찾고 Cupix::Errors::NotFound.new(code: 'ARG10002', reason: "#{current_class.name} not found") 를 raise.
  • Status mapping (tesla): app/controllers/concerns/client_error_controller.rb:27rescue_from Cupix::Errors::NotFound, with: :not_found_403_error 로 HTTP 403 반환.
packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:75-87typescript
await this.updateFloorplanState(targetId, TESLA.FloorplanState.Processing);
await this.downloadFileWithHeader(
    cpFloorplan.cpFloorplanSource.downloadUrl,
    cpFloorplan.cpFloorplanSource.originalFilePath,
    cpFloorplan.cpFloorplanSource.headers
);
await this.translateFloorplan(cpFloorplan);
await this.uploadFile(cpFloorplan.uploadUrl, cpFloorplan.localFilePath);
await this.cupixApi.floorplan.checkUploading(cpFloorplan.id);
packages/base/src/base-service.ts:107-115typescript
try {
    await this.runByMessages();
} catch (error) {
    await this.handlingMessageErrors(error);
}
this.resetMessages();
await CPUtils.sleep(500);
await this.checkingQueue();
packages/base/src/base-service.ts:240-275typescript
private getApiErrorToDeleteMessage = (error: any): any => {
    // ... omitted: nil/system error guards
    const statusCode = response.statusCode ? Number(response.statusCode) : undefined;
    const requestUriHref = response.request?.uri?.href;
    const bodyResult = response.body?.result;
    const errorMsg = { statusCode, requestUriHref, bodyResult, modelId };
    logger.warn('BaseService::getApiErrorToDeleteMessage | error msg - %s', JSON.stringify(errorMsg));

    if (statusCode != undefined && statusCode >= 400 && statusCode <= 500) {
        if (statusCode === 401) return;  // 401 은 retry 대상
        return errorMsg;                  // 그 외 4xx + 500 은 SQS 메시지 삭제 대상
    }
    return;
};
app/repositories/base_repository.rb:351-357ruby
if model.nil?
  if self.where(attrs).in_trash.present?
    raise Cupix::Errors::NotFound.new(code: 'ENT4000', reason: "#{current_class.name} not found")
  else
    raise Cupix::Errors::NotFound.new(code: 'ARG10002', reason: "#{current_class.name} not found")
  end
end

기대 동작: trash 된 floorplan 도 처리 중인 SQS 메시지 한 건은 정상 종료(또는 graceful skip)될 것. 실제 동작: tesla 의 set_floorplan before_action 이 trashed 모델을 NotFound 로 막아, 이미 끝나가던 처리 흐름이 마지막 줄에서 에러로 떨어진다. agent 의 createCPFloorplanByFloorplanId 는 동일한 ARG10002 코드를 무해(return undefined)로 처리하지만 (floorplan-service.ts:120-128), 처리 마지막 단계의 checkUploading 에는 동일한 가드가 없어 catch-all 핸들러로 던져진다.

Log Evidence#

Datadog query (재현):

text
service:cupixworks-any-floorplan-agent (89318)

처리 시작과 종료 로그(시간 순):

text
2026-06-24T15:36:27Z  info  BaseService::runByMessage | id: 89318
2026-06-24T15:36:29Z  info  FloorplanService::translateFloorplan | source: /tmp/workspace/89318/source/36283.pdf, frame: 1, output: /tmp/workspace/89318/89318.png, width: 16384, height: 16384, dpi: 300, LOD: 6
2026-06-24T15:36:57Z  warn  CupixAuth::handleError | Response statusCode: 403, requestUriHref: http://api-tesla.cupix.internal/api/v1/floorplans/89318/check_uploading?...
2026-06-24T15:36:57Z  warn  BaseService::getApiErrorToDeleteMessage | error msg - {"statusCode":403,...,"bodyResult":{"code":"ARG10002","type":"Cupix::Errors::NotFound","reason":"Floorplan not found","message":"Floorplan not found"},"modelId":89318}
2026-06-24T15:36:57Z  info  BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/89318
2026-06-24T15:36:57Z  error BaseService::handlingMessageErrors | Error and message object - { ...statusCode:403, modelId:89318, sqsMessageId:6529504f-..., ApproximateReceiveCount: "1" }

Kibana — floorplans index, id 89318 (production):

json
{
  "id": 89318,
  "state": "processing",
  "name": "A2.2",
  "floorplan_type": "pdf",
  "page": 2,
  "workspace": { "id": 5266, "name": "Active Projects " },
  "facility": { "id": 21088, "name": "8330 - PNB Seattle Sodo School" },
  "created_at": "2026-06-24T15:35:34.067Z",
  "updated_at": "2026-06-24T15:35:34.939Z",
  "tile_state": "created",
  "cover_state": "created",
  "cycle_state": "deleted",
  "cycle_state_updated_at": "2026-06-24T15:36:38.000Z",
  "availability_state": "available"
}

핵심 시간 차:

  • 15:35:34Z floorplan 생성 → 15:36:27Z agent 처리 시작 → 15:36:38Z trash (cycle_state=deleted) → 15:36:57Z check_uploading 호출 시 NotFound.
  • agent 가 marble bucket 에 PDF 업로드와 PNG 변환을 마친 시점에 floorplan 은 이미 trashed 상태였다.

다른 동일 service 의 14:00–16:30Z 에러는 별개 ECONNRESET 케이스(floorplan 89294, 동일 클러스터 아님)로 fingerprint 가 다르며 본 인시던트와 무관하다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 처리 중 사용자가 floorplan 89318 을 trash 하여 check_uploading 이 untrashed scope 에서 못 찾음 Kibana: cycle_state=deleted, cycle_state_updated_at=2026-06-24T15:36:38Z (agent 처리 시작 11s 후, 에러 19s 전). BaseRepository.showCyclable.visibility[:UNTRASHED] 기본값 (base_repository.rb:121,355). 응답 코드 ARG10002 와 reason "Floorplan not found" 가 정확히 이 경로와 일치. Confirmed
H2 인증/세션 만료로 인한 403 응답이 401 이 아닌 403 + Cupix::Errors::NotFound. agent 의 401 분기(getApiErrorToDeleteMessage 에서 401 만 retry, base-service.ts:271) 가 발동되지 않음. body 에 명확히 code:"ARG10002", type:"Cupix::Errors::NotFound" 포함. 401 이 아니라 403. 직전 동일 floorplan 의 translateFloorplan/uploadFile 가 성공. 인증이 살아 있었음을 의미. Rejected
H3 api-tesla.cupix.internal 일시 장애 (외부 의존성) incident-board.ts for-cluster 결과: active: null, recent: []. 같은 시간대 다른 floorplan 호출은 정상. body 가 정상 application JSON (code:"ARG10002")이라 백엔드가 정상 동작. Rejected
H4 agent 가 잘못된 floorplan id 를 보냄 SQS message body 의 id 와 모든 처리 단계 로그에서 일관되게 89318. 이전 단계(translateFloorplan)도 같은 id 로 성공. Rejected
H5 tesla 측 set_floorplan/permission 회귀로 정상 floorplan 도 NotFound 반환 Pundit.policy(...).read? 실패는 PERM10000 으로 매핑(base_repository.rb:359-360)되며 본 케이스는 ARG10002. Kibana 데이터로 모델이 trashed 상태임을 직접 확인. Rejected

Fix Recommendation#

즉시 조치 (Critical)#

조치 불필요. 본 에러는 사용자 trash 와 agent 처리의 race 로 발생한 정상 시나리오 1회성 이벤트이며 데이터 손상이 없다. SQS 메시지는 getApiErrorToDeleteMessage 가 403 을 delete-eligible 로 처리해 이미 정상적으로 정리되었다.

단기 개선 (1주 이내)#

  • packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:83checkUploading 호출 실패 중 body.result.code === 'ARG10002' 인 경우는 warn 로그로 강등하는 것을 검토. 이미 동일 코드가 createCPFloorplanByFloorplanId (floorplan-service.ts:120-128) 에서는 무해하게 처리되지만, 처리 마지막 단계에서는 catch-all 로 떨어져 error 레벨이 된다. 사용자 trash 에 의한 정상 race 는 운영자 알람을 발생시키지 않는 것이 바람직하다.
  • 강등 대신 또는 추가로, runByMessages 의 마지막 단계에서 cupFloorplan 이 여전히 활성인지 가벼운 GET 으로 사전 확인 후 checkUploading 을 건너뛰는 가드도 옵션. 단 race window 자체는 닫지 못함을 인지하고 trade-off 평가 필요.

장기 개선 (재발 방지)#

  • tesla 쪽에서 agent 전용 internal endpoint 가 trashed 모델에도 idempotent 하게 응답하도록 별도 scope (예: Cyclable.visibility[:ALL_INCLUDING_TRASHED] + cycle_state 필드 반환)를 노출하면, agent 가 사용자 trash 와 시스템 에러를 구분해 처리할 수 있다. 같은 패턴이 다른 *-agent 서비스에도 적용된다.
  • 재발 빈도 모니터링이 1주 이상 지속적으로 0 에 가깝다면 어떤 변경도 하지 않아도 된다(현재 occurrence_count: 1).

Monitoring#

agent 측에서 사용자 trash 로 인한 NotFound race 가 늘어나는지 추적용:

text
service:cupixworks-any-floorplan-agent status:error "BaseService::handlingMessageErrors" "ARG10002" "Floorplan not found"

전체 floorplan-agent 에러 base rate 비교 (회귀 감지):

text
service:cupixworks-any-floorplan-agent status:error "BaseService::handlingMessageErrors"

check_uploading 만 별도로 추적:

text
service:cupixworks-any-floorplan-agent "check_uploading" "statusCode\":403"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (코드 변경 권장하지 않음; 운영적 race 시나리오로 분류)