SiteTrackPreprocessorRunner::loadAllElementRecords | match failed (primary + fallback) - facility_ke
RCA: SiteTrackPreprocessorRunner::loadAllElementRecords | match failed (primary + fallback)
Error Log#
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:9—createTargetModels()호출 - Element 로딩:
sitetrack-preprocessor-runner.ts:130-156—createCPElements()에서 bim별, category별, level별 Element API 호출 - ElementRecord 로딩:
sitetrack-preprocessor-runner.ts:295-358—loadAllElementRecords()에서 전체 category + level 기준 ElementRecords API 호출 - Failure point:
element-record.util.ts:68—logElementRecordMatchFailure()에서 매칭 실패 에러 로그 출력
1단계: 로컬 Element 로딩 (createCPElements)
// 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 전반부)
// 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가 맵의 키로 등록됩니다:
// 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 매칭 시도
// sitetrack-preprocessor-runner.ts:312
const srvElementRecords = await this.cupixApi.siteinsights.getElementRecords(cpSitetrack.facilityKey, chunk, uniqueLevelIds, cpSitetrack.recordCapturedAt);
서버 API 호출 시 **bimId 파라미터가 undefined**로 전달됩니다:
// siteinsights.api.ts:23-26
const res = await api.getElementRecords(
Fields.ElementRecordFields,
facilityKey,
undefined, // bimId — 필터 없음
categoryIds,
4단계: Primary + Fallback 매칭 실패
// 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 쿼리:
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건 에러 발생):
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_files → create_request_json → save_file_system으로 정상 진행됩니다.
동시 실행 패턴 (Job 1007059, 1007060):
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, 62704category_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.error→logger.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 레벨에서의 발생 빈도 추적:
service:cupixworks-sitetrack-preprocessor-agent "match failed (primary + fallback)" status:warn
- Facility별 매칭 실패율 모니터링:
service:cupixworks-sitetrack-preprocessor-agent "match failed" | stats by facility_key
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard
- 파이프라인은 에러에도 불구하고 정상 완료되므로, 사용자 영향은 낮습니다. 다만 매칭되지 않은 ElementRecord의 서버 데이터(task, activity, completion 정보 등)가 로컬에 반영되지 않아, 일부 progress 데이터가 누락될 수 있습니다.