ES /docs

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#

Datadog Logs

text
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#

  1. Entry point: sitetrack-preprocessor-runner.ts:9createTargetModels가 전체 모델 로딩 흐름을 시작한다.
typescript
// 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);
}
  1. Task 로딩: sitetrack-preprocessor-runner.ts:159-191createCPTasks에서 서버 API로 facility의 전체 task를 가져온 뒤, element에서 사용하는 categoryIdworkareaId로 필터링한다.
typescript
// 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를 반환하며, 중복을 제거하지 않는다.

  1. Phase에 task 할당: sitetrack-preprocessor-runner.ts:219-221loadAllPhases에서 각 phase에 해당하는 task를 할당한다.
typescript
// 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개 이상 할당될 수 있다.

  1. Element record 생성 시 task 검색: cpelement.ts:83-112makeElementRecords에서 각 phase의 cpTasks를 대상으로 findTask를 호출한다.
typescript
// 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);
}
  1. Failure point: cpelement.ts:115-132findTask에서 categoryId, srvTextureId, workareaId 3가지 조건으로 필터링한 결과 candidateTasks.length > 1이면 에러를 로깅한다.
typescript
// 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 쿼리:

text
service:cupixworks-sitetrack-preprocessor-agent status:error ("CPElement::findTask" OR "Multiple tasks found") @environment:production
text
service:cupixworks-sitetrack-preprocessor-agent (status:warn OR status:info) @environment:production

에러 로그 패턴 — 전체 114건이 27ms 내에 단일 burst로 발생:

text
[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

실행 타임라인:

text
[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.errorlogger.warn으로 변경
  • 이유: 첫 번째 task를 사용하여 정상 처리가 되므로 에러가 아닌 경고가 적절하다. 현재 에러 레벨로 인해 불필요한 모니터링 알림이 발생한다.

단기 개선 (1주 이내)#

  • 데이터 정합성 확인: facility 16358category_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#

  • 현재 에러 모니터링 쿼리:
text
service:cupixworks-sitetrack-preprocessor-agent status:error "CPElement::findTask" "Multiple tasks found"
  • 로그 레벨을 warn으로 변경한 후에는 아래 쿼리로 추적:
text
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 중 올바른 것이 선택되는지는 보장되지 않는다.