ES /docs

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-ENDstatus: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#

  1. 2026-07-21 20:47:02 KST (약) — BIM revision job 시작 (elapsed_ms=883421 역산; BimRevisionService::REVISE-END 로그의 elapsed 값 기준).
  2. 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 표시.
  3. 2026-07-21 21:00:39 KSTChildProcessManager::setupEventHandlers | Child process stderr 로그 다수 emit (본 클러스터의 first_seen/last_seen).
  4. 2026-07-21 21:01:45 KST — child process close/exit, Process killed by signal SIGABRT (code: null). BimRevisionService::run error, REVISE-END status:error 기록.
  5. 2026-07-21 21:01:46 KSTBaseService::cleanUpAnythingRelatedModel path:/tmp/workspace/25433, SQS 메시지 삭제 (dd2cac85-c941-428d-8fbb-bb97168d9bfe).

Error Log#

Datadog Logs

text
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.tsfork() 로 실행) 가 실제 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-agentsCompareExtractor.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:27new ChildProcessManager(__dirname, 'process/bim-compare.process')
  • Actual fork: packages/base/src/manager/child-process.manager.ts:74fork(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 소진.
packages/base/src/manager/child-process.manager.ts:70-77typescript
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'],
  });
packages/base/src/manager/child-process.manager.ts:138-141typescript
this.process.stderr?.on('data', (data: Buffer) => {
  const text = data.toString();
  logger.error(`ChildProcessManager::setupEventHandlers | Child process stderr: ${text}`);
});
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);
});
docker_services/80_run-agent.sh:3bash
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 쿼리 (본 클러스터):

text
service:cupixworks-any-bimrevision-agent status:error @environment:production "ChildProcessManager::setupEventHandlers"

전체 시퀀스 (2026-07-21 21:00:39 ~ 21:01:46 KST):

text
service:cupixworks-any-bimrevision-agent "Child process"

핵심 로그 (KST 표기, Datadog 반환 순 그대로):

text
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 스택 (다른 클러스터에서도 함께 관찰됨, 원인 콜사이트 확인용):

text
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):

json
{
  "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 memoryProcess 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:74fork() 호출에 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::setupEventHandlers prefix 를 fingerprint 정규화 대상에 넣는 방향 검토.

Monitoring#

  • BIM revision job OOM 발생률 — job 결과 이벤트 기반. 같은 서비스에서 SIGABRT 로 종료되는 job 을 시계열로 추적.
text
service:cupixworks-any-bimrevision-agent @environment:production "REVISE-END" @status:error @error_message:"SIGABRT"
  • child process OOM (FATAL 로그) 시계열 — 이 시그니처가 뜨면 곧바로 job 실패로 이어짐.
text
service:cupixworks-any-bimrevision-agent status:error "Ineffective mark-compacts near heap limit"
  • 모든 agent 서비스의 child stderr error 노이즈 — stderr → error 라벨링 개선 배포 후 이 지표가 줄어야 한다 (proof-of-fix).
text
service:cupixworks-*-agent status:error "ChildProcessManager::setupEventHandlers | Child process stderr"
  • BIM revision job elapsed 분포 — 성공/실패 elapsed_ms 를 함께 관찰. 실패 job 이 지나치게 긴 tail (>10분) 에 몰리는지 확인.
text
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 급 작업이며 별도 스프린트 필요.