ES /docs

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#

  1. 2026-06-01 22:52 KSTBimRevisionService::run 시작 (si_trace_id: b7e72d99-0576-4490-afe3-2b756d274670)
  2. 2026-06-01 22:52 KSTREVISE-BEGIN bim_id=1316, V2→V3 비교 시작 (15,122 entities)
  3. 2026-06-01 22:53 KST — Forge comparison 완료: modified=11,402, removed=95, exist=99
  4. 2026-06-01 22:53 KSTvalidateDuplicatedBimExternalIds 6건 중복 감지, 에러 로그 6건 발생
  5. 2026-06-01 22:53 KSTAGT1902 에러로 리비전 처리 실패
  6. 2026-06-01 22:53 KST — SQS 메시지 삭제, 임시 파일 정리

Error Log#

Datadog Logs

text
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와 동일한 경우 충돌이 발생한다. validateDuplicatedBimExternalIdscpElement.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 무조건 할당:

src/model/cpelement.ts:156-162typescript
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 — 중복 검증 로직:

src/model/cpbim-revision.ts:282-300typescript
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 쿼리:

text
service:cupixworks-any-bimrevision-agent status:error @environment:production "CPBimRevision::validateDuplicatedBimExternalIds"
text
service:cupixworks-any-bimrevision-agent @environment:production "b7e72d99-0576-4490-afe3-2b756d274670"

핵심 로그 — 중복 감지 6건 (모두 동일 패턴):

text
[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"
text
[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"
text
[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"

실행 흐름 요약 로그:

json
{"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}
text
[2026-06-01T13:53:17.304Z] BimRevisionService::runBimCompare | report.modified.length: 11402, report.removed.length: 95, report.exist.length: 99
text
[2026-06-01T13:53:17.320Z] CPBimRevision::validateBimComparerResult | exist processing - restored: 7, not found in new BIM: 92
text
[2026-06-01T13:53:17.383Z] CPBimRevision::validateDuplicatedBimExternalIds | summary: total=15129, unique=15028, duplicates=6

setFromEntity 변경 로그에서 충돌의 직접 원인 확인:

json
{"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와 동일.

최종 실패 로그:

text
[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#

  • 중복 감지 빈도 모니터링:
text
service:cupixworks-any-bimrevision-agent "validateDuplicatedBimExternalIds" "duplicated" status:error
  • AGT1902 에러 코드별 리비전 실패 추적:
text
service:cupixworks-any-bimrevision-agent "AGT1902" status:error
  • external_id_changed_count가 높은 리비전 추적 (anomaly 선행 지표):
text
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). 데이터 손실은 없으며 리비전 재시도로 복구 불가능한 상태는 아님.