ChildProcessManager::setupEventHandlers | Child process stderr: 1: 0xb78db3 node::OOMErrorHandler(c
RCA: ChildProcessManager child process OOM (JavaScript heap out of memory)
Overview#
What Happened#
BIM revision agent(cupixworks-any-bimrevision-agent)의 BimCompareProcess 자식 프로세스가 bim_id=17216 리비전 비교(V4→V5, 총 211,677 elements) 실행 중 V8 힙 상한(≈8GB)에 도달해 FATAL ERROR: Ineffective mark-compacts near heap limit 로 종료되었다. 부모 프로세스는 자식의 stderr 스트림을 그대로 error 레벨로 로깅했고(ChildProcessManager::setupEventHandlers | Child process stderr: ...), 이후 자식이 SIGABRT 로 죽자 BimRevisionService::run 이 실패로 종료했다. 총 25개의 에러 클러스터가 동일 시각에 발생했으며 이 파일은 그중 OOM 헤더 라인 하나에 해당한다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | (no exception — child-process stderr forwarded to error log) |
| exception.message | ChildProcessManager::setupEventHandlers | Child process stderr: 1: 0xb78db3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/usr/bin/node] |
| top_frame | packages/base/src/manager/child-process.manager.ts:140 |
| runtime | Node.js on /usr/bin/node (agent runs with node --max-old-space-size=8192) |
| env | production, region us-west-2, tenant cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| clark-vdc (bim-revision-agent) | 25 clusters at 2026-07-21 21:00:39 KST | 1건의 BIM 리비전 비교 작업 실패 (bim_id=17216, src_revision_id=25433, prev_revision_id=21647, si_trace_id a052780a-c69b-457d-b72d-3aa6332272a3). 사용자가 요청한 리비전 diff 산출 실패. |
Timeline#
- 2026-07-21 20:53:10 KST —
BimRevisionService::REVISE-BEGIN— bim_id 17216, cp_elements_count 211,677 (created 154,331 / deleted 57,346), agent_version 10.98.0. - 2026-07-21 20:53:11 KST —
BimRevisionService::runBimCompare호출, query.entities.length=154,331, checkExistence.length=57,346, 3 levels, matchingStrategytreePath. - 2026-07-21 21:00:39 KST — 자식 프로세스가 V8 OOM. stderr 로
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory및 네이티브 스택 트레이스(24 프레임) 출력. 부모가 그대로 error 레벨 로깅. - 2026-07-21 21:01:45 KST —
ChildProcessManager::setupEventHandlers | Child process closed,Process killed by signal SIGABRT (code: null).BimRevisionService::run에러 반환. - 2026-07-21 21:01:46 KST —
BimRevisionService::REVISE-END(statuserror, elapsed_ms 883,421 ≈ 14분 43초).
Error Log#
ChildProcessManager::setupEventHandlers | Child process stderr: 1: 0xb78db3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/usr/bin/node]
Impact#
- Service:
cupixworks-any-bimrevision-agent - Team: clark-vdc
- 발생 횟수: 1 (이 클러스터). 동일 이벤트로 인해 status-board 인시던트
2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1하에 25개 클러스터가 함께 개봉·해소되었다. - 최초 발생: 2026-07-21 21:00:39 KST
- 최근 발생: 2026-07-21 21:00:39 KST
Root Cause Summary#
BimCompareProcess 자식 프로세스가 ForgeAgent.extract 로 154,331개 엔티티에 대한 tree-path 매칭·비교 연산을 수행하는 동안 V8 힙 사용량이 상한(부모에서 상속된 --max-old-space-size=8192, 즉 8GB) 을 초과해 OOM 으로 종료됐다. Datadog stderr 로그의 GC 리포트가 최종 mark-compact 직전 7916.1MB 를 보고하고 있어 heap ceiling 도달이 사실로 확인된다. 자식이 OOM으로 SIGABRT 종료되면 부모의 ChildProcessManager::setupEventHandlers 가 close/exit 이벤트를 잡아 pending IPC 요청을 reject 하고, BimCompareManager::execute 가 이 에러를 재던져 BimRevisionService::run 이 실패한다. 근본 원인은 (a) 자식 프로세스에 명시적 힙 한도가 설정되어 있지 않아 부모의 8GB 값을 그대로 상속하고 있고, (b) 154K entities × treePath 매칭 을 in-memory 로 수행하는 forge-agents extract 경로가 이 크기의 revision 을 처리하기에 부족하다는 두 지점이 결합한 결과다.
Technical Analysis#
Code Path#
- Entry point:
packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:27(new ChildProcessManager(__dirname, 'process/bim-compare.process')) - Child fork:
packages/base/src/manager/child-process.manager.ts:74-77—fork()는execArgv를 지정하지 않으므로 Node 는 부모의 CLI 인자(--max-old-space-size=8192,docker_services/80_run-agent.sh:3)를 자식에 상속. - Child work:
packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67-80(ForgeAgent.extract호출,extractCompare.query.entities154,331개). - Failure point (V8): 자식 프로세스 힙 8GB 도달 →
V8::FatalProcessOutOfMemory→SIGABRT. - Log emission:
packages/base/src/manager/child-process.manager.ts:138-141— 자식 stderr 를 그대로error레벨로 forwarding. 클러스터 헤더 라인은 이 forwarding 의 첫 프레임. - Cleanup:
packages/base/src/manager/child-process.manager.ts:107-120—close이벤트에서rejectAllPending(new Error("Process killed by signal SIGABRT ...")). - Rethrow:
packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:64-78—catch블록이ErrorCode.Agent.BimRevisionExtractorExecute를 설정하고 에러를 재던짐.
기대 동작: 자식 프로세스가 comparison 결과를 IPC 로 반환.
실제 동작: 자식이 heap ceiling 에 부딪혀 SIGABRT 로 종료, 부모는 Process killed by signal SIGABRT 로 pending IPC 를 reject.
try {
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}`);
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);
});
this.process.stderr?.on('data', (data: Buffer) => {
const text = data.toString();
logger.error(`ChildProcessManager::setupEventHandlers | Child process stderr: ${text}`);
});
private async execute(params: BimComparerParams): Promise<ForgeAgent.Result> {
try {
this.log(`BimCompareProcess::execute | forge-agents version: ${forgeAgentsPkg.version}, spec_version: ${SpecVersion}`);
this.log('BimCompareProcess::execute | start');
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
}
node --max-old-space-size=8192 /tmp/agent/dist/app.cjs \
Log Evidence#
Datadog query 재현:
service:cupixworks-any-bimrevision-agent status:error
시간창: 2026-07-21T11:55:00Z → 2026-07-21T12:05:00Z (28개 결과).
핵심 GC 리포트(발생 직전 두 번의 Mark-Compact — heap 이 8GB 상한에 걸리고 있음):
[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
자식 stderr 헤더:
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Native stack (관련 프레임):
1: 0xb78db3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/usr/bin/node]
2: 0xee8300 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/usr/bin/node]
3: 0xee85e7 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/usr/bin/node]
...
16: 0x12cbb55 v8::internal::ArrayConstructInitializeElements(v8::internal::Handle<v8::internal::JSArray>, v8::internal::Arguments<(v8::internal::ArgumentsType)1>*) [/usr/bin/node]
17: 0x1512628 v8::internal::Runtime_NewArray(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]
상단 프레임이 Runtime_NewArray / ArrayConstructInitializeElements 인 것에서 대규모 배열 할당(154K entities 를 다루는 tree-path 매칭이 유력) 시점에 OOM 이 터졌음이 시사된다.
작업 컨텍스트:
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","si_trace_id":"a052780a-c69b-457d-b72d-3aa6332272a3","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: [3 levels], ...
BimRevisionService::runBimCompare | matchingStrategy: treePath
에필로그:
BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"
BimRevisionService::REVISE-END | {..., "elapsed_ms":883421, "status":"error", "error_message":"Process killed by signal SIGABRT (code: null)", "counts":{"total":0,"modified":0,"removed":0,"exist":0}, ...}
Status-board 결과 (동일 이벤트로 25개 클러스터가 함께 개봉·해소):
scope: svc:cupixworks-any-bimrevision-agent::unknown
incident id: 2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1
status: resolved (started 2026-07-21T12:00:39.174Z, resolved 2026-07-21T12:01:45.911Z)
cluster_ids: 25 (this cluster included)
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 자식 프로세스가 V8 heap 상한(8GB)에 도달하여 OOM abort | stderr에 FATAL ERROR: Ineffective mark-compacts near heap limit ... JavaScript heap out of memory, GC 리포트 7916.1(8233.8)MB 도달, 부모의 --max-old-space-size=8192 를 자식이 상속, 스택 상단이 Runtime_NewArray, 154,331 entities 처리 중 |
— | Confirmed |
| H2 | 컨테이너/Pod 레벨 OOM Kill (OS SIGKILL) | 자식이 종료 신호를 SIGABRT 로 받았음(부모 로그: Process killed by signal SIGABRT). Node 의 V8 OOM 핸들러가 abort() 를 호출해 SIGABRT 를 유발; 커널 OOM Killer 는 통상 SIGKILL. 부모 프로세스는 살아남아 이후 로그를 계속 남겼음. |
부모가 함께 죽지 않았다는 사실이 컨테이너 OOM 을 배제. | Rejected |
| H3 | ChildProcessManager 의 executeTimeoutMs 초과로 인한 SIGKILL | child-process.manager.ts:32 에 executeTimeoutMs 옵션이 있으나 BimCompareManager 생성자(bim-compare.manager.ts:27)는 옵션을 넘기지 않아 timeout 이 없음. 실제 종료는 SIGKILL 이 아닌 SIGABRT. |
— | Rejected |
| H4 | Forge/Autodesk 다운스트림 서비스 장애 | 로그에 Forge HTTP 에러나 4xx/5xx 응답 흔적 없음. 실패는 in-process V8 abort. status-board 는 dep:* 가 아닌 svc:* 인시던트로 개봉. | — | Rejected |
| H5 | 특정 리비전 페어(V4→V5)의 tree-path 매칭 조합에서 폭발적 메모리 사용 | 154,331 created + 57,346 deleted 로 규모가 크고 matchingStrategy: treePath 사용, 스택 상단이 대규모 배열 할당 프레임. |
정확한 forge-agents 내부 알고리즘 heap profile 이 없어 트리거 지점을 라인 단위로 확정 불가 (uncertain — needs verification). | Inconclusive (contributing factor) |
Fix Recommendation#
즉시 조치 (Critical)#
packages/base/src/manager/child-process.manager.ts:73-77fork()호출에 자식 프로세스 heap 한도를 명시적으로 지정할 수 있도록execArgv를 옵션화한다. 방향:ChildProcessOptions에maxOldSpaceSizeMb?: number를 추가하고,fork()시execArgv: [\--max-old-space-size=${maxOldSpaceSizeMb}`]` 를 전달.BimCompareManager는 대용량 리비전(≥100K entities) 을 다루므로 부모보다 큰 값(예: 12288 또는 16384) 을 지정. 단, 컨테이너 memory limit 이 이를 수용하는지 인프라 확인 필요 (uncertain — needs verification).
- 그 외 자체 코드 변경 없이도, agent 컨테이너의 memory limit 을 검토해 실제 사용 프로파일 대비 여유가 있는지 확인 (조율 필요 항목 — cupix-infrastructure 팀).
단기 개선 (1주 이내)#
BimRevisionService::runBimCompare진입 시query.entities.length가 임계값(예: 100,000) 을 넘으면 chunk 로 분할해 여러 회ForgeAgent.extract를 호출하거나, 명시적 사전 검증으로 실패 원인을 명확히 로깅. 방향: entities 를 level/modelId 기준으로 분할해 부분 결과를 병합.ChildProcessManager에 자식 프로세스 heap usage 를 주기적으로process.memoryUsage()로 IPC 리포팅해, OOM 근접 시 사전 경고 로그를 남긴다. 이는 다음 대용량 리비전에서 임계값 조정에 근거를 제공.- 자식 stderr 를 무조건
error레벨로 forwarding 하는 현재 로깅(child-process.manager.ts:138-141) 은 25개 별도 error 클러스터를 생성해 노이즈가 되므로, stack frame 라인들을 하나의 aggregated 에러(예: 처음 라인 + 원본 stderr 전체를error.stderr필드로 첨부) 로 묶는 로깅 개선을 고려. 방향만 제시하며 구현은 별도 검토 필요.
장기 개선 (재발 방지)#
- forge-agents
extract의 메모리 프로파일을 정식으로 벤치마킹하고, entity 수 대비 예상 peak heap 을 문서화. 이 벤치마크를 기반으로 (1) child heap 한도, (2) chunking 임계값, (3) 컨테이너 memory limit 을 함께 조정. - BIM 리비전 파이프라인에 사전 크기 예측(예: previous/current revision entity count 로부터 예상 peak) 을 넣어, 특정 임계 초과 시 별도 큐(대용량 전용, 큰 인스턴스) 로 라우팅.
- 자식 프로세스 OOM 시 커널 core dump 나
--heapsnapshot-on-oom기반 아티팩트를 저장·업로드하는 파이프라인 정비 (재현이 어려운 대용량 케이스에서 유일한 사후 분석 수단).
Monitoring#
Datadog 쿼리 (release dashboard timeseries widget 에 그대로 사용 가능):
- OOM stderr forwarding 발생률 (agent-side signal):
service:cupixworks-any-bimrevision-agent status:error "JavaScript heap out of memory"
- 자식 프로세스 SIGABRT 종료율:
service:cupixworks-any-bimrevision-agent status:error "Process killed by signal SIGABRT"
- BIM revision 실패율 (서비스 관점):
service:cupixworks-any-bimrevision-agent "BimRevisionService::REVISE-END" "\"status\":\"error\""
- 대용량 리비전 감지 (사전 신호, entities 수가 큰 요청 발생):
service:cupixworks-any-bimrevision-agent "BimRevisionService::runBimCompare | params - query.entities.length"
Alerts: JavaScript heap out of memory 로그가 5분 창에서 1건 이상 발생 시 clark-vdc 팀 알림.
Risk Assessment#
- Risk level: medium — 단일 리비전 작업 실패이지만 대용량(≥100K entities) 리비전에서 재발 가능. 부모 프로세스는 살아남아 다른 요청은 계속 처리하므로 서비스 전면 장애는 아님.
- 예상 복잡도: standard — 즉시 조치는
ChildProcessOptions확장으로 국소적이나, chunking / 벤치마크는 forge-agents 팀과의 조율 필요.