ChildProcessManager::setupEventHandlers | Child process stderr: 2: 0xee8300 v8::Utils::ReportOOMFai
RCA: ChildProcessManager Child process OOM (V8 heap exhaustion)
Overview#
What Happened#
2026-07-21 21:00 KST, cupixworks-any-bimrevision-agent 서비스에서 BIM 리비전 비교 작업(bim_id=17216, V4 → V5)을 수행하던 child process가 V8 heap 한도(~8 GB) 를 초과하여 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory 로 abort 되었다. 21:01 KST 에 부모 프로세스가 SIGABRT 로 child 종료를 감지하고 BimRevisionService::run 이 예외를 전파했다. 동일 태스크에서 25개 cluster 로 fingerprint 가 분리되었지만 실제 사용자 영향은 리비전 한 건 실패이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | V8 FATAL ERROR (OOM) |
| exception.message | Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory |
| top_frame | packages/base/src/manager/child-process.manager.ts:138-141 (stderr pipe) |
| runtime | Node.js (child process forked via child_process.fork), --max-old-space-size=8192 상속 |
| env | production, us-west-2 |
| tenant | cupix, team_domain: clark-vdc |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| clark-vdc (tenant: cupix) | 25 로그 (한 태스크, bim_id=17216, si_trace_id=a052780a-c69b-457d-b72d-3aa6332272a3) |
BIM revision V5 비교 1건 실패 — Forge compare 결과가 저장되지 않아 리비전 diff 미완성 |
Timeline#
- 2026-07-21 20:47 KST —
BimRevisionService::run시작 (si_trace_id: a052780a-c69b-457d-b72d-3aa6332272a3,bim_id=17216) - 2026-07-21 20:47 KST —
loadAllElements시작 (facilityKey: 3fq7tj) - 2026-07-21 20:53 KST — 211,677 cpElements 로드 완료 (created: 154,331, deleted: 57,346)
- 2026-07-21 20:53 KST —
runBimCompare | begin;query.entities.length: 154331, checkExistence.length: 57346 - 2026-07-21 21:00:39 KST — Child process V8 heap 반복 Mark-Compact (7909.6 → 7916.1 MB / 한도 ~8231 MB),
allocation failure; scavenge might not succeed - 2026-07-21 21:00:39 KST — V8
FATAL ERROR: Ineffective mark-compacts near heap limit, OOM native stack trace 출력 - 2026-07-21 21:01:45 KST —
Child process exited/Child process closed/Process killed by signal SIGABRT (code: null) - 2026-07-21 21:01:45 KST —
BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"
Error Log#
ChildProcessManager::setupEventHandlers | Child process stderr: 2: 0xee8300 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/usr/bin/node]
Impact#
- Service:
cupixworks-any-bimrevision-agent - Team: clark-vdc
- 발생 횟수: 1 (fingerprint 기준; 실제 stderr 로그는 25건, 모두 단일 태스크 실패에서 파생)
- 최초 발생: 2026-07-21 21:00 KST
- 최근 발생: 2026-07-21 21:00 KST
- 비즈니스 영향: BIM revision 비교 1건 실패. 리비전 V5 결과가 저장되지 않아 사용자가 revision diff 를 볼 수 없다.
Root Cause Summary#
BimCompareProcess 는 @cupixapps/forge-agents 의 ForgeAgent.extract 를 호출하여 Autodesk Forge 모델 두 개(V4, V5)를 비교한다. 이번 태스크는 query.entities.length: 154331, checkExistence.length: 57346, 총 cp_elements_count: 211677 규모로 매우 크다. Fork 로 생성된 child process 는 parent 의 execArgv 를 상속받아 --max-old-space-size=8192 (약 8 GB) heap 한도를 사용하지만, forge-agents 가 SVF2 매니페스트/AEC ModelData/grid alignment/compare 결과 자료구조를 로드/생성하는 과정에서 heap 사용량이 7,909 MB → 7,916 MB 로 증가한 뒤 GC 가 회수하지 못해 V8 이 FATAL ERROR: Ineffective mark-compacts near heap limit 로 abort 했다. 즉, 입력 데이터 규모가 현재 heap 한도(8 GB) 를 초과했고, child-process.manager.ts 는 crash 를 감지하지만 memory guard/스트리밍 처리가 없어 그대로 실패한다.
Technical Analysis#
Code Path#
- Entry point (parent):
packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:65—childProcessManager.execute<ForgeAgent.Result>('execute', params) - Fork 지점:
packages/base/src/manager/child-process.manager.ts:74-77—fork(actualScriptPath, { stdio: ['inherit', stdOut, stdErr, 'ipc'] }).execArgv를 미지정하므로 parent 의--max-old-space-size=8192를 상속한다. - Child entry:
packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67-81—ForgeAgent.extract(params.apiConfig, params.urn, { ..., extractCompare: { urn, region, query } }) - Failure point: V8 heap in child (
FATAL ERROR: Ineffective mark-compacts near heap limit), stderr 를 parent 가stderr.on('data')로 수신하여logger.error로 방출.ChildProcessManager::setupEventHandlers의 close 핸들러가SIGABRT신호로 pending message 를 reject.
Parent 가 child 를 fork 하는 부분:
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'],
});
logger.debug(`ChildProcessManager::start | Child process forked with PID: ${this.process.pid}, stdio: ${this.options.stdioMode}`);
} catch (error: any) {
logger.error(`ChildProcessManager::start | Fork failed: ${error.message}`, {
actualScriptPath,
error: error.stack
});
throw error;
}
Child 의 stderr 를 부모 로거로 재출력하는 handler (에러 메시지가 여기서 방출됨):
this.process.stderr?.on('data', (data: Buffer) => {
const text = data.toString();
logger.error(`ChildProcessManager::setupEventHandlers | Child process stderr: ${text}`);
});
Child crash 로 인한 pending message reject 경로:
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);
});
Child 에서 실제 비교를 수행하는 지점 (heap 를 소비하는 곳):
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
}
});
Parent heap 한도는 8 GB 로 고정되어 있음:
node --max-old-space-size=8192 /tmp/agent/dist/app.cjs \
기대 동작: 입력 규모와 무관하게 comparison 완료 또는 재시도 가능한 에러 반환.
실제 동작: 입력 규모가 heap 한도를 넘겨 V8 abort → 상위 서비스에서 SIGABRT 로 실패.
Log Evidence#
Datadog 검색 쿼리 (재현 가능):
service:cupixworks-any-bimrevision-agent status:error
시간 범위 2026-07-21T11:59:00Z ~ 2026-07-21T12:02:00Z.
작업 입력 규모 (사고 발생 7분 전 로그):
2026-07-21T11:53:10.688Z 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":"...","prev_forge_urn":"..."}
2026-07-21T11:53:11.068Z BimRevisionService::runBimCompare | params - query.entities.length: 154331, checkExistence.length: 57346, levels: [{"id":72846,"name":"ROOF","elevation":19.1262},{"id":72845,"name":"LEVEL 2","elevation":9.2837},{"id":72827,"name":"LEVEL 1","elevation":0}], ...
V8 GC 압박 및 abort (핵심 stderr 스트림):
2026-07-21T12:00:39.174Z <--- Last few GCs --->
2026-07-21T12:00:39.175Z [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-21T12:00:39.180Z [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-21T12:00:39.181Z <--- JS stacktrace --->
2026-07-21T12:00:39.181Z FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
2026-07-21T12:00:39.181Z 1: 0xb78db3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/usr/bin/node]
2026-07-21T12:00:39.182Z 2: 0xee8300 v8::Utils::ReportOOMFailure(...)
2026-07-21T12:00:39.196Z 17: 0x1512628 v8::internal::Runtime_NewArray(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]
Abort 후 signal 및 서비스 예외 전파:
2026-07-21T12:01:45.908Z ChildProcessManager::setupEventHandlers | Child process exited
2026-07-21T12:01:45.909Z ChildProcessManager::setupEventHandlers | Child process closed
2026-07-21T12:01:45.910Z Process killed by signal SIGABRT (code: null)
2026-07-21T12:01:45.911Z BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"
V8 top-of-stack frame (Runtime_NewArray + NewFixedArrayWithFiller) 은 큰 array/객체 할당 시점에 OOM 이 났음을 시사한다. 이는 forge-agents 가 154,331 entity 규모의 compare 결과 array 를 만드는 과정과 부합한다.
관련 (동일 태스크 이후, 21:42 KST 재시도로 추정) 로그에서는 ForgeUtilsError: Resource not found / errorCode: 'API_NOT_FOUND' (Autodesk 360 translation storage AEC ModelData) 도 관측되나, 이 클러스터의 fingerprint 원인은 OOM 이며 API_NOT_FOUND 는 별개의 이후 이벤트이다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Child process 가 heap 8 GB 한도 안에서 대규모 BIM compare (154K entities) 를 처리하다가 OOM 으로 abort | V8 GC 로그 7916.1 (8233.8) MB, allocation failure; scavenge might not succeed; FATAL ERROR: Ineffective mark-compacts near heap limit; Runtime_NewArray top-of-stack; 사고 직전 query.entities.length: 154331, checkExistence.length: 57346; parent --max-old-space-size=8192 (80_run-agent.sh:3) 을 fork 가 상속. |
— | Confirmed |
| H2 | Autodesk Forge API 실패 (API_NOT_FOUND) 로 인한 실패 |
동일 시간대 stderr 에 ForgeUtilsError: Resource not found ... AECModelData.json / errorCode: 'API_NOT_FOUND' 관측 |
이 이벤트들은 12:00 UTC 가 아니라 12:42 UTC (21:42 KST) 이후 관찰됨. 클러스터 대표 로그 2026-07-21T12:00:39.182Z 는 OOM native stack (ReportOOMFailure) 로, API_NOT_FOUND 는 이후 재시도 경로. Fingerprint 불일치. |
Rejected |
| H3 | Child process 메모리 누수 (heap 이 시간 경과에 따라 서서히 증가) | GC 로그 두 줄만 노출되어 이전 heap 추이 불명 | 해당 프로세스는 태스크 시작(11:47) 부터 crash(12:00) 까지 약 13분 실행, 그 동안 loadAllElements 로 211K row 를 로드하고 runBimCompare 로 대용량 diff 를 계산 — 누수가 아니라 단일 작업의 절대 사용량으로 설명 가능. 누수를 확정할 debug/heapsnapshot 증거 없음. |
Inconclusive |
| H4 | Docker 컨테이너 cgroup 메모리 한도가 heap 보다 낮아 강제 OOM-kill | Signal 은 SIGABRT (V8 self-abort) 이지 SIGKILL 이 아니다. cgroup OOM 은 통상 SIGKILL 을 낸다. GC 로그에서 V8 스스로 fatal 을 낸 시퀀스가 확인됨. |
동일 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:26-27—ChildProcessManager생성 시execArgv를 override 해서 child 의 heap 한도를 명시적으로 부여하도록base의ChildProcessOptions를 확장하고, BIM revision agent 는 12–16 GB 처럼 여유분을 준다. 컨테이너 메모리 예산과 합의 필요 (인프라 팀 확인). 접근 방식:ChildProcessManager에nodeOptions?: string[]옵션 추가 →fork(scriptPath, { execArgv: [...], stdio: [...] })로 명시.packages/base/src/manager/child-process.manager.ts:74-77— 현재execArgv를 지정하지 않아 parent 의 flag 를 상속하지만, 이 값은 서비스별로 다르게 튜닝 가능해야 한다. Fork 옵션에 명시적execArgv를 노출하는 방향으로 수정. 구현 코드는 후속 PR 에서.- Ops 대응: 실패한 revision
bim_id=17216, src_revision_id=25433을 사후 재처리 큐로 넣고 사용자에게 상태 노출 (별도 트랙 — 팀 협의 필요).
단기 개선 (1주 이내)#
BimCompareProcess::execute(bim-compare.process.ts:67-116) 에서params.query.entities.length가 임계치 (예: 80,000) 를 초과할 때 chunked 처리 또는 warning telemetry 를 추가한다. Forge 의extractCompare를 여러 subset 으로 나눠 호출하고 결과를 병합하는 방향.- OOM signal (
SIGABRT, exit code null 또는FATAL ERRORstderr) 을 명시적으로 감지하는 로직을child-process.manager.ts:107-120의 close handler 에 추가하여, 상위 서비스가 error code 를ErrorCode.Agent.OutOfMemory로 분류 (bim-compare.manager.ts:71근처) — 재시도 정책과 알림을 OOM 전용으로 분기하기 위함. - BIM revision 규모(
cp_elements_count) 를 Datadog custom metric 으로 방출 (BimRevisionService::REVISE-BEGIN시점) — 규모별 실패율 분석 근거.
장기 개선 (재발 방지)#
- forge-agents 의 compare 파이프라인을 스트리밍 또는 out-of-core (디스크 기반) 로 리팩터. 154K 엔티티를 in-memory array 로 유지하는 현재 구조는 heap 을 선형적으로 소비한다.
- BIM revision agent 인스턴스에 heap dump on OOM (
--heapsnapshot-near-heap-limit=3) 을 옵션으로 활성화, 실패 시 S3 로 업로드 — 다음 발생 시 즉시 원인 파악 가능. - Autoscaling: 대규모 BIM (
cp_elements_count > 100k) 태스크는 memory-optimized 인스턴스 큐로 라우팅.
Monitoring#
Datadog release dashboard timeseries widget 에 그대로 넣을 쿼리:
- BIM revision agent OOM 발생 건수 (분당):
sum:datadog.trace_agent.trace_writer.spans_dropped{service:cupixworks-any-bimrevision-agent}.rollup(count, 60)
대체 쿼리 (로그 기반, log metric 미리 생성 필요):
logs("service:cupixworks-any-bimrevision-agent \"ReportOOMFailure\"").index("*").rollup("count").by("environment")
- Child process 이상 종료 (SIGABRT / SIGKILL) 추적:
logs("service:cupixworks-any-bimrevision-agent \"Process killed by signal\"").index("*").rollup("count").by("environment")
- BIM revision 입력 규모 (custom metric 신규 계측 후):
avg:cupixworks.bim_revision.cp_elements_count{service:cupixworks-any-bimrevision-agent} by {team_domain}
- 알림 임계치 권장: OOM 로그가 5분 동안 1건 이상이면 clark-vdc / VDC 채널로 알림.
Risk Assessment#
- Risk level: medium — 실제 사용자 impact 는 revision 1건 실패 (25 로그 fingerprint 는 단일 태스크). 다만 대규모 프로젝트 (예: 15만 엔티티 이상) 는 반복 실패 가능성이 있다.
- 예상 복잡도: standard —
ChildProcessManager에execArgv옵션 추가는 소규모 리팩터. 그러나 heap 한도 상향은 컨테이너/EC2 인스턴스 memory 여유와 합의가 필요하므로 인프라 팀과 협의(별도 트랙).