CPBimRevision::validateDuplicatedBimExternalIds | duplicated bim_external_id="176/7/2/0/4/1" | new:
RCA: CPBimRevision::validateDuplicatedBimExternalIds | duplicated bim_external_id
Overview#
What Happened#
2026-05-23 19:36~20:10 UTC 동안 cupixworks-any-bimrevision-agent 서비스에서 BIM revision comparison 후 duplicate external ID 검증 실패가 30건 발생했다. 동일 BIM(bimId=18109, facility=8lvhnj)에 대해 반복 재시도 시마다 실패하며, comparison 결과에서 modified 원소의 newBimExternalId가 unchanged 원소의 기존 bimExternalId와 충돌하는 것이 원인이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | validateDuplicatedBimExternalIds (validation logic) |
| exception.message | duplicated bim_external_id="176/7/2/0/4/1" |
| top_frame | cpbim-revision.ts:317 (deployed version) |
| env | production, us-west-2 |
Timeline#
- 2026-05-23T19:31:57Z — BimRevisionService::run 시작 (si_trace_id: d010d4a7)
- 2026-05-23T19:36:25Z — 첫 번째 duplicate 검증 실패 (duplicates=16)
- 2026-05-23T19:46:50Z — 재시도 시작 (si_trace_id: 395e11f0)
- 2026-05-23T19:51:15Z — 두 번째 duplicate 검증 실패 (duplicates=6)
- 2026-05-23T20:05:54Z — 재시도 시작 (si_trace_id: f6e29afc)
- 2026-05-23T20:10:20Z — 세 번째 duplicate 검증 실패 (duplicates=8)
Error Log#
CPBimRevision::validateDuplicatedBimExternalIds | duplicated bim_external_id="176/7/2/0/4/1" | new: element_id=12593535 source=modified original="179/4/31/0/10" | existing: element_id=12597404 source=unchanged original="176/7/2/0/4/1"
Impact#
- Service:
cupixworks-any-bimrevision-agent - Team: clark-vdc
- 발생 횟수: 30
- 최초 발생: 2026-05-23T19:36:25.974Z
- 최근 발생: 2026-05-23T20:10:20.268Z
- 사용자 영향: BIM revision comparison이
AGT1902에러로 실패하여 BIM revision 상태가 Error로 전환됨. 해당 BIM model(bimId=18109)의 revision 업데이트가 완료되지 못하고 있으며, SQS 큐에서 재시도가 반복되고 있음.
Root Cause Summary#
Forge BIM comparison 엔진이 modified 원소에 대해 새로운 externalId를 할당할 때, 해당 ID가 이미 다른 unchanged 원소의 기존 bimExternalId와 동일한 값을 반환하는 것이 근본 원인이다. 구체적으로: element_id=12593535의 원래 externalId는 "179/4/31/0/10"이었으나, Forge comparison 결과에서 newBimExternalId로 "176/7/2/0/4/1"을 할당받았고, 이 값은 이미 element_id=12597404가 보유한 bimExternalId와 동일하다. 이는 BIM 모델 내에서 실제 external ID 재할당(remapping)이 일어났음을 의미하며, Forge 엔진의 element matching 로직이 element를 잘못 매핑하거나 BIM 모델 자체에서 external ID 충돌이 존재하는 데이터 문제이다.
Technical Analysis#
Code Path#
- Entry point:
bim-revision-service.ts:150—run()메서드가 SQS 메시지를 수신하여 revision comparison 시작 bim-revision-service.ts:177—runBimCompare()호출bim-revision-service.ts:680— comparison 결과를validateBimComparerResult()에 전달cpbim-revision.ts:156—validateBimComparerResult()가 exist → modified → removed 순서로 처리cpelement.ts:220—setFromModified()가setFromEntity()를 호출하여_newBimExternalId설정cpelement.ts:162—this._newBimExternalId = entity.externalId— Forge가 반환한 새 ID 저장cpbim-revision.ts:310—validateDuplicatedBimExternalIds()에서 중복 검출
validateDuplicatedBimExternalIds = (): boolean => {
const cpElements = this.cpBim.cpElementsCreated;
const bimExternalIdMap = new Map<string, { elementId: number; source: 'exist' | 'modified' | 'unchanged'; original: string | undefined }>();
let validate = true;
let duplicateCount = 0;
cpElements.forEach(cpElement => {
if (cpElement.isRemoved) { return; }
const bimExternalId = cpElement.newBimExternalId ?? cpElement.bimExternalId;
const currentSource = cpElement.newBimExternalId ? (cpElement.isModified ? 'modified' : 'exist') : 'unchanged';
if (bimExternalId) {
const prev = bimExternalIdMap.get(bimExternalId);
if (prev) {
logger.error('...duplicated bim_external_id="%s" | new: element_id=%d source=%s...');
validate = false;
duplicateCount++;
}
}
});
return validate;
};
private setFromEntity = (entity?: ForgeAgent.Entity): boolean => {
if (entity == undefined) { return false; }
this._newBimExternalId = entity.externalId; // Forge가 할당한 새 external ID
// ...
};
기대 동작: Forge comparison이 modified element에 대해 고유한 externalId를 반환하여 중복이 없어야 함.
실제 동작: Forge comparison이 element_id=12593535 (원래 ID "179/4/23/0/619" 계열)에 대해 "176/7/2/0/4/1" 등의 ID를 반환하고, 이 ID들은 이미 다른 unchanged element가 보유한 ID와 동일함. 이는 Forge 엔진이 BIM 모델 간 element를 잘못 매칭한 결과이다.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-any-bimrevision-agent status:error "CPBimRevision::validateDuplicatedBimExternalIds"
service:cupixworks-any-bimrevision-agent "validateDuplicatedBimExternalIds | summary"
service:cupixworks-any-bimrevision-agent "loadAllElements" status:info
핵심 로그:
CPBimRevision::validateDuplicatedBimExternalIds | summary: total=6111, unique=6102, duplicates=8
CPBimRevision::validateBimComparerResult | modified: 6025, removed: 1, exist: 190
BimRevisionService::loadAllElements | end - 6216 cpElements loaded - created: 6028, deleted: 225
BimRevisionService::run | error: "BimRevisionService::runBimCompare | end - validation failed (errorCode: AGT1902)"
충돌 패턴 분석 — 모든 에러 로그에서 source=modified 원소의 original ID가 179/4/... 패턴이고, source=unchanged 원소의 original ID가 176/7/... 패턴이다. 이는 Forge 엔진이 modelId=179의 element를 modelId=176의 element 위치로 재매핑하고 있음을 시사한다:
duplicated bim_external_id="176/7/1/4/0/0" | new: element_id=12593380 source=modified original="179/4/23/0/619" | existing: element_id=12597453 source=unchanged original="176/7/1/4/0/0"
duplicated bim_external_id="176/7/2/0/4/1" | new: element_id=12593535 source=modified original="179/4/31/0/10" | existing: element_id=12597404 source=unchanged original="176/7/2/0/4/1"
duplicated bim_external_id="159/3/9/0/50" | new: element_id=12594677 source=modified original="173/2/4/1/0/4" | existing: element_id=12567796 source=unchanged original="159/3/9/0/50"
세 번의 시도별 duplicate 수 변동:
- 04:36:25 → duplicates=16
- 04:51:15 → duplicates=6
- 05:10:20 → duplicates=8
동일한 BIM, 동일한 Forge URN 쌍에 대해 결과가 다른 것은 Forge comparison 엔진이 비결정적(non-deterministic) 결과를 반환하고 있음을 시사한다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Forge comparison 엔진이 element matching에서 잘못된 externalId를 할당 (cross-model remapping) | 모든 duplicate에서 modified element의 original ID가 다른 modelId 계열이고 unchanged의 ID로 remapping됨; duplicate 수가 시도마다 다름 (non-deterministic) |
— | Confirmed |
| H2 | 데이터베이스에 이미 duplicate external ID가 존재 (pre-existing data issue) | — | loadAllElements 시 addCPElement에서 duplicate key 경고 없음; 첫 pre-comparison validateDuplicatedBimExternalIds 호출도 실패했을 텐데 시도별 총 element 수 차이(6138, 6088, 6111 vs 정상 6026)는 comparison 후에만 발생 |
Rejected |
| H3 | exist 처리에서 restore된 element가 기존 created element와 충돌 | cpElementsCreated에 isExist 포함으로 exist 원소도 validation 대상; exist count 190 |
실제 duplicate 로그의 충돌은 modified vs unchanged 패턴이 대부분; exist 원소가 충돌원인이라면 source=exist가 보여야 함 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 파일:
packages/cupix-tesla-bim-revision-agent/src/model/cpbim-revision.ts(deployed version: 30041c47f) - Forge comparison이 반환하는
modifiedentity의externalId가 기존unchangedelement와 충돌할 때, 해당modifiedelement의newBimExternalId를 원래 값으로 유지(rollback)하는 방어 로직 추가. 즉,validateDuplicatedBimExternalIds에서 duplicate 감지 시modified원소의 external ID 업데이트를 무시하고 원래 ID를 유지하도록 처리. - 또는: 에러 레벨을
warn으로 낮추고, duplicate가 검출되어도validate = true를 반환하되, 충돌하는 element의newBimExternalId를undefined로 reset하여 원래 ID로 fallback.
단기 개선 (1주 이내)#
- Forge comparison 엔진(
@cupix/forge-agents)의 element matching 로직을 검토.modelComparison결과에서 cross-model remapping이 발생하는 조건(modelId 179 → 176, 173 → 159)을 확인하고, BIM model 구조가 변경된 경우의 matching 전략 개선 필요. - 재시도 횟수에 대한 상한을 설정하여 동일 BIM revision에 대해 무한 재시도 방지.
장기 개선 (재발 방지)#
- Forge comparison의 element matching에 confidence score를 도입하여 low-confidence 매핑 결과에 대해서는 external ID 변경을 적용하지 않는 전략 검토.
- Duplicate validation 실패 시 partial success 처리 지원 — 충돌하지 않는 element들에 대해서는 정상 업데이트를 진행하고, 충돌 element만 별도 처리.
Monitoring#
- Duplicate validation 실패 횟수를 메트릭으로 추적:
sum:logs("service:cupixworks-any-bimrevision-agent status:error \"validateDuplicatedBimExternalIds\" \"duplicated\""){*}.rollup("count", 3600)
- 동일 BIM revision에 대한 재시도 패턴 알림:
service:cupixworks-any-bimrevision-agent "validation failed (errorCode: AGT1902)"
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard — Forge 엔진 자체의 matching 로직 개선은 복잡하나, 에이전트 측 방어 로직(duplicate 시 rollback)은 비교적 단순하게 구현 가능.