ChildProcessManager::setupEventHandlers | Child process stderr: [45:0x17bccd20] 814987 ms: Mark-Co
RCA: ChildProcessManager | Child process stderr — V8 Heap OOM (Mark-Compact allocation failure)
Overview#
What Happened#
2026-07-21 21:00:39 KST 에 cupixworks-any-bimrevision-agent (us-west-2, tenant cupix) 의 BIM comparison child process 가 V8 heap 8GB 한계에 도달해 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory 로 종료되었다. 부모 프로세스의 ChildProcessManager stderr listener 가 V8 GC 진단 라인을 logger.error 로 그대로 전달하면서 클러스터에 잡혔고, 이어서 SIGABRT 로 child process 가 close 되어 BimRevisionService::REVISE-END 가 status:error (elapsed_ms=883421) 로 마감되었다. 동일 시각에 같은 서비스에서 25개 클러스터가 함께 발생했다 (status-board incident 2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1).
Quick Facts#
| Field | Value |
|---|---|
| exception.class | FATAL ERROR: Ineffective mark-compacts near heap limit |
| exception.message | Allocation failed - JavaScript heap out of memory |
| top_frame | packages/base/src/manager/child-process.manager.ts:140 (stderr pipe → logger.error) |
| runtime | Node.js child forked via fork(), --max-old-space-size=8192 inherited from parent |
| env | production, us-west-2, tenant cupix |
| affected job | bim_id=17216, src_revision_id=25433, prev_revision_id=21647 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| clark-vdc / cupixworks-any-bimrevision-agent | 2 (본 클러스터) + 23 (동일 인시던트 다른 클러스터) | 단일 BIM revision job (src_revision_id=25433) 실패, 사용자는 revision diff 결과를 받지 못함 |
Timeline#
- 2026-07-21 20:47:02 KST (약) — BIM revision job 시작 (elapsed_ms=883421 역산;
BimRevisionService::REVISE-END로그의 elapsed 값 기준). - 2026-07-21 21:00:39 KST — child process V8 heap 이 8GB 근처 (
7909.6 → 7916.1 MB) 에서 두 차례 Mark-Compact 실패, FATAL OOM 발생. 스택에Runtime_NewArray → ArrayConstructInitializeElements → NewFixedArrayWithFiller표시. - 2026-07-21 21:00:39 KST —
ChildProcessManager::setupEventHandlers | Child process stderr로그 다수 emit (본 클러스터의 first_seen/last_seen). - 2026-07-21 21:01:45 KST — child process close/exit,
Process killed by signal SIGABRT (code: null).BimRevisionService::runerror,REVISE-END status:error기록. - 2026-07-21 21:01:46 KST —
BaseService::cleanUpAnythingRelatedModel path:/tmp/workspace/25433, SQS 메시지 삭제 (dd2cac85-c941-428d-8fbb-bb97168d9bfe).
Error Log#
ChildProcessManager::setupEventHandlers | 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
Impact#
- Service:
cupixworks-any-bimrevision-agent - Team: clark-vdc
- 발생 횟수: 2 (본 클러스터), 동일 인시던트 25 클러스터 합산 시 훨씬 큼
- 최초 발생: 2026-07-21 21:00:39 KST
- 최근 발생: 2026-07-21 21:00:39 KST
BIM revision job 한 건이 실패했고 사용자는 revision 비교 결과를 받지 못했다. Worker container 는 살아있으나 child process 는 SIGABRT 로 종료되어, 동일 revision 을 재시도해도 입력 크기가 같다면 다시 OOM 이 발생할 가능성이 매우 높다.
Root Cause Summary#
BIM comparison child process (packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts 를 fork() 로 실행) 가 실제 BIM 데이터를 로드/비교하는 도중 V8 old-space 를 소진했다. 부모의 --max-old-space-size=8192 (docker_services/80_run-agent.sh:3) 는 fork() 로 자식에게도 상속되어 자식도 8GB 한계에서 OOM. V8 stack trace 의 Runtime_NewArray → ArrayConstructInitializeElements → NewFixedArrayWithFiller 는 대형 배열 할당 시점에 OOM 이 터졌음을 시사하며, 콜스택은 @cupixapps/forge-agents 의 CompareExtractor.compare → GridAlignment.computeGridCorrection → Asset.get → ModelDataClient.getData 경로에서 발생. 이 클러스터의 "에러" 자체는 근본 원인이 아니라 결과이다: 부모의 stderr.on('data') 리스너 (packages/base/src/manager/child-process.manager.ts:138-141) 가 자식의 V8 GC 진단 라인을 무조건 logger.error 로 흘려보내기 때문에, OOM 이 나든 정상 진단이 나든 stderr 는 전부 error-level 로 뜬다.
Technical Analysis#
Code Path#
- Entry point: BIM revision SQS 메시지 수신 →
BimRevisionService::run - Child fork:
packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:27—new ChildProcessManager(__dirname, 'process/bim-compare.process') - Actual fork:
packages/base/src/manager/child-process.manager.ts:74—fork(actualScriptPath, { stdio: ['inherit', stdOut, stdErr, 'ipc'] }) - stderr pipe:
packages/base/src/manager/child-process.manager.ts:138-141 - Failure point: 자식 프로세스 내부
@cupixapps/forge-agents/extractor/compare-extractor/compare-extractor.js:62(CompareExtractor.compare) 에서 대형 배열 할당 중 V8 heap 8GB 소진.
const stdOut = this.options.stdioMode === 'pipe' ? 'pipe' : 'inherit';
const stdErr = this.options.stdioMode === 'pipe' ? 'pipe' : 'inherit';
try {
this.process = fork(actualScriptPath, {
stdio: ['inherit', stdOut, stdErr, 'ipc'],
});
this.process.stderr?.on('data', (data: Buffer) => {
const text = data.toString();
logger.error(`ChildProcessManager::setupEventHandlers | Child process stderr: ${text}`);
});
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);
});
node --max-old-space-size=8192 /tmp/agent/dist/app.cjs \
기대 동작: BIM comparison 이 정상 완료되어 execute promise 가 resolve 되고 revision 결과가 API 로 반환된다.
실제 동작: 약 815초 (13.6분) 시점에 old-space 가 8231MB 로 커진 상태에서 Mark-Compact GC 가 메모리를 회수하지 못하고 (mu = 0.006, mutator utilization 극도로 낮음 → GC 가 CPU 를 지배) V8 이 FatalProcessOutOfMemory 를 호출, 프로세스가 SIGABRT 로 종료된다. 부모는 close 이벤트에서 pending promise 를 reject 하고 BimRevisionService 가 revision 을 status:error 로 마감한다.
Log Evidence#
Datadog 쿼리 (본 클러스터):
service:cupixworks-any-bimrevision-agent status:error @environment:production "ChildProcessManager::setupEventHandlers"
전체 시퀀스 (2026-07-21 21:00:39 ~ 21:01:46 KST):
service:cupixworks-any-bimrevision-agent "Child process"
핵심 로그 (KST 표기, Datadog 반환 순 그대로):
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: <--- Last few GCs --->
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | 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
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | 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
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: <--- JS stacktrace --->
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: ----- Native stack trace -----
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: 1: 0xb78db3 node::OOMErrorHandler(...)
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: 3: 0xee85e7 v8::internal::V8::FatalProcessOutOfMemory(...)
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: 12: 0x10b7fb4 v8::internal::FactoryBase<v8::internal::Factory>::NewFixedArrayWithFiller(...)
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: 16: 0x12cbb55 v8::internal::ArrayConstructInitializeElements(...)
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: 17: 0x1512628 v8::internal::Runtime_NewArray(...)
2026-07-21 21:01:45 error ChildProcessManager::setupEventHandlers | Child process closed
2026-07-21 21:01:45 error ChildProcessManager::setupEventHandlers | Child process exited
2026-07-21 21:01:45 error Process killed by signal SIGABRT (code: null)
2026-07-21 21:01:45 error BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"
JS 스택 (다른 클러스터에서도 함께 관찰됨, 원인 콜사이트 확인용):
at ForgeClient.handleApiError (@cupixapps/forge-utils/server-utils/forge-client.js:90:31)
at ModelDataClient.getBuffer (@cupixapps/forge-utils/server-utils/forge-client.js:140:25)
at async ModelDataClient.getData (@cupixapps/forge-utils/server-utils/model-data.js:144:24)
at async BIMSVF2Manifest.AEC (@cupixapps/forge-utils/convert-utils/svf2/svf2-manifest.js:90:25)
at async Asset.get (@cupixapps/forge-utils/convert-utils/core-utils/block.js:18:32)
at async Object.calculateOrigin (@cupixapps/forge-agents/extractor/compare-extractor/compare-utils.js:76:36)
at async GridAlignment.computeGridCorrection (@cupixapps/forge-agents/extractor/compare-extractor/grid-alignment.js:25:27)
at async CompareExtractor.compare (@cupixapps/forge-agents/extractor/compare-extractor/compare-extractor.js:62:43)
at async extractComparison (@cupixapps/forge-agents/app.js:133:20)
Revision job 마감 로그 (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}
}
elapsed_ms=883421 는 job 시작부터 종료까지 ~14.7분을 의미하며, V8 진단 라인의 814987 ms (child process uptime 13.6분) 와 대략 일치한다. Child fork 는 job 시작 직후 이루어졌다는 근거.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | BIM comparison child process 의 V8 heap (8GB) 소진으로 인한 FATAL OOM. Forge SVF2 model / grid alignment 처리 시 대형 배열 할당. | FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory 로그, Mark-Compact 7909→7916MB 도달, Runtime_NewArray/ArrayConstructInitializeElements 스택, --max-old-space-size=8192 (docker_services/80_run-agent.sh:3), SIGABRT + BimRevisionService::REVISE-END status:error elapsed_ms=883421 |
— | Confirmed |
| H2 | Autodesk Forge API 오류(API_NOT_FOUND, Resource not found: file/urn%3Aadsk.fluent%3A...AECModelData.json) 가 근본 원인이다. |
같은 인시던트의 다른 클러스터 로그에 errorCode: 'API_NOT_FOUND', ForgeUtilsError: Resource not found 존재 |
이 클러스터의 대표 에러는 GC/OOM 라인이며, API_NOT_FOUND 는 별개 클러스터의 stderr. OOM 스택은 GC 경로이지 axios 401/404 경로가 아님. Forge API 실패는 이번 revision (25433) 이 아닌 다른 job 의 stderr 로 판단 | Rejected |
| H3 | ChildProcessManager 의 stderr listener 가 정상 GC 진단까지 error-level 로 뿌려서 클러스터가 부풀려진 것이 유일한 문제이다 (실제 OOM 은 아님). |
stderr.on('data') → logger.error 로 라벨링됨 (child-process.manager.ts:138-141) |
뒤이어 FATAL ERROR ... JavaScript heap out of memory 와 Process killed by signal SIGABRT 가 실제로 발생, REVISE-END status:error 가 남음. 로깅 잡음 문제와 별개로 실제 OOM 이 존재 |
Rejected as sole cause (부수적 문제로는 유효, 아래 fix 권고에 포함) |
| H4 | Container/pod 메모리 제한 (cgroup OOM-kill) 에 의해 죽었다. | SIGABRT + Process killed by signal 문구 |
SIGKILL 이 아니라 SIGABRT 이고, V8 이 스스로 FatalProcessOutOfMemory 호출 후 abort 함. cgroup OOM-kill 이면 SIGKILL 이 일반적이며 V8 stack trace 가 남지 않음 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 해당 revision 재처리 정책 확인 —
bim_id=17216, src_revision_id=25433, prev_revision_id=21647은 같은 조건에서 재시도해도 OOM 이 재현될 확률이 매우 높다. Retry loop 가 있다면 이 job 을 dead-letter 또는 quarantine 처리하고 clark-vdc 팀에 revision 크기/entity 수 공유. 재시도 대신 수동 조사 우선. - child process 메모리 상한을 명시적으로 지정 —
packages/base/src/manager/child-process.manager.ts:74의fork()호출에execArgv: ['--max-old-space-size=8192', ...(추가 필요 시 확장)]를 명시하여 부모 상속에 의존하지 않도록 한다. 상속은 되지만 명시 시 값 조정이 쉬워지고 실수 방지. - stderr 로그 레벨 분리 —
packages/base/src/manager/child-process.manager.ts:138-141에서 V8 GC 진단 라인 (Mark-Compact,Scavenge,<--- Last few GCs --->,[45:0x...] ... ms:) 은warn이하로 낮추고,FATAL ERROR/Error:prefix 만error로 유지. 그렇지 않으면 정상 재시도에서도 error-level 로그가 폭증해 클러스터가 과잉 생성된다 (같은 시각 25 클러스터 중 다수가 이 라벨링 때문).
단기 개선 (1주 이내)#
- BIM comparison 메모리 사용량 프로파일링 —
bim_id=17216처럼 큰 revision 을 로컬 재현하고 heap snapshot 을 뜬다. 콜스택이CompareExtractor.compare → GridAlignment.computeGridCorrection → Asset.get → ModelDataClient.getData이므로 SVF2 asset / property database 를 통째로 로드하는 지점을 확인. 스트리밍/청크 처리로 전환 가능한지 검토. - 입력 크기 상한 / pre-flight 체크 — revision 의 entity 수, model file 크기, dbId 범위를 job 진입 시 측정하여 임계 초과 시 별도 large-job 큐로 라우팅하거나 early-fail (
ErrorCode.Agent.BimRevisionExtractorTooLarge) 로 사용자에게 명확한 원인 반환. - process-level watchdog — child process 에서
process.memoryUsage()을 주기적으로 계측해 heap used 가 75%/90% 임계 시 warn/error 로그 emit. 현재는 FATAL 이 나기 전까지 조기 경보가 없어 14분 동안 조용히 heap 이 차오르다 죽는다.
장기 개선 (재발 방지)#
- BIM comparison 워크로드의 메모리 특성 재설계 — 단일 Node.js 프로세스에 8GB 를 몰아넣는 대신, model chunk 단위 병렬 처리 또는 native/rust 사이드카로 CPU-bound + memory-heavy 부분을 분리. Node.js 는 V8 heap 상한 (실무상 ~16GB) 이 hard limit 이라 스케일업만으로는 한계.
- cluster 분류 개선 — 같은 job 실패로부터 나오는 25개의 stderr 라인이 25개의 클러스터로 튀는 현상은 error-sweeper 의 fingerprint 화 로직도 함께 살펴볼 여지가 있다 (범위: 별도 이슈).
ChildProcessManager::setupEventHandlersprefix 를 fingerprint 정규화 대상에 넣는 방향 검토.
Monitoring#
- BIM revision job OOM 발생률 — job 결과 이벤트 기반. 같은 서비스에서 SIGABRT 로 종료되는 job 을 시계열로 추적.
service:cupixworks-any-bimrevision-agent @environment:production "REVISE-END" @status:error @error_message:"SIGABRT"
- child process OOM (FATAL 로그) 시계열 — 이 시그니처가 뜨면 곧바로 job 실패로 이어짐.
service:cupixworks-any-bimrevision-agent status:error "Ineffective mark-compacts near heap limit"
- 모든 agent 서비스의 child stderr error 노이즈 — stderr → error 라벨링 개선 배포 후 이 지표가 줄어야 한다 (proof-of-fix).
service:cupixworks-*-agent status:error "ChildProcessManager::setupEventHandlers | Child process stderr"
- BIM revision job elapsed 분포 — 성공/실패 elapsed_ms 를 함께 관찰. 실패 job 이 지나치게 긴 tail (>10분) 에 몰리는지 확인.
service:cupixworks-any-bimrevision-agent "REVISE-END" @environment:production
Risk Assessment#
- Risk level: medium — 단일 revision job 실패로 국한되며 서비스 전체는 계속 다른 job 을 처리한다. 다만 clark-vdc 특정 대형 BIM 프로젝트는 동일 원인으로 반복 실패할 수 있고, 25개 클러스터 스파이크로 대시보드/알림 noise 발생.
- 예상 복잡도: standard — 즉시 조치 (stderr 로깅 분리, execArgv 명시) 는 trivial. 근본 대책 (Forge 데이터 스트리밍/청크화) 은 critical 급 작업이며 별도 스프린트 필요.