ES /docs

SiteTrackPreprocessorRunner::loadAllElementRecords | match failed (primary + fallback) - facility_ke

RCA: SiteTrackPreprocessorRunner::loadAllElementRecords | match failed (primary + fallback)

Error Log#

Datadog Logs

text
SiteTrackPreprocessorRunner::loadAllElementRecords | match failed (primary + fallback) - facility_key: svv2zv, server ElementRecord: { revisioned_keys: [17249::::07c5decd-812a-42e2-a351-58d3f9a6a383-0022db36/c25ef096-7a65-42e3-8652-9a4f90ea3f70-008e4637], bim_id: 14898, element_id: 5890368, category_id: 1027058, texture_id: none, level_id: 62700 } | tried primary_keys: [14898-17249::::07c5decd-812a-42e2-a351-58d3f9a6a383-0022db36/c25ef096-7a65-42e3-8652-9a4f90ea3f70-008e4637-null], fallback_key: 5890368-null

Impact#

  • Service: cupixworks-sitetrack-preprocessor-agent
  • Team: ebcinc
  • 발생 횟수: 108
  • 최초 발생: 2026-04-10T04:47:17.476Z
  • 최근 발생: 2026-04-10T05:38:48.899Z

Root Cause Summary#

서버의 ElementRecords API(siteinsights.getElementRecords)가 반환하는 element 목록과 로컬에서 Element API(element.getAll)로 로딩한 element 목록 사이에 **비대칭(asymmetry)**이 존재합니다. 로컬 Element 로딩은 bimId + categoryId + levelIds로 현재 활성 상태인 element만 가져오는 반면, 서버 ElementRecords API는 categoryIds + levelIds모든 BIM의 과거 기록까지 포함하여 반환합니다. 그 결과, 서버에는 ElementRecord가 존재하지만 로컬 맵에 해당 element가 없어 primary key와 fallback key 매칭이 모두 실패합니다. 이 에러는 비치명적(non-fatal)이며 파이프라인 진행에 영향을 주지 않지만, facility svv2zv(bim_id: 14898)에서 일부 ElementRecord의 서버 데이터가 로컬에 반영되지 못합니다.

Technical Analysis#

Code Path#

  • Entry point: sitetrack-preprocessor-runner.ts:9createTargetModels() 호출
  • Element 로딩: sitetrack-preprocessor-runner.ts:130-156createCPElements()에서 bim별, category별, level별 Element API 호출
  • ElementRecord 로딩: sitetrack-preprocessor-runner.ts:295-358loadAllElementRecords()에서 전체 category + level 기준 ElementRecords API 호출
  • Failure point: element-record.util.ts:68logElementRecordMatchFailure()에서 매칭 실패 에러 로그 출력

1단계: 로컬 Element 로딩 (createCPElements)

typescript
// sitetrack-preprocessor-runner.ts:138-144
for (const cpBim of cpBims) {
    for (const cpCategory of cpCategories) {
        const cpElements = await createModels(CPElement, cpBim, this.cupixApi.element.getAll(cpSitetrack.facilityKey, cpBim.id, cpCategory.id, levelIds));
        cpElements.forEach(cpElement => cpElement.cpCategory = cpCategory);
    }
}

Element API는 bimId, categoryId, levelIds로 필터링하여 현재 활성 상태의 element만 반환합니다.

2단계: 로컬 맵 구성 (loadAllElementRecords 전반부)

typescript
// sitetrack-preprocessor-runner.ts:300-303
for (const cpElement of cpElements) {
    const cpElementRecords = cpElement.makeElementRecords();
    cpElementRecords.forEach(cpElementRecord => cpSitetrack.cpElementRecordMap.set(cpElementRecord.identifierKey, cpElementRecord));
}

각 element의 phase별로 CPElementRecord가 생성되고, identifierKey가 맵의 키로 등록됩니다:

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

identifierKey 형식: {bimId}-{bimRevisionId}::::{bimExternalId}-{textureId}

3단계: 서버 ElementRecords 매칭 시도

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

서버 API 호출 시 **bimId 파라미터가 undefined**로 전달됩니다:

typescript
// siteinsights.api.ts:23-26
const res = await api.getElementRecords(
    Fields.ElementRecordFields,
    facilityKey,
    undefined,    // bimId — 필터 없음
    categoryIds,

4단계: Primary + Fallback 매칭 실패

typescript
// sitetrack-preprocessor-runner.ts:321-327 (primary)
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;
    }
}

// sitetrack-preprocessor-runner.ts:330-336 (fallback)
if (!cpElementRecord) {
    populateFallbackMap(cpFallbackRecordMap, cpSitetrack.cpElementRecordMap, ...);
    const fKey = buildFallbackKey(elementRecord.element?.id, textureId);
    cpElementRecord = cpFallbackRecordMap.get(fKey);
}

Fallback key 형식: {elementId}-{textureId}. Element DB id 기반이므로 BIM revision에 무관하지만, 로컬에 해당 element 자체가 로딩되지 않았으면 fallback도 실패합니다.

