ES /docs

ChildProcessManager::setupEventHandlers | Child process stderr: FATAL ERROR: Ineffective mark-compac

RCA: BimCompareProcess child heap OOM (Ineffective mark-compacts)

Overview#

What Happened#

2026-07-21 21:00:39 KST, cupixworks-any-bimrevision-agent production 인스턴스에서 BIM revision 비교 작업을 실행하던 자식 프로세스(BimCompareProcess)가 V8 heap 한계(8GB)에 도달해 FATAL ERROR: Ineffective mark-compacts near heap limit 로 종료되었다. 이후 부모 프로세스는 자식으로부터 SIGABRT 를 감지하고 bim_id=17216, bim_revision_id=25433 (V5 vs V4) 의 비교를 실패 상태로 마무리했다.

Quick Facts#

Field Value
exception.class V8 fatal error (Node.js child process)
exception.message FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
top_frame v8::internal::Runtime_NewArrayFactoryBase::NewFixedArrayWithFiller
runtime Node.js (child forked from packages/base/src/manager/child-process.manager.ts), --max-old-space-size=8192
env production, us-west-2
affected_bim bim_id=17216, facility 3fq7tj, src_revision_id=25433 (V5), prev_revision_id=21647 (V4), forge-agents 10.98.0

Affected Teams#

Team / Domain Error Count Impact
clark-vdc 1 BIM revision 비교 1건(V4→V5) 실패, bim_comparison_state=Error 로 기록. 후속 element/model 업데이트와 S3 결과 업로드가 실행되지 않음

Timeline#

  1. 2026-07-21 20:47:02 KSTBimRevisionService::run 시작 (si_trace_id=a052780a-c69b-457d-b72d-3aa6332272a3)
  2. 2026-07-21 20:47:03 KSTloadAllElements 시작 (bim 17216, facility 3fq7tj)
  3. 2026-07-21 20:53:08 KST — 211,677개 element 로드 완료 (created 154,331 / deleted 57,346)
  4. 2026-07-21 20:53:10 KSTREVISE-BEGIN 로그, runBimCompare 시작
  5. 2026-07-21 20:53:11 KSTbim-compare.process 자식 프로세스에 execute IPC 발송 (query.entities.length=154331, checkExistence.length=57346)
  6. 2026-07-21 21:00:39 KST — 자식 프로세스 V8 OOM. 마지막 GC 로그 Mark-Compact 7916.1 (8233.8) -> 7916.1 (8233.8) MBFATAL ERROR 및 native stack (Runtime_NewArray)
  7. 2026-07-21 21:01:45 KST — 자식 프로세스 close 이벤트, signal=SIGABRT, code=nullBimRevisionService::run catch 블록에서 bim_comparison_state=Error 저장
  8. 2026-07-21 21:01:46 KSTREVISE-END 로그, elapsed_ms=883421 (~14.7분)

Error Log#

Datadog Logs

text
ChildProcessManager::setupEventHandlers | Child process stderr: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

Impact#

  • Service: cupixworks-any-bimrevision-agent
  • Team: clark-vdc
  • 발생 횟수: 1
  • 최초 발생: 2026-07-21 21:00:39 KST
  • 최근 발생: 2026-07-21 21:00:39 KST

BIM revision 25433 (V5) 의 비교가 중단되어 element revision 및 model ID mapping 업데이트, S3 결과 업로드가 실행되지 않았다. 최종 상태는 bim_comparison_state=Error 이며 재시도되지 않는 한 사용자는 이전 revision 상태를 그대로 보게 된다.

Root Cause Summary#

BimCompareProcess 자식 노드 프로세스가 forge-agents extract() 를 통해 두 개의 대형 SVF2 BIM revision (154,331 + 57,346 = 211,677 dbIds) 을 비교하는 동안 V8 heap 이 지정된 상한(--max-old-space-size=8192 MB) 에 도달해 mark-compact GC 가 더 이상 공간을 확보하지 못하고 Runtime_NewArray 할당이 실패하면서 프로세스가 SIGABRT 로 종료되었다. docker_services/80_run-agent.sh 는 부모 에이전트에만 8GB heap 을 설정하고 있고, fork() 로 생성된 자식 bim-compare.process 는 부모의 execArgv 를 상속하지만 실제 필요한 힙(대형 IAD157 Coordination 모델 전체를 메모리에 올려 비교)이 8GB 를 초과했다. 파라미터 검증 실패나 코드 버그가 아니라 입력 규모 대비 heap budget 부족이 근본 원인이다.

