CPBimRevision::validateDuplicatedBimExternalIds | duplicated bim_external_id="92fb93c9-0f4b-4359-92e
RCA: CPBimRevision::validateDuplicatedBimExternalIds | duplicated bim_external_id
Overview#
What Happened#
2026-06-01 22:53 KST에 cupixworks-any-bimrevision-agent 서비스에서 BIM 리비전 비교(V2→V3) 처리 중 validateDuplicatedBimExternalIds 검증 단계에서 6건의 중복 bim_external_id가 감지되었다. 이로 인해 리비전 처리가 error code AGT1902로 실패하였으며, bim_id=1316의 V3 리비전 업데이트가 완료되지 않았다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | BimRevisionService |
| exception.message | BimRevisionService::runBimCompare | end - validation failed (errorCode: AGT1902) |
| top_frame | cpbim-revision.ts:290 |
| runtime | Node.js (agent v10.98.0) |
| env | production, eu-central-1 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| byuk (BIM Revision) | 6 | bim_id=1316 리비전 V3 업데이트 실패, 사용자에게 최신 BIM 변경사항 미반영 |
Timeline#
- 2026-06-01 22:52 KST —
BimRevisionService::run시작 (si_trace_id: b7e72d99-0576-4490-afe3-2b756d274670) - 2026-06-01 22:52 KST —
REVISE-BEGINbim_id=1316, V2→V3 비교 시작 (15,122 entities) - 2026-06-01 22:53 KST — Forge comparison 완료: modified=11,402, removed=95, exist=99
- 2026-06-01 22:53 KST —
validateDuplicatedBimExternalIds6건 중복 감지, 에러 로그 6건 발생 - 2026-06-01 22:53 KST —
AGT1902에러로 리비전 처리 실패 - 2026-06-01 22:53 KST — SQS 메시지 삭제, 임시 파일 정리
Error Log#
CPBimRevision::validateDuplicatedBimExternalIds | duplicated bim_external_id="92fb93c9-0f4b-4359-92e7-4d33e55083a4-007dcc33" | new: element_id=726370 source=exist original="cfcae523-58ba-4f7c-997c-1f0cce770d20-007da6b6" | existing: element_id=726367 source=unchanged original="92fb93c9-0f4b-4359-92e7-4d33e55083a4-007dcc33"
Impact#
- Service:
cupixworks-any-bimrevision-agent - Team: byuk
- 발생 횟수: 6
- 최초 발생: 2026-06-01 22:53 KST
- 최근 발생: 2026-06-01 22:53 KST
Root Cause Summary#
Forge comparison engine이 "exist" (복원 대상) 또는 "modified" 상태의 element에 새로운 externalId를 할당할 때, 해당 ID가 이미 "unchanged" 상태의 다른 element가 보유한 bim_external_id와 동일한 경우 충돌이 발생한다. validateDuplicatedBimExternalIds는 cpElement.newBimExternalId ?? cpElement.bimExternalId로 최종 ID를 결정하는데, exist/modified element의 newBimExternalId가 unchanged element의 기존 bimExternalId와 겹치면서 중복이 감지된다. 이는 Forge comparison engine이 BIM 모델의 external ID 재할당(Revit 재내보내기 등)을 반영할 때, unchanged element의 기존 ID와의 충돌을 사전에 검증하지 않기 때문이다.
Technical Analysis#
Code Path#
- Entry point:
bim-revision-service.ts:runBimCompare() - Forge comparison 결과 수신 후
validateBimComparerResult()호출:cpbim-revision.ts:152 - exist element 처리 —
setExist()→setFromEntity():cpelement.ts:238-241 - modified element 처리 —
setFromModified()→setFromEntity():cpelement.ts:220-223 setFromEntity에서newBimExternalId할당:cpelement.ts:162- 최종 중복 검증:
validateDuplicatedBimExternalIds():cpbim-revision.ts:275 - 검증 실패 시 에러 throw:
bim-revision-service.ts:688-690
핵심 코드 1 — setFromEntity에서 새 externalId 무조건 할당:
private setFromEntity = (entity?: ForgeAgent.Entity): boolean => {
if (entity == undefined) {
logger.warn('CPElement::setFromEntity | entity is undefined - element_id: %d', this.id);
return false;
}
this._newBimExternalId = entity.externalId;
Forge comparison 결과의 entity.externalId를 검증 없이 바로 _newBimExternalId로 설정한다.
핵심 코드 2 — 중복 검증 로직:
const cpElements = this.cpBim.cpElementsCreated;
const bimExternalIds = new Set<string>();
let validate = true;
cpElements.forEach(cpElement => {
if (cpElement.isRemoved) { return; }
const bimExternalId = cpElement.newBimExternalId ?? cpElement.bimExternalId;
if (bimExternalId) {
if (bimExternalIds.has(bimExternalId)) {
logger.error('CPBimRevision::validateDuplicatedBimExternalIds | duplicated bim_external_id: %s', bimExternalId);
validate = false;
} else {
bimExternalIds.add(bimExternalId);
}
}
});
cpElementsCreated 전체를 순회하며 중복을 검사한다. unchanged element는 newBimExternalId가 없으므로 기존 bimExternalId를 사용하고, exist/modified element는 Forge가 할당한 newBimExternalId를 사용한다. 두 값이 동일하면 중복으로 판정된다.
기대 동작 vs 실제 동작:
- 기대: 각 element의 최종
bim_external_id는 유일해야 함 - 실제: Forge comparison engine이 exist element(element_id=726370)에
92fb93c9-...-007dcc33을 새 externalId로 할당했으나, unchanged element(element_id=726367)가 이미 동일한 ID를 보유하고 있어 충돌 발생
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-any-bimrevision-agent status:error @environment:production "CPBimRevision::validateDuplicatedBimExternalIds"
service:cupixworks-any-bimrevision-agent @environment:production "b7e72d99-0576-4490-afe3-2b756d274670"
핵심 로그 — 중복 감지 6건 (모두 동일 패턴):
[2026-06-01T13:53:17.382Z] CPBimRevision::validateDuplicatedBimExternalIds | duplicated bim_external_id="92fb93c9-0f4b-4359-92e7-4d33e55083a4-007dcc33" | new: element_id=726370 source=exist original="cfcae523-58ba-4f7c-997c-1f0cce770d20-007da6b6" | existing: element_id=726367 source=unchanged original="92fb93c9-0f4b-4359-92e7-4d33e55083a4-007dcc33"
[2026-06-01T13:53:17.382Z] CPBimRevision::validateDuplicatedBimExternalIds | duplicated bim_external_id="2ad0b355-4db7-4a6d-b6ca-75464aebafe9-007dcc0d" | new: element_id=726369 source=exist original="cfcae523-58ba-4f7c-997c-1f0cce770d20-007da114" | existing: element_id=726380 source=unchanged original="2ad0b355-4db7-4a6d-b6ca-75464aebafe9-007dcc0d"
[2026-06-01T13:53:17.382Z] CPBimRevision::validateDuplicatedBimExternalIds | duplicated bim_external_id="3fecff95-2757-4549-a417-8bb9e1b84de4-00f96c20" | new: element_id=726377 source=modified original="c2c8665d-268d-4b0c-8979-2914a77588c4-00ee7498" | existing: element_id=805267 source=unchanged original="3fecff95-2757-4549-a417-8bb9e1b84de4-00f96c20"
실행 흐름 요약 로그:
{"bim_id":1316,"src_revision_id":2094,"src_revision_name":"V3","prev_revision_id":1961,"prev_revision_name":"V2","agent_version":"10.98.0","si_trace_id":"b7e72d99-0576-4490-afe3-2b756d274670","cp_elements_count":15221,"cp_elements_created_count":15122,"cp_elements_deleted_count":99}
[2026-06-01T13:53:17.304Z] BimRevisionService::runBimCompare | report.modified.length: 11402, report.removed.length: 95, report.exist.length: 99
[2026-06-01T13:53:17.320Z] CPBimRevision::validateBimComparerResult | exist processing - restored: 7, not found in new BIM: 92
[2026-06-01T13:53:17.383Z] CPBimRevision::validateDuplicatedBimExternalIds | summary: total=15129, unique=15028, duplicates=6
setFromEntity 변경 로그에서 충돌의 직접 원인 확인:
{"elementId":726370,"source":"exist","before":{"externalId":"cfcae523-58ba-4f7c-997c-1f0cce770d20-007da6b6","cycle_state":"deleted"},"after":{"externalId":"92fb93c9-0f4b-4359-92e7-4d33e55083a4-007dcc33","cycle_state":"created"}}
element_id=726370 (deleted → restored)의 새 externalId가 element_id=726367 (unchanged)의 기존 externalId와 동일.
최종 실패 로그:
[2026-06-01T13:53:17.493Z] BimRevisionService::run | error: "BimRevisionService::runBimCompare | end - validation failed (errorCode: AGT1902)"
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Forge comparison engine이 exist/modified element에 할당한 새 externalId가 unchanged element의 기존 ID와 충돌 | 로그에서 6건 모두 source=exist 또는 source=modified의 새 ID가 source=unchanged의 기존 ID와 동일함. setFromEntity (cpelement.ts:162)에서 무조건 할당 확인 |
— | Confirmed |
| H2 | DB에 이미 중복된 bim_external_id가 저장되어 있던 pre-existing 데이터 문제 | — | 모든 충돌에서 "existing" element의 source=unchanged이고 original ID가 충돌 ID와 동일 (이미 해당 ID를 정상 보유). "new" element는 다른 original ID에서 변경됨. pre-comparison validation (createTargetModels)을 통과했으므로 초기 상태에서는 중복 없음 |
Rejected |
| H3 | Revit 모델 재내보내기로 인한 external ID 대규모 재할당이 근본 원인 | REVISE-END 로그에서 external_id_changed_count: 81 확인. 6건의 충돌 중 대부분이 original과 다른 externalId를 받음 |
81개 변경 중 6개만 충돌 → 완전한 재할당이 아닌 부분적 shuffle | Confirmed (근본 원인) |
Fix Recommendation#
즉시 조치 (Critical)#
- 파일:
src/model/cpbim-revision.ts:275-304(validateDuplicatedBimExternalIds) - 현재 검증은 중복을 감지하고 전체 리비전을 실패시킨다. 중복이 감지된 exist/modified element에 대해
newBimExternalId를 롤백(원래 ID 유지)하는 fallback 로직을 추가하여 리비전 전체가 실패하지 않도록 개선. - 또는 이 검증의 log level을
warn으로 낮추고, 중복 element만 skip하면서 나머지 element는 정상 처리하는 방식 검토.
단기 개선 (1주 이내)#
validateBimComparerResult에서 exist/modified element의setFromEntity호출 전에, Forge가 할당하는entity.externalId가 이미 다른 unchanged element에 의해 사용 중인지 사전 검증 로직 추가.- 충돌 시 해당 element의
newBimExternalId를 설정하지 않고 원래 ID를 유지하도록 처리 (cpelement.ts:162분기).
장기 개선 (재발 방지)#
- Forge comparison engine (
@cupix/forge-agents)에서 external ID 매핑을 반환할 때, target BIM의 전체 external ID set과 대조하여 충돌 없는 매핑만 제공하도록 상위 레이어 개선. - BIM 리비전 비교에서 "partial success" 모드 도입: 충돌 element는 원래 상태를 유지하면서 비충돌 element만 업데이트하는 방식.
Monitoring#
- 중복 감지 빈도 모니터링:
service:cupixworks-any-bimrevision-agent "validateDuplicatedBimExternalIds" "duplicated" status:error
- AGT1902 에러 코드별 리비전 실패 추적:
service:cupixworks-any-bimrevision-agent "AGT1902" status:error
- external_id_changed_count가 높은 리비전 추적 (anomaly 선행 지표):
service:cupixworks-any-bimrevision-agent "external_id_changed_count"
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard
- bim_id=1316 단일 BIM에서만 발생. external ID 재할당이 빈번한 Revit 모델에서 반복 가능성 있으나, 대부분의 BIM 리비전은 정상 통과(15,028/15,129 unique). 데이터 손실은 없으며 리비전 재시도로 복구 불가능한 상태는 아님.