BaseService::handlingMessageErrors | Error and message object - {"error":{"statusCode":403,"requestU
RCA: BaseService::handlingMessageErrors — Bim not found (ENT4000)
Overview#
What Happened#
2026-06-24 15:38 KST, cupixworks-any-thumbnail-agent (us-west-2)에서 BIM thumbnail 생성 SQS 메시지(modelId=20140)를 처리하던 중 GET /api/v1/bims/20140 호출이 HTTP 403 / body code ENT4000 ("Bim not found", 실제는 trash 상태)로 응답되었다. CPBim::getBimById는 ARG10002 코드만 정상 skip으로 처리하고 ENT4000는 reject 하기 때문에, 예외가 BaseService::handlingMessageErrors까지 전파되어 ERROR 레벨 로그가 1건 기록되었다. SQS 메시지는 정상적으로 삭제되어 retry 폭주는 없었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::NotFound (tesla 측, body type) |
| exception.message | Bim not found (code: ENT4000) |
| top_frame | packages/cupix-tesla-thumbnail-agent/src/model/cpbim.ts:100 (reject) |
| log emit | packages/base/src/base-service.ts:311 (handlingMessageErrors) |
| env | production, us-west-2 |
| affected modelId | Bim/20140 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| secc / thumbnail-agent | 1 (이 클러스터) + 최근 3일 동안 유사 ENT4000 케이스 다수 (Bim/Floorplan) | 단일 모델의 thumbnail 생성 실패. 해당 모델은 이미 trash 상태이므로 사용자 노출 영향은 없음. ERROR 로그 노이즈와 그에 따른 알람 false-positive가 주된 영향. |
Timeline#
- 2026-06-24 15:38:56 KST — SQS message
c1a74a48-8179-4581-acb6-f29fff059a7a(Bim id=20140) 수신,runByMessage시작 (base-service.ts:153). - 2026-06-24 15:38:56 KST —
CPBim::getBimById(20140)→ tesla APIGET /api/v1/bims/20140응답 403 /ENT4000"Bim not found". - 2026-06-24 15:38:56 KST —
CupixAuth::handleErrorwarn 로그,CPBim::getBimByIdreject (cpbim.ts:100). - 2026-06-24 15:38:56 KST —
BaseService::getApiErrorToDeleteMessage가 statusCode 403을 보고 errorMsg를 반환 → SQS 메시지 삭제(base-service.ts:301-304). - 2026-06-24 15:38:56 KST —
handlingMessageErrors가 ERROR 로그 emit (base-service.ts:311). 클러스터 생성.
Error Log#
BaseService::handlingMessageErrors | Error and message object - {"error":{"statusCode":403,"requestUriHref":"http://api-tesla.cupix.internal/api/v1/bims/20140?fields[...]","bodyResult":{"code":"ENT4000","type":"Cupix::Errors::NotFound","reason":"Bim not found","message":"Bim not found"},"modelId":20140},"sqsMessage":{"MessageId":"c1a74a48-8179-4581-acb6-f29fff059a7a","Attributes":{"ApproximateReceiveCount":"1"}}}
Impact#
- Service:
cupixworks-any-thumbnail-agent - Team: secc
- 발생 횟수: 1
- 최초 발생: 2026-06-24 15:38 KST
- 최근 발생: 2026-06-24 15:38 KST
Root Cause Summary#
Thumbnail agent가 BIM thumbnail 생성을 위해 GET /api/v1/bims/20140를 호출했지만, 해당 BIM은 이미 trash(soft-delete) 상태였다. tesla의 BaseRepository는 trash된 레코드에 대해 Cupix::Errors::NotFound를 code: 'ENT4000'로 raise하고, controller는 이를 HTTP 403으로 응답한다(client_error_controller.rb:27,57-59). CPBim::getBimById(그리고 CPPano, CPFloorplan, CPAttachment, CPAsset 모두 동일하게)는 ARG10002(완전히 존재하지 않는 ID)만 정상 skip으로 처리하고 ENT4000(trash 상태)는 reject한다. 그 결과 예외가 BaseService::checkingQueue의 catch로 올라가 handlingMessageErrors가 ERROR 로그를 emit한다. SQS 메시지는 statusCode 4xx 분기에서 삭제되므로 retry 폭주나 사용자 영향은 없고, 로그 노이즈만 발생한다.
Technical Analysis#
Code Path#
Entry point: packages/base/src/base-service.ts:107-112 (checkingQueue → runByMessages → catch → handlingMessageErrors)
private getBimById = (bimId: number): Promise<TESLA.Bim | undefined> => new Promise((resolve, reject) => {
logger.debug('CPBim::getBimById | begin');
logger.debug('CPBim::getBimById | bimId: %d', bimId);
this.cupixApi.bim.get(bimId)
.then(resBim => {
logger.debug('CPBim::getBimById | end');
resolve(resBim);
})
.catch(ec => {
const body = ec?.response?.body;
if (body) {
const code = body.result?.code;
const reason = body.result?.reason ? body.result.reason : body.result?.message;
logger.warn('CPBim::getBimById | end - reason: %s', reason);
if (code === 'ARG10002') return resolve(undefined);
}
reject(ec);
});
});
ENT4000은 catch에 진입하지만 if (code === 'ARG10002') 조건을 통과하지 못해 reject(ec)로 빠진다. 이후 setModel에서 catch되지 않고 run → runByMessage → runByMessages로 전파되어 checkingQueue의 catch가 잡는다.
try {
await this.runByMessages();
} catch (error) {
await this.handlingMessageErrors(error);
}
this.resetMessages();
await CPUtils.sleep(500);
await this.checkingQueue();
private handlingMessageErrors = async (error: any): Promise<void> => {
const errorAndMessage = {
error: error,
sqsMessage: {}
};
if (this.messageInProcess) {
errorAndMessage.sqsMessage = {
MessageId: this.messageInProcess.MessageId,
Attributes: this.messageInProcess.Attributes
};
const apiErrorObject = this.getApiErrorToDeleteMessage(error);
if (apiErrorObject != undefined || this.checkReceiveCountToDeleteMessage()) {
try {
errorAndMessage.error = apiErrorObject;
await this.deleteByMessage(this.messageInProcess);
if (this._modelInProcess != undefined && this._modelInProcess.id > 0) await this.updateErrorState(this._modelInProcess);
} catch (error) {
logger.error('BaseService::handlingMessageErrors | Errors in error handling', error);
}
}
}
logger.error('BaseService::handlingMessageErrors | Error and message object - %s', JSON.stringify(errorAndMessage));
resetLogMeta();
};
getApiErrorToDeleteMessage가 statusCode 403을 받아 errorMsg 객체를 반환하기 때문에(base-service.ts:270-274) 메시지 삭제는 정상적으로 수행되며, 마지막 줄(base-service.ts:311)에서 ERROR 레벨로 통합 로그를 남긴다 — 이 로그가 클러스터의 representative error다.
tesla 측 응답 매핑은 다음과 같다.
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
rescue_from Cupix::Errors::NotFound, with: :not_found_403_error
# ...
def not_found_403_error(exception)
raise_error(403, exception)
end
기대 동작: thumbnail 대상 모델이 trash 상태일 때는 worker가 조용히 종료(메시지 삭제, 로그는 warn 이하). 실제 동작: ENT4000이 reject되어 BaseService ERROR 로그가 발생한다.
Log Evidence#
Datadog query:
service:cupixworks-any-thumbnail-agent "ENT4000"
같은 SQS 메시지 처리 중 emit된 연속 로그(KST):
warn CupixAuth::handleError | Response statusCode: 403, requestUriHref: .../api/v1/bims/20140..., body.result: {"code":"ENT4000","type":"Cupix::Errors::NotFound","reason":"Bim not found","message":"Bim not found"}
warn BaseService::getApiErrorToDeleteMessage | error msg - {"statusCode":403,"requestUriHref":"...","bodyResult":{"code":"ENT4000",...},"modelId":20140}
error BaseService::handlingMessageErrors | Error and message object - {"error":{"statusCode":403,...,"bodyResult":{"code":"ENT4000","type":"Cupix::Errors::NotFound","reason":"Bim not found","message":"Bim not found"},"modelId":20140},"sqsMessage":{"MessageId":"c1a74a48-8179-4581-acb6-f29fff059a7a","Attributes":{"ApproximateReceiveCount":"1"}}}
같은 ENT4000 패턴이 최근 3일 동안 다른 modelId(Bim/20097, Bim/1679, Floorplan/12010, Floorplan/11968)에서도 반복적으로 발생:
2026-06-24 11:48 KST bims/20097 ApproximateReceiveCount=1
2026-06-24 07:58 KST bims/1679 ApproximateReceiveCount=1
2026-06-23 23:23 KST floorplans/12010/cover_upload_url ApproximateReceiveCount=1
2026-06-23 22:22 KST floorplans/12010/cover_upload_url ApproximateReceiveCount=2 (retry)
2026-06-23 20:31 KST floorplans/11968/cover_upload_url ApproximateReceiveCount=1
2026-06-23 20:18 KST floorplans/11968 ApproximateReceiveCount=2 (retry)
대부분 ReceiveCount=1이지만 일부는 2까지 가는데, 이는 메시지가 삭제되기 전 visibility-timeout 만료 또는 다른 reason path를 거쳤기 때문으로 보인다 — 클러스터 분석 범위는 아님.
tesla 측 코드 (config/error_code/entity.yml:49-50)에서 ENT4000의 의미가 "Record has trashed"로 정의되어 있어, 응답 message("Bim not found")는 클라이언트 노출용 마스킹이고 실제 의미는 soft-delete 상태이다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 대상 BIM(id=20140)이 trash 상태인데 SQS 메시지가 이미 큐에 들어가 있어 CPBim::getBimById가 ENT4000을 받고 reject한다. ARG10002만 정상 skip으로 처리하기 때문에 ENT4000은 ERROR 로그를 유발한다. |
tesla entity.yml ENT4000: Record has trashed. base_repository.rb:352-353 in_trash.present?이면 ENT4000 raise. cpbim.ts:98 ARG10002만 resolve(undefined), 나머지는 reject. Datadog 로그에서 status 403 + body code ENT4000 confirmed. |
— | Confirmed |
| H2 | tesla API 인증/권한 문제(real 403)로 인해 thumbnail agent가 BIM 조회에 실패. | HTTP 응답 statusCode가 403. | body.code가 ENT4000(NotFound)이며 code: AUTH*(authorization) 계열 코드가 아님. client_error_controller.rb:57-59에서 NotFound를 의도적으로 403으로 매핑한 것이 확인됨. CupixAuth는 session 갱신/실패 메시지를 별도로 남기지 않고 단순히 응답을 그대로 전파. |
Rejected |
| H3 | SQS retry 폭주 또는 visibility-timeout 이슈로 동일 메시지가 반복 처리되어 발생. | 일부 Floorplan 케이스에서 ApproximateReceiveCount=2 관측. | 본 클러스터 메시지(c1a74a48-...)는 ReceiveCount=1. getApiErrorToDeleteMessage가 statusCode 4xx에서 errorMsg를 반환(base-service.ts:270-274)하므로 첫 try에 메시지가 삭제됨. retry로 인한 클러스터 증폭은 발생하지 않음. |
Rejected |
| H4 | tesla API의 일시적 4xx 응답(데이터 정합성 race) — 메시지 enqueue 시점 직후 BIM이 trash로 전환되어 race가 발생. | 메시지 처리 시점에 BIM이 trash 상태로 관측됨. | 원인의 핵심은 race가 아니라 "trash 상태인 모델에 대한 thumbnail 요청을 정상 skip으로 처리하지 못함" — 즉 race가 있어도 클라이언트 측에서 ENT4000을 ARG10002와 동일하게 흡수했으면 ERROR 로그는 안 났을 것. H1과 결합하면 H1이 우선 원인. | Inconclusive (contributing 가능, 핵심 원인 아님) |
Fix Recommendation#
즉시 조치 (Critical)#
- 없음. 메시지는 정상 삭제되고 사용자 영향이 없으므로 긴급 hotfix 불필요.
단기 개선 (1주 이내)#
packages/cupix-tesla-thumbnail-agent/src/model/cpbim.ts:98,cppano.ts:54,cpfloorplan.ts:65,cpattachment.ts:73,cpasset.ts:73에서ARG10002와 함께ENT4000(trash 상태)도 정상 skip으로 처리(resolve(undefined))하고, 호출부(setModel)는 undefined 반환 시 thumbnail 생성을 silent skip 하도록 한다. 동일한 catch 블록을 가진 5개 모델 클래스 모두 적용 필요.- 보조적으로
BaseService::getApiErrorToDeleteMessage호출 전,setModel/run단계에서 trash/not-found 코드는 throw 하지 않고warn으로만 남기도록 하면 ERROR 로그 노이즈가 줄어든다. 단, 변경 범위가 넓어지므로 모델 측 fix가 더 안전한 first step.
장기 개선 (재발 방지)#
- tesla 측
entity.yml에러 코드 매핑을 agents 패키지에서 공유 상수로 사용하도록@agents/api(또는@agents/utils)에KNOWN_SKIPPABLE_ERROR_CODES = ['ARG10002', 'ENT4000']같은 명세를 두고, 모든 model 파일에서 동일 list를 참조하게 한다 — 5개 파일이 동일 패턴을 복제하는 현재 구조에서 한쪽만 수정되는 drift 방지. - 또는 production-side에서 BIM/Floorplan/Pano를 trash 처리할 때 큐에 남아있는 thumbnail 메시지를 invalidate 하거나, queue producer 측에서 모델 active 여부를 확인하고 enqueue 하도록 producer를 보강한다(좀 더 큰 작업).
Monitoring#
- 추적할 메트릭:
cupixworks-any-thumbnail-agent의BaseService::handlingMessageErrorsERROR 발생 카운트 중 body code별 분포. fix 적용 후 ENT4000 분기가 0으로 떨어져야 함.
Datadog timeseries widget 쿼리 예시(release dashboard 호환):
logs("service:cupixworks-any-thumbnail-agent status:error \"handlingMessageErrors\" \"ENT4000\"").index("*").rollup("count").by("@environment")
logs("service:cupixworks-any-thumbnail-agent status:error \"handlingMessageErrors\"").index("*").rollup("count").by("@environment")
(전체 vs ENT4000 두 시계열을 비교하여 fix 후 ENT4000 기여분이 사라지는지 확인)
Risk Assessment#
- Risk level: low — 사용자 노출 영향 없음, SQS 메시지 정상 삭제, 로그 노이즈가 주된 영향.
- 예상 복잡도: trivial — 5개 model 파일에
ENT4000코드 1줄씩 추가. 회귀 위험 거의 없음. unit test로 catch 분기 검증 가능.