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_NewArray → FactoryBase::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#
- 2026-07-21 20:47:02 KST —
BimRevisionService::run시작 (si_trace_id=a052780a-c69b-457d-b72d-3aa6332272a3) - 2026-07-21 20:47:03 KST —
loadAllElements시작 (bim 17216, facility3fq7tj) - 2026-07-21 20:53:08 KST — 211,677개 element 로드 완료 (created 154,331 / deleted 57,346)
- 2026-07-21 20:53:10 KST —
REVISE-BEGIN로그,runBimCompare시작 - 2026-07-21 20:53:11 KST —
bim-compare.process자식 프로세스에executeIPC 발송 (query.entities.length=154331,checkExistence.length=57346) - 2026-07-21 21:00:39 KST — 자식 프로세스 V8 OOM. 마지막 GC 로그
Mark-Compact 7916.1 (8233.8) -> 7916.1 (8233.8) MB후FATAL ERROR및 native stack (Runtime_NewArray) - 2026-07-21 21:01:45 KST — 자식 프로세스
close이벤트,signal=SIGABRT,code=null—BimRevisionService::runcatch 블록에서bim_comparison_state=Error저장 - 2026-07-21 21:01:46 KST —
REVISE-END로그,elapsed_ms=883421(~14.7분)
Error Log#
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:150—BimRevisionService.run - 비교 준비:
bim-revision-service.ts:603—runBimCompare가query.entities154,331개,checkExistence57,346개를 담아 파라미터 구성 - IPC 위임:
packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:65—childProcessManager.execute<ForgeAgent.Result>('execute', params) - 자식 프로세스 진입:
packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67—execute()가ForgeAgent.extract(...)호출 - 자식 fork/stderr 캡처:
packages/base/src/manager/child-process.manager.ts:74,:138 - Failure point: 자식 프로세스의 V8 heap 소진 (native
v8::internal::Runtime_NewArray)
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);
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
}
});
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 이 설정되어 있으므로 자식도 동일한 상한을 갖는다.
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:107 의 close 핸들러가 signal=SIGABRT 로 pending IPC 를 reject → BimRevisionService::run catch 절이 bim_comparison_state=Error 로 상태 전이.
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 + 상위 컨텍스트):
service:cupixworks-any-bimrevision-agent "Ineffective mark-compacts"
service:cupixworks-any-bimrevision-agent BimRevisionService
핵심 자식 프로세스 stderr (2026-07-21 21:00:39 KST):
[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):
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):
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:74의fork()호출부에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 guard —
BimRevisionService::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 alarm —
system.mem.used또는runtime.node.mem.heap_used를service: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 에 사용할 수 있는 쿼리:
sum:trace.node.request.errors{service:cupixworks-any-bimrevision-agent,env:production}.as_count()
avg:runtime.node.mem.heap_used{service:cupixworks-any-bimrevision-agent,env:production}
avg:runtime.node.mem.heap_total{service:cupixworks-any-bimrevision-agent,env:production}
로그 기반 감시 (Datadog log analytics widget — timeseries 위젯이 아닌 monitor/dashboard log widget 용):
service:cupixworks-any-bimrevision-agent ("Ineffective mark-compacts" OR "heap out of memory" OR "SIGABRT")
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 도입이 함께 필요.