CPElement::findTask | Multiple tasks found for Element bim_external_id: f21fea6f-2d2c-4b95-ac4f-9619
RCA: CPElement::findTask | Multiple tasks found for Element
Error Log#
CPElement::findTask | Multiple tasks found for Element bim_external_id: f21fea6f-2d2c-4b95-ac4f-961978663afe-0037ea53, category_id: 1030891, phase_id: 12597, workarea_ids: [ 103615 ]
Impact#
- Service:
cupixworks-sitetrack-preprocessor-agent - Team: tpc
- 발생 횟수: 114
- 최초 발생: 2026-04-14T16:50:30.759Z
- 최근 발생: 2026-04-14T16:50:30.786Z
Root Cause Summary#
Sitetrack preprocessor Job 1014318 (sitetrack 16358)에서 114개의 BIM element에 대해 동일한 category_id: 1030891, phase_id: 12597, workarea_ids: [103615] 조합으로 task를 검색했을 때, 서버에서 반환된 task 목록에 해당 조건(동일 categoryId, srvTextureId, workareaId)을 만족하는 task가 2개 이상 존재했다. findTask 메서드는 이를 에러로 로깅하지만 첫 번째 task를 사용하여 처리를 계속한다. 이는 서버 측 데이터 문제로, facility 내에서 동일한 category + phase(texture) + workarea 조합의 task가 중복 생성된 것이 원인이다. agent 코드 자체에는 버그가 없으며, 에러 로그는 데이터 정합성 경고 역할을 한다.
Technical Analysis#
Code Path#
- Entry point:
sitetrack-preprocessor-runner.ts:9—createTargetModels가 전체 모델 로딩 흐름을 시작한다.
// applications/agents/packages/cupix-sitetrack-preprocessor-agent/src/runner/sitetrack-preprocessor-runner.ts:9-31
async createTargetModels(cpSitetrack: CPSitetrack): Promise<void> {
// ...
const cpCategories = await this.loadAllCategories(cpSitetrack);
const cpElements = await this.createCPElements(cpSitetrack, uniqueLevelIds, cpBims, cpCategories);
await this.createCPTasks(cpSitetrack, cpCategories, cpElements);
// ...
await this.loadAllPhases(cpSitetrack, cpCategories);
// ...
await this.loadAllElementRecords(cpSitetrack, cpElements, uniqueLevelIds);
}
- Task 로딩:
sitetrack-preprocessor-runner.ts:159-191—createCPTasks에서 서버 API로 facility의 전체 task를 가져온 뒤, element에서 사용하는categoryId와workareaId로 필터링한다.
// applications/agents/packages/cupix-sitetrack-preprocessor-agent/src/runner/sitetrack-preprocessor-runner.ts:170-174
const srvTasks = await this.cupixApi.task.getAll(cpSitetrack.facilityKey);
const filteredTasks = srvTasks.filter(task =>
uniqueCategoryIds.includes(task.category?.id ?? -1)
&& uniqueWorkareaIds.includes(task.workarea?.id ?? -1)
);
이 단계에서 category와 workarea가 같은 task가 여러 개 있으면 모두 포함된다. Task API는 facilityKey만으로 전체 task를 반환하며, 중복을 제거하지 않는다.
- Phase에 task 할당:
sitetrack-preprocessor-runner.ts:219-221—loadAllPhases에서 각 phase에 해당하는 task를 할당한다.
// applications/agents/packages/cupix-sitetrack-preprocessor-agent/src/runner/sitetrack-preprocessor-runner.ts:219-221
for (const cpPhase of cpPhases) {
const cpTasksInPhase = cpTasks.filter(cpTask => cpTask.phaseId === cpPhase.id);
cpTasksInPhase.forEach(cpTask => cpPhase.addCPTask(cpTask));
}
동일 phase에 category와 workarea가 같은 task가 2개 이상 할당될 수 있다.
- Element record 생성 시 task 검색:
cpelement.ts:83-112—makeElementRecords에서 각 phase의cpTasks를 대상으로findTask를 호출한다.
// applications/agents/packages/models/src/cpelement.ts:99-104
for (const cpPhase of cpPhases) {
if (srvTextureIds.has(cpPhase.srvTextureId || UnknownId)) {
continue;
}
const cpTask = this.findTask(cpPhase.cpTasks, cpPhase);
}
- Failure point:
cpelement.ts:115-132—findTask에서categoryId,srvTextureId,workareaId3가지 조건으로 필터링한 결과candidateTasks.length > 1이면 에러를 로깅한다.
// applications/agents/packages/models/src/cpelement.ts:115-132
protected findTask = (cpTasks: CPTask[], cpPhase: CPPhase): CPTask | undefined => {
const candidateTasks = cpTasks
.filter(cpTask => cpTask.categoryId === this.categoryId)
.filter(cpTask => cpTask.srvTextureId === cpPhase.srvTextureId)
.filter(cpTask => cpTask.workareaId && this.workareaIds.includes(cpTask.workareaId));
if (candidateTasks.length === 0) {
logger.warn('CPElement::findTask | This element will not be tracked...');
return undefined;
} else if (candidateTasks.length > 1) {
// logs as error but does not throw an error. use the first task found.
logger.error('CPElement::findTask | Multiple tasks found for Element bim_external_id: %s, category_id: %d, phase_id: %d, workarea_ids: %s'
, this.bimExternalId, this.categoryId, cpPhase.id, this.workareaIds);
}
return candidateTasks[0];
};
기대 동작: 각 element에 대해 (categoryId, srvTextureId, workareaId) 조합으로 정확히 1개의 task만 매칭되어야 한다.
실제 동작: 114개의 element에 대해 해당 조합으로 2개 이상의 task가 매칭되었다. 에러를 로깅하지만 첫 번째 task를 사용하여 정상 처리를 계속했다.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-sitetrack-preprocessor-agent status:error ("CPElement::findTask" OR "Multiple tasks found") @environment:production
service:cupixworks-sitetrack-preprocessor-agent (status:warn OR status:info) @environment:production
에러 로그 패턴 — 전체 114건이 27ms 내에 단일 burst로 발생:
[2026-04-14T16:50:30.759Z] CPElement::findTask | Multiple tasks found for Element bim_external_id: f21fea6f-2d2c-4b95-ac4f-961978663afe-0037ea53, category_id: 1030891, phase_id: 12597, workarea_ids: [ 103615 ]
[2026-04-14T16:50:30.762Z] CPElement::findTask | Multiple tasks found for Element bim_external_id: 817685e5-3a52-4938-bd00-bccfd48a482d-00eca161, category_id: 1030891, phase_id: 12597, workarea_ids: [ 103615 ]
...
[2026-04-14T16:50:30.786Z] CPElement::findTask | Multiple tasks found for Element bim_external_id: 92648c04-f0e4-4d23-b0ff-ef933626d8ee-003b08f3, category_id: 1030891, phase_id: 12597, workarea_ids: [ 103615 ]
공통 속성:
- Job ID:
1014318, Sitetrack ID:16358 - User:
alexander.shahinian@tutorperini.com(ID:41934) - Team:
tpc(ID:778) - 모든 에러가 동일한
category_id: 1030891,phase_id: 12597,workarea_ids: [103615] - 33개의 고유 BIM model UUID에 속한 114개의 서로 다른 element
실행 타임라인:
[16:48:31.211Z] [info] BaseService::runByMessage | id: 1014318
[16:48:31.259Z] [info] PreprocessorService::run | begin
[16:48:31.555Z] [info] PreprocessorService::createCPSitetrack | begin - sitetrackId: 16358
[16:48:31.587Z] [info] PreprocessorService::createCPSitetrack | end
[16:50:30.759Z] [error] <<< 114 CPElement::findTask errors (27ms burst) >>>
[16:50:38.081Z] [info] SiteTrackPreprocessorRunner::loadAllElementRecords | fallback map built with 26110 entries
[16:50:43.612Z] [info] SiteTrackPreprocessorRunner::loadAllElementRecords | fallback matches: 155
[16:52:59.065Z] [info] SiteTrackPreprocessorRunner::createTradeTaskTracesJson | loaded 162 TradeTaskTraces
[16:52:59.465Z] [info] FileSystemManager::saveCPSiteinsightsModel | Save total 26110 cpElementRecords
[16:53:49.528Z] [info] PreprocessorService::run | end
[16:53:50.449Z] [info] AwsQueueManager::deleteMessage | end
핵심 관찰: Job이 에러에도 불구하고 정상 완료되었다. 26,110개의 element record가 저장되었으며, SQS 메시지도 정상 삭제되었다. findTask는 에러를 로깅하지만 candidateTasks[0]을 반환하여 처리를 계속하는 설계이다.
Fix Recommendation#
즉시 조치 (Critical)#
심각도: 낮음 — 에러가 처리에 영향을 주지 않으므로 즉각적인 코드 수정은 불필요하다.
하지만 에러 로그가 114건 발생하여 모니터링 노이즈를 유발하므로, 로그 레벨을 error에서 warn으로 변경하는 것을 권장한다.
- 파일:
applications/agents/packages/models/src/cpelement.ts:127 logger.error→logger.warn으로 변경- 이유: 첫 번째 task를 사용하여 정상 처리가 되므로 에러가 아닌 경고가 적절하다. 현재 에러 레벨로 인해 불필요한 모니터링 알림이 발생한다.
단기 개선 (1주 이내)#
- 데이터 정합성 확인: facility
16358의category_id: 1030891,phase_id: 12597,workarea_id: 103615조합에 대해 중복 task가 있는지 서버 DB에서 확인하고, 중복된 task가 있으면 정리해야 한다. - Task API 중복 방지: 서버 측(tesla API)에서 동일
(category, phase, workarea)조합의 task가 중복 생성되지 않도록 유니크 제약조건 또는 validation을 추가하는 것을 검토한다.
장기 개선 (재발 방지)#
- 서버 측 유니크 제약조건: Task 테이블에
(category_id, phase_id, workarea_id)복합 유니크 인덱스를 추가하여 데이터 레벨에서 중복을 방지한다. - Agent 측 방어 로직:
findTask에서 중복이 발견될 때 어떤 task를 선택했는지(예: task ID)를 로그에 포함하면 디버깅에 도움이 된다.
Monitoring#
- 현재 에러 모니터링 쿼리:
service:cupixworks-sitetrack-preprocessor-agent status:error "CPElement::findTask" "Multiple tasks found"
- 로그 레벨을
warn으로 변경한 후에는 아래 쿼리로 추적:
service:cupixworks-sitetrack-preprocessor-agent status:warn "CPElement::findTask" "Multiple tasks found"
- 특정 facility에서 반복적으로 발생하는 경우를 추적하기 위해
sitetrack_id별 집계 모니터를 추가하는 것을 권장한다.
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial
- 에러가 처리 결과에 영향을 주지 않는 데이터 정합성 경고이다. Job은 정상 완료되었으며 26,110개의 element record가 모두 저장되었다. 첫 번째 매칭 task가 사용되므로 기능적 문제는 없으나, 중복 task 중 올바른 것이 선택되는지는 보장되지 않는다.