ChildProcessManager::setupEventHandlers | Child process stderr: 14: 0x12a6793 [/usr/bin/node]
RCA: BIM Compare Child Process V8 Heap Out-of-Memory
Overview#
What Happened#
cupixworks-any-bimrevision-agent (us-west-2, production) 에서 BIM revision compare 작업(bim_id=17216, src_revision_id=25433 V5 vs prev_revision_id=21647 V4) 을 처리하던 자식 프로세스가 V8 힙 한계인 약 8GB (--max-old-space-size=8192) 에 도달해 FATAL ERROR: Ineffective mark-compacts near heap limit 로 SIGABRT 되었다. 부모 프로세스는 child stderr 의 heap dump/native stack trace 각 라인을 error 레벨로 로깅하며 오늘 오전 21:00:39 KST 에 28건의 error 클러스터로 확산되었다 (status board 상 2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1 인시던트, resolved). 이 클러스터(14: 0x12a6793 [/usr/bin/node]) 는 그 stack trace 중 하나.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Node.js V8 FatalProcessOutOfMemory (child process) |
| exception.message | FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory |
| top_frame | packages/base/src/manager/child-process.manager.ts:140 (stderr forwarder) |
| runtime | Node.js (child forked from docker_services/80_run-agent.sh with --max-old-space-size=8192) |
| deploy | agent_version 10.98.0, forge-agents @cupixapps/forge-agents@10.98.0 |
| env | production, us-west-2, tenant cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| clark-vdc / bim-revision-agent | 25 개 클러스터 (동일 인시던트), 이 클러스터 1건 | BIM revision compare 1건 실패 (bim_id=17216, revision V4 → V5). modified=0, removed=0, exist=0 — 사용자에게 변경 리포트가 생성되지 않음. |
Timeline#
- 2026-07-21 20:53:10 KST —
BimRevisionService::REVISE-BEGIN—bim_id=17216, src_revision_id=25433 (V5), prev_revision_id=21647 (V4), cp_elements_count=211,677, cp_elements_created_count=154,331, cp_elements_deleted_count=57,346, si_trace_id=a052780a-c69b-457d-b72d-3aa6332272a3. IAD157 Coordination Model (Autodesk Forge SVF2). - 2026-07-21 21:00:39 KST — 자식 프로세스가 약 816.5s 실행 후 힙 7916 MB / 8233 MB 에 도달, Mark-Compact 가 회수 실패하고
FATAL ERROR ... JavaScript heap out of memory로 abort.ChildProcessManager::setupEventHandlers가 stderr 27+ 줄을 error 레벨로 흘려보내 클러스터가 다수 생성됨. (이 클러스터의 첫 로그) - 2026-07-21 21:01:45 KST — 부모의
Child process closed / exited이벤트 로깅,BimRevisionService::run이Process killed by signal SIGABRT (code: null)로 실패,REVISE-END status=error, elapsed_ms=883,421. - 2026-07-21 21:01:46 KST — SQS 메시지 삭제 및
/tmp/workspace/25433정리, 서비스는 자연 회복 (재현 없음).
Error Log#
ChildProcessManager::setupEventHandlers | Child process stderr: 14: 0x12a6793 [/usr/bin/node]
Impact#
- Service:
cupixworks-any-bimrevision-agent - Team: clark-vdc
- 발생 횟수: 1 (이 클러스터). 동일 crash 사건에서 파생된 sibling stderr 클러스터 총 24개 (status board 인시던트 상 25 clusters, 이 클러스터 포함)
- 최초 발생: 2026-07-21 21:00:39 KST
- 최근 발생: 2026-07-21 21:00:39 KST
Root Cause Summary#
BIM revision compare 자식 프로세스가 매우 큰 Autodesk Forge 모델 (IAD157 Coordination Model, cp_elements_count=211,677, V4→V5 사이 154,331 신규 + 57,346 삭제) 을 in-memory 로 diff 하는 도중 V8 old-generation 힙이 상한 (--max-old-space-size=8192, 실측 8233 MB) 에 도달했고, Mark-Compact 가 유효 회수를 못 해 V8 이 FatalProcessOutOfMemory 로 abort (SIGABRT) 했다. 자식 프로세스의 stderr 로 나가는 heap dump/native stack trace 각 라인이 ChildProcessManager::setupEventHandlers 의 stderr.on('data') 핸들러에서 logger.error 로 재발행되어 (packages/base/src/manager/child-process.manager.ts:140) 단일 crash 가 다수의 error 클러스터로 분화되었다. Root cause 는 (a) forge-agents 의 compare pipeline 이 model diff 를 스트리밍 없이 전체 fixed-array 로 할당하는 특성 + (b) 214k 규모 모델에 대한 heap headroom 부족이며, 로그 폭발 자체는 stderr 를 무조건 error 로 승격시키는 forwarder 의 secondary contributor.
Technical Analysis#
Code Path#
- Entry point:
applications/agents/packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:65—BimCompareManager.execute가 자식 프로세스로executeIPC 를 보냄. - Child worker:
applications/agents/packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:72—ForgeAgent.extract(...)호출로 두 URN 을 각각 로드해Compare리포트를 생성. - Failure point: 자식 프로세스 V8 힙. 부모의 stderr forwarder 는
applications/agents/packages/base/src/manager/child-process.manager.ts:140.
부모의 fork 지점 — execArgv 를 재정의하지 않으므로 --max-old-space-size=8192 가 자식에 그대로 상속된다. 로그의 7916 MB / 8233 MB 수치가 이를 확증한다.
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}`);
자식 crash 시 stderr 의 모든 라인이 error 레벨로 승격되어 collector 가 각 라인마다 fingerprint 를 만든 결과 25개 sibling 클러스터가 발생.
this.process.stderr?.on('data', (data: Buffer) => {
const text = data.toString();
logger.error(`ChildProcessManager::setupEventHandlers | Child process stderr: ${text}`);
});
자식 프로세스 내부에서는 forge-agents 라이브러리가 예외를 던지지 않은 상태로 OOM 이 발생 — 즉 try/catch 로 잡을 수 없는 프로세스-레벨 abort.
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
}
});
부모 매니저의 close 핸들러가 Process killed by signal SIGABRT 로 pending IPC 를 reject 하여 BimCompareManager.execute 는 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));
...
});
기대 동작 vs 실제 동작
- 기대: 214k element 규모의 revision compare 도 자식 프로세스가 성공적으로 diff 를 계산해
Compare.modified/removed/exist를 반환. - 실제: V8 heap 이 8GB 상한에 부딪혀 자식이 SIGABRT, 상위
BimRevisionService::REVISE-END는status=error, counts.total=0로 종료.
Log Evidence#
Datadog 쿼리:
service:cupixworks-any-bimrevision-agent status:error @environment:production "ChildProcessManager::setupEventHandlers"
작업 시작 로그 (bim_id=17216 compare 크기 문맥):
{
"timestamp": "2026-07-21 20:53:10 KST",
"level": "info",
"message": "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,
"src_forge_urn": "dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dGVzbGEtcHJvZHVjdGlvbi8xNzIxNl8xNzg0NjMzMjM0OTY3X0lBRDE1N19Db29yZGluYXRpb24lMjBNb2RlbF9DdXBpeC5ud2Q=",
"prev_forge_urn": "dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dGVzbGEtcHJvZHVjdGlvbi8xNzIxNl8xNzcyODEyMzI4NTA3X0lBRDE1N19Db29yZGluYXRpb24lMjBNb2RlbF9DdXBpeC5ud2Q="
}
V8 OOM 스택 트레이스 (자식 프로세스 stderr, 21:00:39 KST). 특히 heap 사용량 라인:
Child process stderr: [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
Child process stderr: [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
Child process stderr: <--- JS stacktrace --->
Child process stderr: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Native stack (Array 확장 중 OOM):
Child process stderr: 12: 0x10b7fb4 v8::internal::FactoryBase<v8::internal::Factory>::NewFixedArrayWithFiller(...) [/usr/bin/node]
Child process stderr: 16: 0x12cbb55 v8::internal::ArrayConstructInitializeElements(...) [/usr/bin/node]
Child process stderr: 17: 0x1512628 v8::internal::Runtime_NewArray(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]
부모 매니저의 종료/실패 로그:
21:01:45 error ChildProcessManager::setupEventHandlers | Child process exited
21:01:45 error ChildProcessManager::setupEventHandlers | Child process closed
21:01:45 error Process killed by signal SIGABRT (code: null)
21:01:45 error BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"
작업 종료 요약:
{
"timestamp": "2026-07-21 21:01:46 KST",
"level": "info",
"message": "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}
}
지난 7일간 동일 서비스에서 "JavaScript heap out of memory" 발생 횟수: 1 (아래 쿼리로 확인).
service:cupixworks-any-bimrevision-agent "JavaScript heap out of memory"
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 자식 프로세스가 V8 힙 한계(8GB) 를 초과해 OOM abort. compare 대상 모델이 214k elements 로 특히 크다. | Mark-Compact 7916.1 (8233.8) MB ... allocation failure; FATAL ERROR: Ineffective mark-compacts near heap limit; native stack 이 NewFixedArrayWithFiller / ArrayConstructInitializeElements / Runtime_NewArray 로 시작; REVISE-BEGIN 의 cp_elements_count=211,677, cp_elements_created_count=154,331; 재시작 없이 close/exit 로 SIGABRT (Process killed by signal SIGABRT). |
없음 — 로그, 스택, 시그널, 실행 시간(816.5s Mark-Compact 반복) 모두 일치. | Confirmed |
| H2 | Autodesk Forge API 오류 (API_NOT_FOUND 등) 가 원인. |
같은 서비스에서 20:42 대에 ForgeUtilsError: Resource not found ... AECModelData.json (errorCode: 'API_NOT_FOUND') 로그가 존재. |
이 오류는 크래시와 무관한 21:42 KST (약 40분 후) 다른 job. 21:00:39 크래시 스택 어디에도 ForgeUtilsError, handleApiError, ForgeClient 프레임이 없음. REVISE-END 는 API 오류가 아니라 SIGABRT. |
Rejected |
| H3 | ChildProcessManager 의 IPC 타임아웃 또는 부모 측 로직 버그. |
Process killed by signal SIGABRT (code: null) 문자열이 부모에서 로그됨. |
executeTimeoutMs 미설정 (child-process.manager.ts:39-47), 실제 사망은 child 내부의 V8 FatalProcessOutOfMemory 이며 부모는 close/exit 시그널을 관찰만 함. |
Rejected |
| H4 | 단일 crash 가 아니라 25건의 개별 error 가 발생했다. | 같은 인시던트에 25 cluster_ids. | Datadog 로그가 모두 Child process stderr: 접두어 + 하나의 V8 heap dump 를 라인별로 쪼갠 것이며, 모두 21:00:39 초 단위 timestamp 하나로 몰림. 부모의 stderr forwarder 가 각 라인을 logger.error 로 승격시켜 collector 가 sibling cluster 로 분화. |
Rejected (secondary contributor 로만 인정) |
Fix Recommendation#
즉시 조치 (Critical)#
- 자식 프로세스 힙 상한 상향 —
applications/agents/packages/base/src/manager/child-process.manager.ts:74의fork(...)호출 시execArgv를 명시적으로 설정하여 대용량 BIM compare 에 필요한 heap 을 확보. 예:execArgv: ['--max-old-space-size=<container_limit_-_1GB>']. 컨테이너 memory limit 을 함께 상향해야 실제 효과. Docker/ECS task definition 의 memory 값도 검토 (현재--max-old-space-size=8192는 부모 스크립트docker_services/80_run-agent.sh:3기준이며 자식이 이를 상속받고 있음). 주의: 단순 상향은 근본 처방이 아니라 완충. bim revision compare 는 214k 규모까지 자연 스케일되므로 (H1 로그의cp_elements_created_count=154,331) 300k+ 모델이 오면 다시 재발할 수 있음. - 자식 stderr forwarder 로그 레벨 downgrade —
applications/agents/packages/base/src/manager/child-process.manager.ts:138-141의stderr.on('data')핸들러가 stderr 를 무조건logger.error로 흘리고 있어 단일 crash 로 다수의 fingerprint 가 발생. 실제 fatal 판정은close/exit이벤트 (line 107, 122) 에서 이미 error 로 로깅되고 있으므로, stderr chunk 는warn또는debug로 낮추고 fatal 은 close 시점에 정리된 summary 하나로 집계. (팀 컨벤션 확인 필요 — 로거가 Error 객체를 처리하는 방식은 MEMORY 의 cplogger 규칙 참고)
단기 개선 (1주 이내)#
- REVISE-BEGIN 시 규모 기반 pre-flight guard —
cp_elements_count,cp_elements_created_count + cp_elements_deleted_count가 임계치를 넘으면 (a) 별도 higher-memory 큐로 라우팅하거나 (b) 사용자에게 revision 을 chunk 로 나눠 재요청하도록 안내. 로그 상 이 job 은 20:53:10 에 시작해 21:00:39 crash 까지 약 7.5분간 heap 을 눈덩이처럼 키움 — 사전 예측 가능한 시그널. - 자식 프로세스에
--heapsnapshot-near-heap-limit=1을 추가해 다음 OOM 발생 시 자동으로 heap snapshot 을 dump, 어느 라이브러리 (forge-agents/forge-utils) 가 어느 자료구조로 힙을 채우는지 원인 특정. 현재 native stack 에서 확인되는 프레임은NewFixedArrayWithFiller까지로 애플리케이션 프레임이 없음. - 부모 매니저에
executeTimeoutMs활성화 —child-process.manager.ts:39-47의 옵션이 이미 존재하나BimCompareManager에서 미설정. 15-20분 상한을 걸어 OOM 직전 slow-GC 루프(로그에 mu=0.006 로 나타남) 를 조기 종결.
장기 개선 (재발 방지)#
- forge-agents compare pipeline 을 streaming/chunked diff 로 리팩터링 — 214k elements 를 전체 array 로 유지하는 현재 알고리즘은 힙 상수시간이 elements 에 선형. Autodesk Forge SDK 의 model iterator 를 chunk 단위로 소비하고 partial
Compare.modified/removed/exist를 append-only 로 flush 하는 방식으로 재설계. - 에이전트 큐를 모델 크기별로 분리 — small/medium/large 큐를 두고 large 는 higher memory instance 에 배정.
cupix-tesla-bim-revision-agent-productionSQS 하나에 모든 크기가 몰리는 현재 구조는 tail-latency 및 재시도 정책 튜닝이 어려움.
Monitoring#
- Node child process OOM (across all agents) — 개수 시계열:
sum:trace.node.request.errors{service:cupixworks-any-bimrevision-agent} by {resource_name}.as_count()
- BIM revision agent 에서 SIGABRT 시그니처 카운트 (log-based custom metric 필요 시 아래 쿼리로 monitor 정의):
service:cupixworks-any-bimrevision-agent "Process killed by signal SIGABRT"
- Child stderr forwarder 폭발 (heap dump 라인) 시계열:
service:cupixworks-any-bimrevision-agent "JavaScript heap out of memory"
- BIM compare 작업 크기 분포 —
cp_elements_countP95/P99 를 log-based metric 으로 등록해 200k 이상 job 비율을 관측:
service:cupixworks-any-bimrevision-agent "REVISE-BEGIN"
- ECS/Fargate task memory utilization (컨테이너 레벨 상한 도달 여부):
avg:aws.ecs.memory_utilization{service:cupixworks-any-bimrevision-agent}
Risk Assessment#
- Risk level: medium — 단일 job 실패로 사용자 impact 는 국소적(1 revision compare 미생성)이나, 로그 폭증으로 온콜 노이즈가 크고 모델이 계속 커지면 재발 확률 높음.
- 예상 복잡도: standard —
execArgv세팅, stderr forwarder 레벨 조정, pre-flight guard 는 기존 인터페이스만 만짐. forge-agents streaming diff 는 별도 이니셔티브 (critical scale) 로 트랙 분리 권장.