ES /docs

SiteTrackPreprocessorRunner::loadAllElementRecords | cpElementRecord-elementRecord match failed - el

RCA: SiteTrackPreprocessorRunner::loadAllElementRecords | cpElementRecord-elementRecord match failed

Error Log#

Datadog Logs

text
SiteTrackPreprocessorRunner::loadAllElementRecords | cpElementRecord-elementRecord match failed - elementRecord revisioned_keys: 16756::::2901321#4 (category_id: 1027274, texture_id: none, bim_id: 15567, level_id: 65069) | found cpElementRecord revisioned_key: 17480::::2901321#4 (category_id: 1027274, bim_id: 15567, bim_revision_id: 17480, bim_external_id: 2901321#4)

Impact#

  • Service: cupixworks-sitetrack-preprocessor-agent
  • Team: accoes
  • 발생 횟수: 1206
  • 최초 발생: 2026-04-06T20:54:43.628Z
  • 최근 발생: 2026-04-07T04:07:21.748Z

Root Cause Summary#

loadAllElementRecords 메서드에서 서버 elementRecord와 로컬 cpElementRecord를 매칭할 때, BIM revision ID 불일치로 인해 키 기반 조회가 실패합니다. cpElementRecordidentifierKey는 Element API에서 가져온 현재(최신) BIM revision ID로 생성되지만, 서버의 elementRecord.revisioned_keys는 element record가 처음 생성된 시점의 (과거) BIM revision ID를 포함합니다. BIM 모델이 새 revision으로 업데이트되면 이 두 revision ID가 달라져서, 동일한 bim_external_id를 가진 레코드임에도 키 매칭에 실패합니다. 이 에러는 로그만 남기고 처리를 계속하므로 job 자체는 실패하지 않지만, 해당 element record의 서버 데이터(completed_at, task 변경 등)가 누락됩니다.

Technical Analysis#

Code Path#

  • Entry point: preprocessor-service.ts:71runner.createTargetModels(cpSitetrack) 호출
  • sitetrack-preprocessor-runner.ts:28createTargetModels 내에서 loadAllElementRecords 호출
  • sitetrack-preprocessor-runner.ts:295-343loadAllElementRecords 메서드 전체

Step 1: 각 cpElement에 대해 makeElementRecords()를 호출하여 cpElementRecord를 생성하고, identifierKey를 맵에 등록합니다.

typescript
// cpelement-record.ts:29-30
const textureId = this._cpPhase.srvTextureId || null;
this._identifierKey = `${this._cpElement.cpBim.id}-${this._cpElement.bimRevisionedKey}-${textureId}`;

여기서 bimRevisionedKey는 다음과 같이 구성됩니다:

typescript
// cpelement.ts:54
get bimRevisionedKey(): string { return `${this.bimRevisionId}::::${this.bimExternalId}`; }

bimRevisionId는 Element API로부터 받은 현재 BIM revision의 ID입니다.

Step 2: 서버에서 getElementRecords API를 호출하여 srvElementRecords를 가져옵니다.

typescript
// sitetrack-preprocessor-runner.ts:310
const srvElementRecords = await this.cupixApi.siteinsights.getElementRecords(cpSitetrack.facilityKey, chunk, uniqueLevelIds, cpSitetrack.recordCapturedAt);

Step 3 (Failure point): 서버 elementRecordrevisioned_keys를 사용하여 맵에서 cpElementRecord를 찾습니다.

typescript
// sitetrack-preprocessor-runner.ts:319-325
for (const revisionKey of elementRecord.revisioned_keys) {
    const key = `${elementRecord.bim?.id}-${revisionKey}-${textureId}`;
    if (cpSitetrack.cpElementRecordMap.has(key)) {
        cpElementRecord = cpSitetrack.cpElementRecordMap.get(key);
        break;
    }
}