핵심 원인: 서버의 getElementRecords API는 facility 전체의 ElementRecord를 반환하는데(bimId 필터 없음), 로컬 Element 맵은 현재 활성 element만 포함합니다. BIM revision 업데이트나 element 삭제/비활성화로 인해, 서버에는 ElementRecord가 남아 있지만 Element API에서는 더 이상 반환하지 않는 element가 존재할 수 있습니다.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-sitetrack-preprocessor-agent "loadAllElementRecords" "match failed"

시간: 2026-04-10T03:47:00Z ~ 2026-04-10T06:00:00Z

에러 분포 (job별):

Job ID Sitetrack ID 에러 수
1007000 16156 67
1007053 16160 13
1007059 16159 11
1007060 16170 11
1007052 16158 4
1006984 16157 1
1006990 16163 1

모든 에러가 동일한 facility_key: svv2zv, bim_id: 14898, team: ebcinc (team_id: 937)에 집중됩니다.

실행 흐름 (Job 1007000 — 67건 에러 발생):

text
05:11:52.171Z  info   BaseService::runByMessage | id: 1007000
05:11:52.213Z  info   JobManager::loadJob | begin
05:11:52.240Z  info   JobManager::loadJob | end
05:11:52.370Z  info   updateJobProgress | load_sitetrack_on_preprocessor
05:11:52.402Z  info   updateJobProgress | load_target_models
05:12:10.934Z  error  loadAllElementRecords | match failed (67 errors, 05:12:10-05:12:12)
05:12:14.043Z  info   updateJobProgress | download_files
05:12:36.852Z  info   updateJobProgress | create_request_json
05:12:37.121Z  info   updateJobProgress | save_file_system

에러가 load_target_models 단계에서 발생하지만, 파이프라인은 download_filescreate_request_jsonsave_file_system으로 정상 진행됩니다.

동시 실행 패턴 (Job 1007059, 1007060):

text
05:38:34.992Z  info   BaseService::runByMessage | id: 1007059
05:38:35.644Z  info   BaseService::runByMessage | id: 1007060
05:38:48.894Z  error  loadAllElementRecords | match failed (x11 each, both jobs)

동일 facility의 여러 sitetrack job이 동시 실행되며, 각각 동일한 element에서 매칭 실패가 발생합니다.

에러 공통 특성:

  • 모든 에러에서 texture_id: none (서버), textureId: null (로컬)
  • bim_id: 14898로 고정 — target BIM임 (logElementRecordMatchFailure에서 non-target BIM 필터링 통과)
  • level_id 값: 62700, 62703, 62704
  • category_id 값: 1027053, 1027056, 1027058
  • 서로 다른 job에서 동일 element_id가 반복 실패 (예: element_id 5890281이 job 1007059, 1007060 모두에서 실패)

Fix Recommendation#

즉시 조치 (Critical)#

이 에러는 non-fatal이며 파이프라인 진행을 차단하지 않으므로, 즉시 긴급 조치는 필요하지 않습니다. 다만 에러 로그 수준을 조정할 필요가 있습니다.

  • 파일: packages/siteinsights/src/util/element-record.util.ts:68
  • 방향: logElementRecordMatchFailure의 로그 수준을 logger.errorlogger.warn으로 변경하는 것을 검토. 이 상황은 데이터 비대칭에 의한 예상 가능한 동작이며, 에러 모니터링 노이즈를 줄일 수 있습니다.

단기 개선 (1주 이내)#

  • ElementRecords API 호출 시 bimId 필터 추가: siteinsights.api.ts:26에서 undefined로 전달되는 bimId 파라미터에 실제 target BIM ID 목록을 전달하여, 로컬에 없는 element의 ElementRecord가 반환되지 않도록 합니다.
  • 대안: 서버 API가 bimId 필터를 지원하지 않는 경우, 매칭 루프(sitetrack-preprocessor-runner.ts:314)에서 서버 ElementRecord의 bim.id가 target BIM에 포함되지 않으면 조기 skip하는 로직이 이미 logElementRecordMatchFailure에 있으므로, 매칭 시도 전에 동일한 필터를 적용하여 불필요한 에러 로그를 방지합니다.

장기 개선 (재발 방지)#

  • 서버 API 개선: ElementRecords API에 bim_ids[] 필터 파라미터를 추가하여 클라이언트가 target BIM에 한정된 레코드만 가져올 수 있도록 합니다. 이렇게 하면 네트워크 트래픽과 불필요한 매칭 시도를 모두 줄일 수 있습니다.
  • Element 수명 관리: BIM revision 업데이트 시 더 이상 활성 상태가 아닌 element에 연결된 ElementRecord를 정리(cleanup)하는 프로세스를 검토합니다.

Monitoring#

  • 에러 수준 변경 후, warn 레벨에서의 발생 빈도 추적:
text
service:cupixworks-sitetrack-preprocessor-agent "match failed (primary + fallback)" status:warn
  • Facility별 매칭 실패율 모니터링:
text
service:cupixworks-sitetrack-preprocessor-agent "match failed" | stats by facility_key

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • 파이프라인은 에러에도 불구하고 정상 완료되므로, 사용자 영향은 낮습니다. 다만 매칭되지 않은 ElementRecord의 서버 데이터(task, activity, completion 정보 등)가 로컬에 반영되지 않아, 일부 progress 데이터가 누락될 수 있습니다.