SiteTrackPreprocessorRunner::loadAllElementRecords | match failed (primary + fallback) - facility_ke
RCA: SiteTrackPreprocessorRunner::loadAllElementRecords | match failed (primary + fallback)
Overview#
What Happened#
2026-06-24 20:17 KST 경 cupixworks-sitetrack-preprocessor-agent가 facility dwwuda (bim_id 16250) sitetrack 전처리를 수행하면서 서버에서 받은 ElementRecord 40건을 로컬 cpElementRecordMap과 매칭하지 못해 match failed (primary + fallback) error 로그가 1분 사이에 burst로 기록되었다. 같은 run에서 fallback 매칭은 12,725건 성공했으므로 sitetrack 처리 자체는 진행되었지만, 40개 element record의 oid/task 동기화가 누락되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | (no exception — logger.error only) |
| exception.message | SiteTrackPreprocessorRunner::loadAllElementRecords | match failed (primary + fallback) |
| top_frame | applications/agents/packages/siteinsights/src/util/element-record.util.ts:68-80 |
| runtime | Node.js sitetrack-preprocessor agent (cupixworks monorepo) |
| env | production, us-west-2 |
| facility_key | dwwuda |
| bim_id | 16250 |
| sitetrackId | 20051 (createCPSitetrack begin 20:14:59 KST) |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
clark-vdc / sitetrack preprocessor (facility dwwuda) |
40 | 단일 sitetrack run에서 40개 server ElementRecord가 oid/task 정보로 동기화되지 않음. 동일 run의 fallback 매칭은 12,725건 성공하여 sitetrack 처리는 계속되었으나, 미매칭 element는 후속 postprocessor 단계에서 trace가 생성되지 않거나 oid가 비어 있을 가능성. |
Timeline#
- 2026-06-24 20:14:59 KST —
PreprocessorService::createCPSitetrack | begin - sitetrackId: 20051(run 시작) - 2026-06-24 20:17:20 KST —
loadAllElementRecords에서 첫 match failed (error) 발생 — first_seen - 2026-06-24 20:18:10 KST — 동일 패턴 마지막 발생 — last_seen, 총 40건
- 2026-06-24 20:18:12 KST —
loadAllElementRecords | fallback matches: 12725info 로그 출력 (run 정상 진행)
Error Log#
SiteTrackPreprocessorRunner::loadAllElementRecords | match failed (primary + fallback) - facility_key: dwwuda, server ElementRecord: { revisioned_keys: [17832::::4/0/710], bim_id: 16250, element_id: 8897738, category_id: 1027977, texture_id: none, level_id: 54149 } | tried primary_keys: [16250-17832::::4/0/710-null], fallback_key: 8897738-null
Impact#
- Service:
cupixworks-sitetrack-preprocessor-agent - Team: clark-vdc
- 발생 횟수: 40
- 최초 발생: 2026-06-24 20:17 KST
- 최근 발생: 2026-06-24 20:18 KST
Root Cause Summary#
SiteTrackPreprocessorRunner.loadAllElementRecords는 facility dwwuda / bim 16250 의 server ElementRecord 집합을 로컬 cpElementRecordMap (primary key ${bimId}-${bimRevisionId}::::${bimExternalId}-${textureId}) 또는 fallback cpFallbackRecordMap (key ${cpElement.id}-${textureId}, TSLA-12370 에서 추가) 와 매칭한다. 12,725 건은 fallback 으로 매칭되었으나 40 건은 server ElementRecord 가 가리키는 element_id (예: 8897738, 12621421~12622866) 가 로컬 cpElements 집합에 존재하지 않아 primary 와 fallback 모두 실패했다. 즉 element.getAll(facilityKey, cpBim.id=16250, cpCategory.id, levelIds) 응답에서 누락된 Element를 server-side siteinsights.getElementRecords 가 반환했기 때문에, 어떤 키로도 매칭할 수 없는 상태였다. 동일 burst의 모든 미매칭 record가 같은 bim_id=16250, level_id=54149, category_id(1027977 또는 1030269)에 속하므로, server-side ElementRecord와 element.getAll API 가 보는 Element 집합 사이의 일관성 깨짐 이 root cause다 (예: 일부 Element 가 server-side에서 archived / soft-deleted / 다른 bim_revision 으로 옮겨졌으나 ElementRecord 는 그대로 남아 있는 데이터 상태).
Technical Analysis#
Code Path#
Entry point: applications/agents/packages/cupix-sitetrack-preprocessor-agent/src/runner/sitetrack-preprocessor-runner.ts:28 — await this.loadAllElementRecords(cpSitetrack, cpElements, uniqueLevelIds)
전처리 흐름:
createCPElements(line 130) — bim×category 조합으로element.getAll(facilityKey, cpBim.id, cpCategory.id, levelIds)호출하여 로컬cpElement집합을 빌드. 각 cpElement는 DB id (cpElement.id) 와bimRevisionedKey = ${bimRevisionId}::::${bimExternalId}를 가진다.loadAllElementRecords(line 295) — 각 cpElement에 대해makeElementRecords()로 가능한CPElementRecord들을 생성하고 primary keyidentifierKey로cpSitetrack.cpElementRecordMap에 등록.- 동일 메서드에서
siteinsights.getElementRecords(facilityKey, categoryIds chunk, uniqueLevelIds, recordCapturedAt)로 server ElementRecord 를 가져와 두 단계 매칭:- Primary:
${elementRecord.bim?.id}-${revisionKey}-${textureId}가cpElementRecordMap에 있는지. - Fallback (TSLA-12370): primary 실패 시
cpFallbackRecordMap을 lazy 빌드 후${elementRecord.element?.id}-${textureId}로 조회.
- Primary:
- 둘 다 실패하면
logElementRecordMatchFailure가 error 로깅. 이 함수는isTargetBim인 경우(즉cpSitetrack.cpBims에 해당bim.id가 존재)에만 error를 남긴다 — 이번 케이스는bim_id 16250이 target bim에 포함되어 있어 error가 발생한 것.
Primary key는 bim_revision_id 가 다르면 매칭 실패함:
const textureId = this._cpPhase.srvTextureId || null;
this._identifierKey = `${this._cpElement.cpBim.id}-${this._cpElement.bimRevisionedKey}-${textureId}`;
// ...
get fallbackKey(): string {
const textureId = this._cpPhase.srvTextureId || null;
return `${this._cpElement.id}-${textureId}`;
}
매칭 로직 (실패 지점은 line 344):
for (const elementRecord of srvElementRecords) {
if (!elementRecord.revisioned_keys?.length) continue;
const textureId = elementRecord.texture?.id ?? null;
let cpElementRecord: CPElementRecord | undefined = undefined;
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;
}
}
if (!cpElementRecord) {
populateFallbackMap(cpFallbackRecordMap, cpSitetrack.cpElementRecordMap, 'SiteTrackPreprocessorRunner', 'loadAllElementRecords');
const fKey = buildFallbackKey(elementRecord.element?.id, textureId);
cpElementRecord = cpFallbackRecordMap.get(fKey);
if (cpElementRecord) {
fallbackMatchCount += 1;
}
}
if (cpElementRecord) {
cpElementRecord.setFromSrvElementRecord(elementRecord);
setFromSrvElementRecordCount += 1;
} else {
// WARN: Server ElementRecord not found by full key match
logElementRecordMatchFailure(elementRecord, cpSitetrack, 'SiteTrackPreprocessorRunner');
}
}
Failure 로깅 (target bim 인 경우에만 error):
export function logElementRecordMatchFailure(
elementRecord: TESLA.ElementRecord,
cpSitetrack: CPSitetrack,
className: string
): void {
const bimId = elementRecord.bim?.id;
const isTargetBim = cpSitetrack.cpBims.some(cpBim => cpBim.id === bimId);
if (!isTargetBim) return;
// ...
logger.error(
'%s::loadAllElementRecords | match failed (primary + fallback) - facility_key: %s, ...',
...
);
}
기대 동작: TSLA-12370 도입 후 bim_revision_id 가 달라지더라도 element.id 가 동일하면 fallback 매칭이 성공해야 함.
실제 동작: server ElementRecord 가 가리키는 element.id (예: 8897738, 12621421) 가 로컬 cpFallbackRecordMap (= cpElement.id 기반) 에도 존재하지 않아 fallback 도 실패. 즉 cpElements 집합 자체에 이 element id 들이 빠져 있다.
Log Evidence#
Datadog 쿼리 (재현용):
service:cupixworks-sitetrack-preprocessor-agent "SiteTrackPreprocessorRunner::loadAllElementRecords" "dwwuda"
미매칭 record 패턴 (모두 같은 sitetrack run, bim 16250, level 54149, texture none):
2026-06-24 20:17:20 KST — error
match failed (primary + fallback) - facility_key: dwwuda, server ElementRecord:
{ revisioned_keys: [17832::::4/0/710], bim_id: 16250, element_id: 8897738,
category_id: 1027977, texture_id: none, level_id: 54149 }
| tried primary_keys: [16250-17832::::4/0/710-null], fallback_key: 8897738-null
2026-06-24 20:18:10 KST — error
match failed (primary + fallback) - facility_key: dwwuda, server ElementRecord:
{ revisioned_keys: [17832::::4/0/452], bim_id: 16250, element_id: 12621431,
category_id: 1030269, texture_id: none, level_id: 54149 }
| tried primary_keys: [16250-17832::::4/0/452-null], fallback_key: 12621431-null
같은 run에서 fallback 다수 성공:
2026-06-24 20:18:12 KST — info
SiteTrackPreprocessorRunner::loadAllElementRecords | fallback matches: 12725
Run-context (begin):
2026-06-24 20:14:59 KST — info
PreprocessorService::createCPSitetrack | begin - sitetrackId: 20051
PreprocessorService::createCPSitetrack | end
미매칭 element_id 들의 분포(샘플): 8897738, 12621421, 12621422, 12621423, 12621425, 12621426, 12621428, 12621431, 12621466, 12621469, 12621471, 12621499, 12621505, 12621575, 12621577, 12621606, 12622203, 12622538, 12622863, 12622866 등. 큰 묶음(12621xxx~12622xxx)이 연속되어 있어 동일 시점에 server-side 에 일괄 생성된 element 그룹으로 보이며, 로컬 element.getAll 응답에서만 누락되었다는 의미.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Server ElementRecord 가 참조하는 element_id 가 로컬 element.getAll(facilityKey, bim.id=16250, category_id, levelIds) 응답에 누락되어 있어 primary 와 fallback 모두 실패. 데이터 일관성 깨짐 (Element soft-delete / bim_revision 이동 / API 필터로 인한 제외 가능성). |
40건 모두 fallback 까지 시도 후 실패. 같은 run에서 fallback 12,725건 성공 → 매칭 메커니즘 자체는 동작. 누락된 element_id 들이 연속 번호로 묶여 있음 (12621421-12622866). 모두 동일 bim 16250 / level 54149 / category 1027977·1030269. | 없음 — log 와 코드 모두 같은 결론을 가리킴 | Confirmed |
| H2 | TSLA-12370 fallback 매칭이 적용되지 않은 구버전 코드가 배포되어 fallback 실패. | log 메시지가 match failed (primary + fallback) 형식 → fallback 로직 존재 |
TSLA-12370 PR 86801 (4월 8일) 머지 이후 코드가 production 에 배포되어 있어야 함. 같은 run에서 fallback matches: 12725 info 로그가 출력됨 → 코드 경로상 fallback 이 실제 실행됨. |
Rejected |
| H3 | bim_revision_id 가 다른 bim 이 target 에서 제외되어 (removeNonTargetBims) 매칭 실패. |
revisioned_key 가 17832::::4/0/710 처럼 단일 revision |
error 로그는 isTargetBim 가 true 일 때만 출력됨 (element-record.util.ts:57-58). 즉 bim 16250 은 target 에 남아 있음 → 다른 bim 의 record 가 흘러들어온 것 아님. |
Rejected |
| H4 | Texture/Phase mismatch 로 인한 매칭 실패 (textureId 불일치). |
fallback key 도 textureId 포함 | 미매칭 record 모두 texture_id: none 이고 fallback_key 도 ${elementId}-null 형식으로 정상 빌드됨. 즉 텍스처가 아닌 element_id 자체가 로컬에 없음. |
Rejected |
| H5 | Chunk pagination 누락 — element.getAll 이 한 페이지만 반환하고 페이지네이션이 잘려 일부 element 가 빠짐. |
미매칭 element_id 들이 연속 번호 (12621421-12622866) → 시간상 인접 생성, pagination boundary 일 가능성 | 직접 증거(element.getAll 응답 size 로그) 없음. element.getAll 구현 미확인 → 가능성은 남아 있음. |
Inconclusive — needs verification |
Fix Recommendation#
즉시 조치 (Critical)#
- 운영 영향이 제한적이므로 긴급 핫픽스는 불필요. 단, 데이터 정합성 영향을 가시화하기 위해:
applications/agents/packages/cupix-sitetrack-preprocessor-agent/src/runner/sitetrack-preprocessor-runner.ts:344의 미매칭을logger.warn으로 강등하는 것을 검토 (다음 항목 참고). 현재 error 레벨은 운영자 알람을 유발하지만 sitetrack run 자체는 정상 완료된다 (fallback matches: 12725info 가 같은 run에 기록됨).
- facility
dwwuda/ bim 16250 의 미매칭 element_id 목록 (위 Log Evidence) 을 clark-vdc 팀에 공유하여 server-side DB 상태 확인 요청:Element테이블에서 해당 id 들이 존재하는지,bim_id=16250인지,discarded_at이 set 되었는지.- 동일 element_id 가 다른
bim_revision_id로 옮겨졌는지, 옮겨진 경우 ElementRecord 의 stale reference 인지.
단기 개선 (1주 이내)#
- 로그 레벨 재조정 —
cpFallbackRecordMap에도 매칭되지 않는 케이스는 "server-side ElementRecord 가 로컬 element 집합 밖을 참조" 라는 데이터 상태이며, agent 코드가 자체적으로 복구할 수 없다. error → warn 으로 강등하고, log 메시지에 "likely server-side stale ElementRecord referencing missing/archived Element" 같은 hint 추가. 위치:applications/agents/packages/siteinsights/src/util/element-record.util.ts:68-80. - 결과 요약 메트릭 추가 —
loadAllElementRecords종료 시 (line 349-357 부근)primaryMatchCount,fallbackMatchCount,unmatchedCount를 info 로그로 함께 출력해 미매칭 비율을 추적할 수 있게 한다. 현재는 fallback 성공 카운트만 info 로 남고 unmatched 는 row-level error 로만 남아 burst 시 알람만 시끄럽다. - pagination 확인 (H5) —
applications/agents/packages/api/.../element.ts의getAll이 모든 페이지를 합쳐서 반환하는지 검증. 연속 element_id 묶음이 누락되는 패턴이 pagination boundary 가능성을 시사한다.
장기 개선 (재발 방지)#
- Server-side
siteinsights.getElementRecords응답과element.getAll응답 간의 일관성 계약 정의. 둘 다 동일한bim_id + bim_revision_id + level_ids필터로 동작하지 않으면 본 RCA 같은 mismatch 가 반복된다. 가능한 방향:- ElementRecord 에
element_id외에bim_revision_id를 함께 저장하고 server 가 현재 active revision 의 record 만 반환. - Element 가 archive/migrate 되면 관련 ElementRecord 도 cleanup 하는 데이터 lifecycle 보장.
- ElementRecord 에
- Sitetrack postprocessor 에서 unmatched ElementRecord 가 후속 trace 생성에 끼치는 영향을 정량화하는 별도 audit job — 정합성 손실을 빨리 감지하기 위함.
Monitoring#
- 시계열 위젯:
loadAllElementRecordsunmatched error 비율textsum:trace.sitetrack_preprocessor.errors\{service:cupixworks-sitetrack-preprocessor-agent,resource_name:loadAllElementRecords\}.as_count() - 시계열 위젯: facility 별 burst 감시 (현재 alert 의 잡음 줄이기)
text
count:cupixworks-sitetrack-preprocessor-agent.logs\{status:error,@function:loadAllElementRecords\} by \{facility_key\}.as_count() - 시계열 위젯: fallback 매칭 성공 vs 실패 비율 (단기 개선 메트릭 도입 후)
text
sum:cupixworks-sitetrack-preprocessor-agent.element_records.unmatched\{service:cupixworks-sitetrack-preprocessor-agent\} by \{facility_key\}.as_count()
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard
- 운영 영향: 단일 sitetrack run 의 일부 ElementRecord 가 oid/task 동기화를 누락한다는 데이터 정합성 영향이 존재하지만 sitetrack run 자체는 정상 종료되었다. 동일 패턴이 다른 facility/run 으로 확산되는지 7일 추세 모니터링 후 코드 변경(log downgrade + audit metric)을 적용하는 것이 안전하다.