Technical Analysis#

Code Path#

  • Entry point (부모): packages/cupix-tesla-bim-revision-agent/src/bim-revision-service.ts:150BimRevisionService.run
  • 비교 준비: bim-revision-service.ts:603runBimComparequery.entities 154,331개, checkExistence 57,346개를 담아 파라미터 구성
  • IPC 위임: packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:65childProcessManager.execute<ForgeAgent.Result>('execute', params)
  • 자식 프로세스 진입: packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67execute()ForgeAgent.extract(...) 호출
  • 자식 fork/stderr 캡처: packages/base/src/manager/child-process.manager.ts:74, :138
  • Failure point: 자식 프로세스의 V8 heap 소진 (native v8::internal::Runtime_NewArray)
packages/cupix-tesla-bim-revision-agent/src/bim-revision-service.ts:617-647typescript
const params: BimComparerParams = {
    apiConfig: { clientId: Environment.ADF_CLIENT_ID, clientSecret: Environment.ADF_CLIENT_SECRET },
    urn: cpBimRevision.forgeUrn,
    formatType: cpBimRevision.forgeFormatType,
    region: cpBimRevision.forgeRegion as 'US' | 'EMEA' | undefined,
    bimOrigin: bimSetting.origin,
    previousUrn: cpPreviousBimRevision.forgeUrn,
    previousFormatType: cpPreviousBimRevision.forgeFormatType,
    previousRegion: cpPreviousBimRevision.forgeRegion as 'US' | 'EMEA',
    query: {
        entities: cpBim.getBimEntities(),                       // 154,331 entries
        checkExistence: checkExistenceList,                     // 57,346 entries
        levels: this.iLevels,
        bimSetting: bimSetting,
        propNames: ['Volume'],
        customPropNames: cpBim.getCustomPropNames(),
        configuration: { boxTolerance }
    }
};
// ...
const bimCompareResults = await this.bimCompareManager.execute(params);
packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67-82typescript
private async execute(params: BimComparerParams): Promise<ForgeAgent.Result> {
    try {
        // ...
        const result: ForgeAgent.Result = await ForgeAgent.extract(params.apiConfig, params.urn, {
            region: params.region,
            extractRoom: false,
            extractMeta: false,
            extractCompare: {
                urn: params.previousUrn,
                region: params.previousRegion,
                query: params.query
            }
        });
packages/base/src/manager/child-process.manager.ts:74-77typescript
this.process = fork(actualScriptPath, {
    stdio: ['inherit', stdOut, stdErr, 'ipc'],
});
logger.debug(`ChildProcessManager::start | Child process forked with PID: ${this.process.pid}, stdio: ${this.options.stdioMode}`);

fork() 는 명시적 execArgv 를 주지 않으면 부모의 process.execArgv 를 그대로 상속한다. 부모는 docker_services/80_run-agent.sh 로 8GB heap 이 설정되어 있으므로 자식도 동일한 상한을 갖는다.

docker_services/80_run-agent.sh:3-9bash
node --max-old-space-size=8192 /tmp/agent/dist/app.cjs \
  --env_name $CPX_ENVIRONMENT_NAME \
  --api_endpoint $CPX_API_ENDPOINT \
  ...

기대 동작: 자식 프로세스가 두 revision 의 SVF2 model data 를 로드해 dbId/geometry 비교를 완료하고 ForgeAgent.Result 를 반환.

실제 동작: 비교 중 대형 배열 할당 (Runtime_NewArray) 시 heap 이 소진(7916/8233 MB) 되고 mark-compact 가 더 이상 공간을 확보하지 못해 V8 이 프로세스를 abort. 부모 child-process.manager.ts:107close 핸들러가 signal=SIGABRT 로 pending IPC 를 reject → BimRevisionService::run catch 절이 bim_comparison_state=Error 로 상태 전이.

packages/base/src/manager/child-process.manager.ts:107-120typescript
this.process.on('close', (code: number | null, signal: string | null) => {
    logger.error('ChildProcessManager::setupEventHandlers | Child process closed', { code, signal });
    const errorMsg = signal
        ? `Process killed by signal ${signal} (code: ${code})`
        : `Process exited with code ${code}`;
    this.rejectAllPending(new Error(errorMsg));
    this.process = undefined;
    this.emit('close', code, signal);
});

Log Evidence#

Datadog 쿼리 (자식 프로세스 fatal + 상위 컨텍스트):

text
service:cupixworks-any-bimrevision-agent "Ineffective mark-compacts"
text
service:cupixworks-any-bimrevision-agent BimRevisionService

핵심 자식 프로세스 stderr (2026-07-21 21:00:39 KST):

text
[45:0x17bccd20]   814987 ms: Mark-Compact 7909.6 (8231.3) -> 7909.6 (8227.1) MB, 1558.72 / 0.00 ms  (average mu = 0.091, current mu = 0.008) allocation failure; scavenge might not succeed
[45:0x17bccd20]   816543 ms: Mark-Compact 7916.1 (8233.8) -> 7916.1 (8233.8) MB, 1547.82 / 0.00 ms  (average mu = 0.050, current mu = 0.006) allocation failure; scavenge might not succeed
<--- JS stacktrace --->
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
----- Native stack trace -----
 1: 0xb78db3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/usr/bin/node]
 2: 0xee8300 v8::Utils::ReportOOMFailure(...) [/usr/bin/node]
 3: 0xee85e7 v8::internal::V8::FatalProcessOutOfMemory(...) [/usr/bin/node]
...
16: 0x12cbb55 v8::internal::ArrayConstructInitializeElements(...) [/usr/bin/node]
17: 0x1512628 v8::internal::Runtime_NewArray(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]

부모 서비스 상태 전이 (21:01:45–21:01:46 KST):

text
ChildProcessManager::setupEventHandlers | Child process closed          (signal=SIGABRT, code=null)
ChildProcessManager::setupEventHandlers | Child process exited
BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"
BimRevisionService::REVISE-END | {"bim_id":17216,"src_revision_id":25433,"prev_revision_id":21647,"si_trace_id":"a052780a-c69b-457d-b72d-3aa6332272a3","elapsed_ms":883421,"status":"error","error_message":"Process killed by signal SIGABRT (code: null)","counts":{"total":0,"modified":0,"removed":0,"exist":0}}

입력 규모 (20:53:10–20:53:11 KST):

text
BimRevisionService::REVISE-BEGIN | {"bim_id":17216,"src_revision_id":25433,"src_revision_name":"V5","prev_revision_id":21647,"prev_revision_name":"V4","agent_version":"10.98.0","cp_elements_count":211677,"cp_elements_created_count":154331,"cp_elements_deleted_count":57346}
BimRevisionService::runBimCompare | params - query.entities.length: 154331, checkExistence.length: 57346, levels: [ROOF, LEVEL 2, LEVEL 1]

지난 30일 동안 동일 서비스에서 "Ineffective mark-compacts" 로그는 이 1건뿐 — 만성적인 OOM 이 아니라 이 특정 대형 revision 페어에서만 발생.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 자식 프로세스 heap 이 SVF2 대형 revision 비교 부하를 감당하지 못해 V8 OOM Ineffective mark-compacts near heap limit ... Allocation failed, GC 로그 7916/8233 MB, Runtime_NewArray native frame, 입력 211,677 elements Confirmed
H2 자식 프로세스 IPC/코드 버그 (예: 무한 배열 성장, 메모리 leak) 없음. 로그에는 정상적으로 forge-agents ForgeAgent.extract 실행 중 GC 반복이 확인됨 첫 발생 (30일 내 유일), 동일 서비스에서 다른 revision 비교는 성공, agent version 10.98.0 은 동일 버전으로 다른 요청 처리 Rejected
H3 부모 프로세스에 heap 상한이 설정 안 되어 있고 자식은 기본 heap(~4GB) 로 실행되어 OOM docker_services/80_run-agent.sh--max-old-space-size=8192 확인, GC 로그가 8233 MB (~8GB) 상한을 보여줌 → 자식이 부모 execArgv 를 상속했음 Rejected
H4 Forge API 응답이 비정상적으로 커서 (예: API 스펙 변경) heap 을 소진 비교 대상 URN 이 커스텀 대형 모델 (IAD157 Coordination Model) 임 단발 이벤트, 다른 URN 비교는 성공 — API 응답 크기가 아니라 이 특정 모델 크기가 원인 Inconclusive (H1 의 subset — 데이터 규모)
H5 외부 dependency 장애 (S3, Forge) Datadog 로그에 network/S3 에러 없음, status-board 결과 svc:cupixworks-any-bimrevision-agent::unknown 는 이 heap OOM 이 트리거한 자체 클러스터 burst (25건은 동일 stderr line 이 분리 클러스터로 잡힌 것) Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 자식 프로세스 heap 상한 상향 검토packages/base/src/manager/child-process.manager.ts:74fork() 호출부에 execArgv: [...process.execArgv, '--max-old-space-size=<N>'] 로 자식만 부모보다 큰 heap 을 갖도록 조정하거나, docker_services/80_run-agent.sh:3 의 상한 자체를 상향. 컨테이너 메모리 한도를 먼저 확인해야 하므로 SRE 조율 필요 (자동 code-fix 대상 아님 — Fix Scope Filtering 룰에 따라 사전 조율 항목).
  • 실패한 revision 재실행 절차bim_id=17216, bim_revision_id=25433 는 현재 Error 상태로 남아 있음. clark-vdc 팀과 협의해 heap 상향 이후 재시도하거나 element 개수를 나눠 처리하는 경로로 재실행할지 결정.

단기 개선 (1주 이내)#

  • 입력 규모 기반 pre-flight guardBimRevisionService::runBimCompare (bim-revision-service.ts:603) 진입 직전에 query.entities.length + checkExistence.length 가 임계값(예: 150,000) 을 넘으면 명확한 에러 코드(BimRevisionInputTooLarge) 로 조기 실패시켜 8분간 heap 을 채우다 SIGABRT 로 끝나는 사이클을 피한다. 현재는 실패 감지까지 elapsed 883s 소요.
  • 자식 프로세스 exit 원인 구분 로깅child-process.manager.ts:107 의 close 핸들러에서 signal=SIGABRT 인 경우 heap OOM 가능성을 별도 warn 로 남기고 후속 알림 파이프라인에서 재시도 가능/불가능을 구분한다.
  • Datadog metric alarmsystem.mem.used 또는 runtime.node.mem.heap_usedservice:cupixworks-any-bimrevision-agent 로 필터해 --max-old-space-size 대비 80% 초과 시 warn. (아래 Monitoring 참조)

장기 개선 (재발 방지)#

  • BIM compare 스트리밍/청크화 — 현재는 query.entities 전체를 IPC 로 전달하고 자식이 두 SVF2 모델을 통째로 heap 에 올린다. dbId 단위 배치 처리 또는 forge-agents 측 스트리밍 API 도입으로 최대 heap peak 을 낮춘다.
  • 에이전트 인스턴스 사이징 프로파일링 — IAD157 급의 대형 coordination 모델 실사용 사례가 추가로 있다면 별도 인스턴스 크기 프로파일(예: 16GB heap ECS task) 로 라우팅.
  • Autodesk Forge 응답 캐싱/디스크 spill — 반복적으로 큰 revision 을 비교하는 경우 model data 를 임시 파일로 흘려보내 heap 상주를 줄인다.

Monitoring#

Release dashboard timeseries widget 에 사용할 수 있는 쿼리:

text
sum:trace.node.request.errors{service:cupixworks-any-bimrevision-agent,env:production}.as_count()
text
avg:runtime.node.mem.heap_used{service:cupixworks-any-bimrevision-agent,env:production}
text
avg:runtime.node.mem.heap_total{service:cupixworks-any-bimrevision-agent,env:production}

로그 기반 감시 (Datadog log analytics widget — timeseries 위젯이 아닌 monitor/dashboard log widget 용):

text
service:cupixworks-any-bimrevision-agent ("Ineffective mark-compacts" OR "heap out of memory" OR "SIGABRT")
text
service:cupixworks-any-bimrevision-agent "REVISE-END" @status:error

권장 알림 임계값: 5분 동안 Ineffective mark-compacts 매칭 로그 ≥1 건이면 즉시 페이지. REVISE-END @status:error 는 시간당 3건 이상이면 warn.

Risk Assessment#

  • Risk level: medium — 단발성이지만 heap 상한이 정해진 이상 대형 coordination 모델이 유입되면 재현 가능. 컨테이너 메모리 한도와 얽혀 있어 pure-code fix 로는 완전 해결 불가.
  • 예상 복잡도: standard — heap 상향 자체는 trivial 하나 인프라(ECS task memory, --max-old-space-size) 조율과 pre-flight guard 도입이 함께 필요.