기대 동작: revisionKey(예: 16756::::2901321#4)가 맵의 키(예: 15567-16756::::2901321#4-null)와 일치해야 합니다.

실제 동작: 맵의 키는 현재 revision ID를 사용하여 15567-17480::::2901321#4-null로 생성되었으므로, 서버의 과거 revision ID 16756과 일치하지 않습니다.

Step 4: 매칭 실패 시 logElementRecordMatchFailure가 호출됩니다.

typescript
// element-record.util.ts:22-45
if (isTargetBim) {
    // bimExternalId로 fallback 매칭을 시도하여 디버그 정보 출력
    const foundRecord = elementRecord.revisioned_keys?.reduce<CPElementRecord | undefined>((found, revisionedKey) => {
        if (found) return found;
        const bimExternalId = revisionedKey.split('::::')[1] || revisionedKey;
        return cpElementRecordArray.find(cp =>
            cp.bimExternalId === bimExternalId &&
            cp.cpElement.cpBim.id === elementRecord.bim?.id
        );
    }, undefined);
    // ... logger.error with both revisioned_keys for comparison
}

이 유틸 함수에서 bimExternalId 기반으로 relaxed matching을 수행하면 레코드를 찾을 수 있음을 확인합니다 — 이는 bim_external_id는 동일하지만 bim_revision_id만 다르다는 것을 증명합니다.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-sitetrack-preprocessor-agent status:error "loadAllElementRecords"

패턴 1 — bim_id: 15567, revision 16756 vs 17480:

text
SiteTrackPreprocessorRunner::loadAllElementRecords | cpElementRecord-elementRecord match failed - elementRecord revisioned_keys: 16756::::2901321#4 (category_id: 1027274, texture_id: none, bim_id: 15567, level_id: 65069) | found cpElementRecord revisioned_key: 17480::::2901321#4 (category_id: 1027274, bim_id: 15567, bim_revision_id: 17480, bim_external_id: 2901321#4)

패턴 2 — bim_id: 14742, revision 16763 vs 21770 (다른 facility):

text
SiteTrackPreprocessorRunner::loadAllElementRecords | cpElementRecord-elementRecord match failed - elementRecord revisioned_keys: 16763::::76901e65-45f6-495b-b572-3c5a9ab425b4-0020fc46 (category_id: 1030905, texture_id: none, bim_id: 14742, level_id: 62492) | found cpElementRecord revisioned_key: 21770::::76901e65-45f6-495b-b572-3c5a9ab425b4-0020fc46 (category_id: 1030905, bim_id: 14742, bim_revision_id: 21770, bim_external_id: 76901e65-45f6-495b-b572-3c5a9ab425b4-0020fc46)

두 패턴 모두 동일한 근본 원인을 보여줍니다:

  • bim_external_id는 동일 (예: 2901321#4, 76901e65-...-0020fc46)
  • bim_id는 동일 (예: 15567, 14742)
  • bim_revision_id만 다름 (예: 16756→17480, 16763→21770 — 서버 ElementRecord가 과거 revision 참조)

서비스 실행 컨텍스트:

text
service:cupixworks-sitetrack-preprocessor-agent status:info "PreprocessorService::run"
text
2026-04-07 13:04:47 KST - PreprocessorService::run | begin
2026-04-07 13:10:13 KST - PreprocessorService::run | end
2026-04-07 13:38:41 KST - PreprocessorService::run | begin
2026-04-07 13:39:45 KST - PreprocessorService::run | end

여러 preprocessor job이 지속적으로 실행되며, 각 실행마다 동일한 매칭 실패가 반복됩니다. Job 자체는 성공적으로 완료(end 로그 존재)되지만, 매칭 실패한 element record들은 서버 데이터 없이 처리됩니다.

관련 에러 (같은 시간대, 다른 에러):

text
service:cupixworks-sitetrack-preprocessor-agent status:error -"loadAllElementRecords"
text
CPElement::makeElementRecords | No phases found for Element bim_external_id: 9bb53fe0-15d5-43bd-99d2-690240742c4d-007a6f36, category_id: 1030869, workarea_ids: [ 103620 ]

이 에러는 관련되어 있지만 별도의 이슈입니다 — phase가 없는 element에 대한 것으로, 이 RCA의 대상과는 다릅니다.

Fix Recommendation#

즉시 조치 (Critical)#

  • 파일: sitetrack-preprocessor-runner.ts:319-325
  • 방향: 키 매칭 시 bim_revision_id를 제외한 조회를 fallback으로 추가. 현재 logElementRecordMatchFailure 유틸에서 이미 bimExternalId + bim.id로 relaxed matching을 수행하여 레코드를 찾고 있으므로, 이 로직을 실제 매칭에도 적용.
  • 구체적으로: cpElementRecordMap의 full key 매칭이 실패하면, bim.id + bimExternalId + textureId 조합으로 2차 조회를 시도. element-record.util.ts:24-31의 기존 fallback 로직을 참고.

단기 개선 (1주 이내)#

  • CPElementRecordidentifierKey 생성 로직 재설계 검토. bim_revision_id를 키의 일부로 포함할 필요성 재평가 — BIM 모델 업데이트 시 element record의 revision이 자연스럽게 바뀌는 것이 예상 동작이라면, 키에서 revision을 제거하는 것이 근본적 해결.
  • 또는 서버의 getElementRecords API가 현재 revision의 revisioned_keys도 함께 반환하도록 API 변경을 요청.

장기 개선 (재발 방지)#

  • Element record 매칭 로직에 대한 통합 테스트 추가 — BIM revision이 업데이트된 시나리오를 포함.
  • logElementRecordMatchFailure의 fallback matching 결과를 메트릭으로 수집하여, revision mismatch가 발생하는 facility/bim 조합을 모니터링.

Monitoring#

  • 추가할 메트릭: cpElementRecord-elementRecord match failed 에러 발생 횟수를 facility_key, bim_id별로 집계
  • Datadog 쿼리:
text
service:cupixworks-sitetrack-preprocessor-agent status:error "cpElementRecord-elementRecord match failed"
  • 현재 1206건/7시간 수준으로 발생 중이며, BIM revision 업데이트가 있는 facility에서 반복적으로 발생. Fix 적용 후 이 에러 수가 0에 근접해야 함.

Risk Assessment#

  • Risk level: medium
  • 예상 복잡도: standard — 매칭 로직 변경은 단순하나, revision을 키에서 제거할 경우 다른 곳에서 revision 구분이 필요한 케이스가 없는지 확인 필요. logElementRecordMatchFailure에서 이미 fallback matching을 구현하고 있으므로 방향은 검증됨.