CPBim::downloadOriginal | end - undefined forgeUrn or path
RCA: CPBim::downloadOriginal | end - undefined forgeUrn or path
Overview#
What Happened#
2026-04-23 15:05:22 UTC에 cupixworks-any-thumbnail-agent 서비스에서 BIM ID 19214의 썸네일 생성 중 forge_urn이 undefined인 상태로 downloadOriginal이 호출되어 에러가 발생했다. 동일 시간대 같은 팀(aec-on)의 다른 BIM 모델(19209, 19211, 19213)은 정상적으로 썸네일이 생성되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.message | CPBim::downloadOriginal | end - undefined forgeUrn or path |
| top_frame | cpbim.ts:52 |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| aec-on (team 657) | 1 | BIM 19214의 썸네일이 생성되지 않음. 사용자 뷰어에서 BIM 미리보기 이미지 누락 |
Timeline#
- 2026-04-23T15:03:14Z — BIM 19214 레코드 생성 (Kibana
created_at) - 2026-04-23T15:05:22Z — Thumbnail agent가 SQS 메시지 수신,
CPBim::downloadOriginal에러 발생 - 2026-04-23T15:05:26Z — BIM 19214 레코드 업데이트 (
updated_at) — forge_state가translated로 변경되고forge_urn설정 완료 - 2026-04-24 — Error sweeper에서 감지, RCA 수행
Error Log#
CPBim::downloadOriginal | end - undefined forgeUrn or path
Impact#
- Service:
cupixworks-any-thumbnail-agent - Team: aec-on (id: 657)
- 발생 횟수: 1
- 최초 발생: 2026-04-23T15:05:22.440Z
- 최근 발생: 2026-04-23T15:05:22.440Z
Root Cause Summary#
BIM의 forge_state → translated 전환 시 update_thumbnail (SQS 메시지 발송)이 update_last_bim_revsion (forge_urn 설정)보다 먼저 실행되는 race condition이 근본 원인이다. Tesla(Rails) 측 BIM 상태 머신에서 forge_state → translated 후 update_thumbnail을 호출하지만, forge_urn은 BIM의 state → done 전환 시 update_last_bim_revsion에서 BimRevision에서 복사된다. forge_state 전환의 after_transition callback이 main state 머신에 forge_translated_state 이벤트를 발생시키지만, 해당 이벤트가 main state machine에 정의되어 있지 않아 :done 전환이 이 경로에서는 발생하지 않는다. :done 전환은 이후 BimRevision state → :done 에서 별도로 트리거되므로, 썸네일 SQS 메시지가 forge_urn이 아직 null인 상태에서 전송된다.
Technical Analysis#
Code Path#
1. BimRevision forge_state → translated (트리거)
BimRevision의 forge_state가 translated로 전환되면, BIM의 forge_state에 동일한 이벤트를 전파한다:
after_transition from: any, to: %i[queued translating translated translation_skipped] do |bim_revision, transition|
bim_revision.bim.fire_events((transition.to + '_forge_state').to_sym)
end
2. BIM forge_state → translated (after_transition callbacks)
BIM의 forge_state가 :translated로 전환되면 두 개의 after_transition callback이 정의 순서대로 실행된다:
after_transition from: any, to: any - [:created] do |bim, transition|
if bim.bim_revisions.size == 1
bim.fire_events(('forge_' + transition.to + '_state').to_sym)
else
bim.fire_events(:in_revision_state) unless bim.state_in_revision?
end
end
after_transition from: any, to: %i[translated translation_skipped] do |bim, transition|
bim.update_thumbnail
end
첫 번째 callback (line 168-174)이 먼저 실행되어 fire_events(:forge_translated_state)를 호출하지만, BIM의 main state 머신에 forge_translated라는 event가 정의되어 있지 않다:
state_machine :state, initial: :initializing, namespace: :state do
state :initializing,
:created,
:resource_uploading,
:resource_missing,
:resource_uploaded,
:forge_queued,
:forge_started,
:forge_translating, # forge_translating은 있지만
:in_revision,
:revision_error,
:error,
:done do # forge_translated 이벤트는 없음
end
따라서 fire_events(:forge_translated_state)는 no-op이 되고, BIM state는 :done으로 전환되지 않는다.
두 번째 callback (line 176-178)이 이어서 실행되어 update_thumbnail을 호출한다.
3. SQS 메시지 발송 (forge_urn이 null인 상태)
def update_thumbnail!(validate: true)
return if self.respond_to?(:soft_copied?) && self.soft_copied?
check_thumbnail_updatable if validate
session = self.user.agent_team_session(self.team)
client = Cws::ThumbnailAgent::Client.new(model: self, session: session)
update(thumbnail_update_queued_at: DateTime.now)
client.invoke!
end
else
body[:model] = {
id: @model.id, # BIM ID만 전달, forge_urn은 포함되지 않음
type: @model.class.name
}
end
4. Thumbnail Agent에서 API 조회 (forge_urn이 아직 null)
setModel = async (): Promise<boolean> => {
logger.debug('CPBim::setModel | begin');
logger.debug('CPBim::setModel | model id: %d, type: %s', this.modelId, this.modelType);
if (this.modelId == undefined || this.modelType == undefined || this.cupixApi == undefined) {
logger.error('CPBim::setModel | end - undefined id or type or cupixApi');
return false;
}
this._serverModel = await this.getBimById(this.modelId);
if (this._serverModel == undefined) return false;
API에서 BIM을 조회하지만, 이 시점에 forge_urn은 아직 BIM 레코드에 설정되지 않은 상태이다.
5. Failure point — forge_urn undefined 검증 실패
downloadOriginal = (): Promise<void> => new Promise((resolve, reject) => {
const serverModel = this._serverModel;
const forgeUrn = serverModel && (<any>serverModel).forge_urn; // null — 아직 설정 안됨
const forgeRegion = serverModel && (<any>serverModel).forge_region ? serverModel.forge_region : Environment.ADF_REGION;
logger.debug('CPBim::downloadOriginal | begin');
logger.debug('CPBim::downloadOriginal | forge urn: %s, path: %s', forgeUrn, this._originalFilePath);
if (forgeUrn == undefined || this._originalFilePath == undefined) {
logger.error('CPBim::downloadOriginal | end - undefined forgeUrn or path');
return reject(); // ← 에러 발생 지점
}
6. forge_urn이 설정되는 시점 (에러 이후)
BIM state → :done은 BimRevision state → :done에서 트리거된다:
after_transition from: any, to: :done do |bim_revision, transition|
if bim_revision.bim.bim_revisions.size == 1
bim_revision.send_upload_web_to_case
else
bim_revision.send_reupload_web_to_case
end
bim_revision.bim.fire_events(:done_state) unless bim_revision.bim.state_done?
end
BIM state → :done 전환 시 forge_urn이 최종 설정된다:
after_transition from: any, to: :done do |bim, transition|
bim.update_last_bim_revsion # 여기서 forge_urn 복사
# ...
end
def update_last_bim_revsion
# ...
self.forge_urn = _last_bim_revision&.forge_urn # line 290
self.forge_region = _last_bim_revision&.forge_region # line 291
self.forge_format_type = _last_bim_revision&.forge_format_type # line 292
self.last_bim_revision = _last_bim_revision
# ...
end
Log Evidence#
에러 로그 조회에 사용한 Datadog 쿼리:
service:cupixworks-any-thumbnail-agent status:error "CPBim::downloadOriginal"
Time: 2026-04-23T14:00:00Z to 2026-04-23T16:00:00Z
BIM 19214 관련 전체 로그 (시간순):
2026-04-24 00:05:22 KST [info] BaseService::runByMessage | id: 19214, key: undefined
2026-04-24 00:05:22 KST [info] ThumbnailService::newModel | model id: 19214, type: Bim
2026-04-24 00:05:22 KST [error] CPBim::downloadOriginal | end - undefined forgeUrn or path
2026-04-24 00:05:22 KST [error] BaseService::handlingMessageErrors | Error and message object - {"error":"undefined error","sqsMessage":{"MessageId":"5bc8eba4-a0b7-4ade-9c53-154cdeca2f10","Attributes":{"ApproximateReceiveCount":"1"}}}
모든 로그가 동일 초(15:05:22Z)에 발생 — setModel에서 API 조회 → downloadOriginal에서 forge_urn null 확인 → 즉시 reject.
같은 시간대 성공한 BIM (비교):
2026-04-24 00:05:31 KST [info] BaseService::runByMessage | id: 19209, key: undefined
2026-04-24 00:05:31 KST [info] ThumbnailService::newModel | model id: 19209, type: Bim
2026-04-24 00:05:32 KST [info] ImageCPObject::generateThumbnail | begin
2026-04-24 00:05:32 KST [info] ImageCPObject::generateThumbnail | end - output: /tmp/workspace/19209/19209_thumbnail.jpg, size: 4451
2026-04-24 00:05:33 KST [info] BaseService::runByMessage | id: 19213, key: undefined
2026-04-24 00:05:34 KST [info] ImageCPObject::generateThumbnail | end - output: /tmp/workspace/19213/19213_thumbnail.jpg, size: 5155
2026-04-24 00:05:36 KST [info] BaseService::runByMessage | id: 19211, key: undefined
2026-04-24 00:05:36 KST [info] ImageCPObject::generateThumbnail | end - output: /tmp/workspace/19211/19211_thumbnail.jpg, size: 3508
BIM 19209, 19213, 19211은 모두 generateThumbnail까지 정상 진행됨. BIM 19214만 downloadOriginal에서 실패.
Kibana에서 BIM 19214의 타임라인:
{
"id": 19214,
"state": "done",
"forge_state": "translated",
"created_at": "2026-04-23T15:03:14.745Z",
"updated_at": "2026-04-23T15:05:26.342Z"
}
created_at(15:03:14) → 에러(15:05:22) → updated_at(15:05:26): 에러 발생 4초 후 BIM 레코드가 업데이트됨. 이는 update_last_bim_revsion이 forge_urn을 설정하며 save한 시점과 일치한다.
동일 시간대 Bim not found 에러도 발생 (BIM 19197-19200):
2026-04-23 23:50:53~55 KST [error] BaseService::handlingMessageErrors | "statusCode":403, "code":"ENT4000", "reason":"Bim not found"
이들은 별도 이슈(삭제된 BIM에 대한 썸네일 요청)로, 본 건과 무관하다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Race condition: SQS 메시지가 forge_urn 설정 전에 발송됨 |
BIM updated_at(15:05:26)이 에러(15:05:22)보다 4초 후. forge_state after_transition이 update_thumbnail을 forge_urn 설정 전에 호출. BIM main state에 forge_translated event 미정의 → :done 전환 불가 → update_last_bim_revsion 미실행 |
— | Confirmed |
| H2 | BIM 레코드 자체가 존재하지 않거나 API 조회 실패 | — | setModel이 false를 반환했다면 downloadOriginal이 호출되지 않음 (thumbnail-service.ts:75-77). 에러가 downloadOriginal에서 발생했으므로 setModel은 성공 |
Rejected |
| H3 | Autodesk Forge 서비스 장애로 forge_urn이 영구적으로 미설정 |
— | Kibana에서 BIM 19214의 현재 forge_state: "translated", state: "done" 확인. 최종적으로 정상 완료됨 |
Rejected |
| H4 | _originalFilePath가 undefined (path 문제) |
에러 메시지가 "undefined forgeUrn or path"로 둘 중 하나일 수 있음 | setModel 성공 시 line 36에서 _originalFilePath가 반드시 설정됨. forgeUrn이 null인 것이 주요 원인 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 파일:
app/models/concerns/statable/bim.rb:176-178 update_thumbnail호출을forge_state→translated시점이 아닌 BIMstate→done전환 시점으로 이동해야 한다.state→done전환 시update_last_bim_revsion이 먼저 실행되어forge_urn이 설정된 후 썸네일 에이전트가 호출되므로 race condition이 해소된다.- 대안:
after_transition from: any, to: :donecallback (line 88-94)에bim.update_thumbnail을 추가하고,forge_state→translated에서의update_thumbnail호출(line 176-178)을 제거한다.
단기 개선 (1주 이내)#
- Thumbnail agent 측 retry 로직 추가:
cpbim.ts:51-53에서forge_urn이 undefined일 때 즉시 reject하는 대신, 짧은 delay 후 API를 재조회하는 retry 메커니즘을 추가한다. 이는 SQS 메시지 순서가 보장되지 않는 환경에서 방어적 코딩이 된다. - 에러 레벨 조정: 현재
logger.error로 기록하지만, 이는 일시적 timing 이슈이므로logger.warn으로 변경하고 retry 후에도 실패할 때만error로 기록하는 것이 적절하다.
장기 개선 (재발 방지)#
- BIM state machine에서
forge_translatedevent를 main state machine에 추가하여, forge 번역 완료 →:done전환이forge_statecallback 체인 내에서 동기적으로 실행되도록 한다. 현재는 이 이벤트가 no-op이어서 별도의 BimRevision:done전환에 의존하는 구조적 문제가 있다. - SQS 메시지에
forge_urn을 포함하여 전달하는 방안도 검토할 수 있다. 현재 메시지에는model.id와model.type만 포함되어 있어 agent가 별도로 API를 조회해야 한다.
Monitoring#
- 기존 에러 모니터링 유지:
service:cupixworks-any-thumbnail-agent status:error "CPBim::downloadOriginal"
- 수정 후 해당 에러 발생 빈도가 0으로 감소하는지 확인
- 추가 모니터링:
service:cupixworks-any-thumbnail-agent status:error "undefined forgeUrn"
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard
- 썸네일 생성 실패는 사용자 경험에 영향을 주지만, BIM 데이터 자체의 무결성이나 핵심 기능에는 영향 없음. 재시도 메커니즘이 없어 수동으로 썸네일 업데이트를 트리거해야 복구 가능.