ChildProcessManager::setupEventHandlers | Child process stderr: 17: 0x1512628 v8::internal::Runtime_
RCA: ChildProcessManager::setupEventHandlers | Child process stderr: 17: 0x1512628 v8::internal::Runtime_
Overview#
What Happened#
2026-07-21 21:00 KST, production cupixworks-any-bimrevision-agent (region us-west-2, tenant cupix, team clark-vdc) 에서 BIM 비교 작업을 담당하는 자식 Node 프로세스가 V8 FATAL ERROR: Ineffective mark-compacts near heap limit — JavaScript heap out of memory 로 강제 종료되었다. ChildProcessManager 가 자식의 stderr 를 한 줄씩 logger.error 로 남기면서 하나의 크래시가 25개 이상의 error 로그(=25개 클러스터) 로 폭발해 status-board 에 svc-scope incident 로 검출되었다. 이 클러스터는 크래시 스택프레임 17번째(v8::internal::Runtime_NewArray) 에 해당한다.
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:140 (stderr → logger.error) |
| workload_top_frame | v8::internal::Runtime_NewArray → ArrayConstructInitializeElements → NewFixedArrayWithFiller |
| runtime | Node.js on /usr/bin/node, child launched with inherited --max-old-space-size=8192 |
| env | production, us-west-2, tenant cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| clark-vdc (cupixworks-any-bimrevision-agent) | 25 클러스터 (동일 크래시 1회) | 해당 BIM revision 비교 job 1건 실패 — Forge 모델 비교 결과가 반환되지 않음 |
Timeline#
- 2026-07-21 20:53:10 KST —
BimRevisionService::loadBimRevisionEntityParameters시작 (matchingStrategy: treePath, threshold ratio 40%) - 2026-07-21 20:53:11 KST —
BimRevisionService::runBimCompare파라미터 로깅:query.entities.length: 154331,checkExistence.length: 57346 - 2026-07-21 20:53:11 ~ 21:00:39 KST — 약 7분 30초간 forge-agents
ForgeAgent.extract이 자식 프로세스에서 실행되며 heap 누적 (V8 uptime 816543ms ≈ 13.6분 시점에 종료) - 2026-07-21 21:00:39 KST — 연속 Mark-Compact 실패:
7909.6 → 7909.6 MB (mu=0.008),7916.1 → 7916.1 MB (mu=0.006). V8 가 OOM 판정, stderr 로 fatal error + native stack trace 방출 - 2026-07-21 21:00:39 KST —
ChildProcessManager가 자식 stderr 각 라인을logger.error로 전달 → 25개 클러스터가 100ms 이내에 생성됨. status-board2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1incident 개시 - 2026-07-21 21:01:45 KST —
Child process exited/Child process closed(codenull, signalSIGABRT류) 이벤트, incident resolved
Error Log#
ChildProcessManager::setupEventHandlers | Child process stderr: 17: 0x1512628 v8::internal::Runtime_NewArray(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]
Impact#
- Service:
cupixworks-any-bimrevision-agent - Team: clark-vdc
- 발생 횟수: 1 (본 클러스터 기준). 동일 크래시가 stderr 라인당 별도 클러스터로 파편화되어 총 25개 클러스터가 svc-scope incident 로 묶임.
- 최초 발생: 2026-07-21 21:00 KST
- 최근 발생: 2026-07-21 21:00 KST
Root Cause Summary#
cupix-tesla-bim-revision-agent 의 자식 프로세스 BimCompareProcess 가 ForgeAgent.extract 로 154,331 entities / 57,346 checkExistence 규모의 BIM 비교를 수행하던 중 V8 old-space 가 상한 (~8 GB, 부모의 --max-old-space-size=8192 를 fork 상속) 에 도달했다. Mark-Compact GC 가 연속 실패(mu=0.006, scavenge might not succeed) 하면서 V8 이 FATAL ERROR: Ineffective mark-compacts near heap limit 로 프로세스를 종료했다. 크래시 자체는 workload-driven OOM 이지만, 클러스터 폭발의 부차적 원인은 ChildProcessManager 가 자식 stderr 를 라인별로 logger.error 로 승격시키는 로깅 정책(child-process.manager.ts:138-141) 이다. Native stack trace + JS stacktrace 의 각 라인이 별개 error 로그가 되어 fingerprint 가 흩어졌고, 하나의 OOM 이 25개 클러스터로 검출되었다.
Technical Analysis#
Code Path#
- Entry point:
packages/cupix-tesla-bim-revision-agent/src/service/bim-revision-service.ts—BimRevisionService::runBimCompare(파라미터 로깅에서 확인됨, 로그: "matchingStrategy: treePath", "query.entities.length: 154331") - 자식 프로세스 매니저:
packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:53-79—BimCompareManager.execute가ChildProcessManager.execute<Result>('execute', params)로 IPC 위임 - 자식 프로세스 fork:
packages/base/src/manager/child-process.manager.ts:74-77 - 실제 workload:
packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67-81—ForgeAgent.extract(apiConfig, urn, { extractCompare: { urn: previousUrn, region, query } }) - Failure point (V8): 자식 프로세스 heap →
Runtime_NewArray할당 실패,V8::FatalProcessOutOfMemory로 abort - Failure point (로그 폭발):
packages/base/src/manager/child-process.manager.ts:138-141— stderr 라인마다 개별logger.error
fork 호출 (child heap 상한을 별도로 지정하지 않음):
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;
}
Node.js fork() 는 execArgv 미지정 시 부모의 process.execArgv 를 상속한다. 부모는 docker_services/80_run-agent.sh:3 에서 --max-old-space-size=8192 로 기동되므로 자식도 8 GB heap 을 갖는다. V8 로그(7916 / 8233 MB) 가 이 상한과 일치한다.
stderr → error 로그 승격 (클러스터 폭발의 직접 원인):
this.process.stderr?.on('data', (data: Buffer) => {
const text = data.toString();
logger.error(`ChildProcessManager::setupEventHandlers | Child process stderr: ${text}`);
});
Workload 규모 (프론트엔드/tesla 가 요청한 파라미터):
BimRevisionService::runBimCompare | params - query.entities.length: 154331, checkExistence.length: 57346, levels: [ROOF, LEVEL 2, LEVEL 1], propNames: ["Volume"], customPropNames: ["Level","Reference Level","LENGTH","Length"]
기대 동작: ForgeAgent.extract 가 8 GB heap 안에서 BIM 비교를 완료하고 Result 를 반환해야 한다.
실제 동작: entity 15만+ 규모에서 heap 사용량이 상한에 도달, Mark-Compact 가 회수에 실패(mu=0.006), V8 fatal OOM. 자식이 SIGABRT 상당으로 종료되고, ChildProcessManager.on('close') 가 Process exited prematurely 로 pending IPC 를 reject 하여 상위 BimCompareManager.execute 에서 ErrorCode.Agent.BimRevisionExtractorExecute 를 설정하고 throw 한다.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-any-bimrevision-agent status:error @environment:production "ChildProcessManager"
service:cupixworks-any-bimrevision-agent @environment:production
핵심 V8 로그 (원문):
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory<--- Last few GCs --->[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----- Native stack trace ----- 1: 0xb78db3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/usr/bin/node] 2: 0xee8300 v8::Utils::ReportOOMFailure(...) [/usr/bin/node] 3: 0xee85e7 v8::internal::V8::FatalProcessOutOfMemory(...) [/usr/bin/node] ...16: 0x12cbb55 v8::internal::ArrayConstructInitializeElements(...) [/usr/bin/node]17: 0x1512628 v8::internal::Runtime_NewArray(...) [/usr/bin/node]작업 개시 시점 파라미터 로그 (원문):
BimRevisionService::runBimCompare | params - urn: dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dGVzbGEtcHJvZHVjdGlvbi8xNzIxNl8xNzg0NjMzMjM0OTY3X0lBRDE1N19Db29yZGluYXRpb24lMjBNb2RlbF9DdXBpeC5ud2Q=, formatType: svf2, region: US, ...
previousUrn: dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dGVzbGEtcHJvZHVjdGlvbi8xNzIxNl8xNzcyODEyMzI4NTA3X0lBRDE1N19Db29yZGluYXRpb24lMjBNb2RlbF9DdXBpeC5ud2Q=, previousFormatType: svf2, previousRegion: US
Base64 URN 을 디코드하면 urn:adsk.objects:os.object:tesla-production/17216_1784633234967_IAD157_Coordination%20Model_Cupix.nwd 와 이전 revision(17216_1772812328507) — 즉 facility 17216 의 IAD157 Coordination Model. 두 revision 간 154,331 entities 비교.
Incident timeline (status-board):
2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1
started_at: 2026-07-21T12:00:39.174Z
resolved_at: 2026-07-21T12:01:45.911Z
cluster_ids: 25개 (본 클러스터 포함)
affected_services: [cupixworks-any-bimrevision-agent]
Child close/exit 이벤트 (V8 fatal 이후 66초 지연):
2026-07-21 21:01:45 KST ChildProcessManager::setupEventHandlers | Child process closed
2026-07-21 21:01:45 KST ChildProcessManager::setupEventHandlers | Child process exited
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 자식 프로세스가 V8 old-space 8 GB 상한에서 workload-driven OOM. 154,331 entities BIM 비교가 heap 을 소진하고 Mark-Compact 가 회수에 실패. | V8 로그 7916.1 → 7916.1 MB, mu=0.006, FATAL ERROR ... heap out of memory. 부모 실행 스크립트 docker_services/80_run-agent.sh:3 에서 --max-old-space-size=8192. fork() 가 execArgv 미지정으로 부모 flag 상속. 크래시 시점 스택 최상위가 Runtime_NewArray → ArrayConstructInitializeElements → NewFixedArrayWithFiller — 대형 배열 할당 지점. |
— | Confirmed |
| H2 | External dependency (Autodesk Forge) 장애. | ForgeAgent.extract 호출 도중 크래시. | 크래시 원인이 network/HTTP 가 아니라 V8 내부 OOM. status-board dep:* scope 아님 (svc:cupixworks-any-bimrevision-agent::unknown). Forge API 관련 error/timeout 로그 없음. |
Rejected |
| H3 | Native library (forge-agents C++ addon) 메모리 누수. | 크래시 스택이 V8 heap allocator. | Native stack 최상위가 v8::internal::Runtime_NewArray → JS 코드가 배열 할당 중이었음. Native memory leak 이면 malloc/free 경로에서 실패했을 것. |
Rejected |
| H4 | 25개 클러스터 = 실제로 25건의 독립 크래시. | 25개 fingerprint 가 동시에 발화. | 25개 이벤트가 모두 100 ms 안에 발생, 동일 [45:0x17bccd20] V8 isolate 태그, 하나의 native stack trace 를 라인별로 분해한 것. ChildProcessManager::setupEventHandlers 의 stderr.on('data') 가 라인마다 logger.error 를 호출(child-process.manager.ts:138-141) — 프레임당 fingerprint 가 달라짐. |
Rejected |
| H5 | --max-old-space-size 설정이 자식에 상속되지 않아 기본 heap (~1.7 GB) 에서 실패. |
소규모 heap 이면 15만 entity 처리에서 훨씬 빨리 실패했을 것. | V8 로그가 명확히 8231.3 / 8233.8 MB 로 8 GB 상한을 보여줌. Node fork() 가 부모 execArgv 를 상속하는 문서화된 기본 동작과 일치. |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- facility 17216 IAD157 Coordination Model 의 이전/현재 revision 비교 재시도 시 workload 축소 필요: 클라이언트가 동일 파라미터로 재요청하면 동일 OOM 재현. 프론트엔드/tesla 담당자와 협의하여 (a)
query.entities를 level/카테고리별 배치로 분할 요청하거나, (b)threshold.count(현재 10000) /matchingStrategy를 조정해 비교 대상 엔티티를 줄이는 우회를 검토. — 자동 code-fix 대상 아님, 조율 필요.
단기 개선 (1주 이내)#
- 자식 프로세스 stderr 로깅을 error → warn 으로 다운그레이드하고 라인 단위가 아닌 청크 단위로 집계:
packages/base/src/manager/child-process.manager.ts:138-141에서 stderr chunk 를logger.error로 라인별 방출하는 정책이 하나의 크래시를 25 fingerprint 로 파편화한다. V8 native stack 은close이벤트의code/signal이 이미 error 로 남으므로, stderr 는logger.warn+ 버퍼링(예: 최근 N줄만 crash summary 로 첨부) 이면 status-board 노이즈가 크게 줄어든다. 크래시 자체는on('close')하나로 충분히 검출된다. - BIM 비교 자식 프로세스에 명시적 heap 상한 및 OOM 재시도/스킵 정책 도입:
BimCompareManager생성자에서ChildProcessManager에execArgv: ['--max-old-space-size=<size>']를 넘기도록 API 확장을 검토하고,executecatch 블록에서closecode === null && signal 가 OOM 계열이면 별도ErrorCode.Agent.BimRevisionExtractorOOM을 부여해 재큐잉/스킵을 구분할 수 있게 한다.
장기 개선 (재발 방지)#
- BIM 비교 파이프라인의 heap 사용량 프로파일링: 15만 entity 규모가 정상 시나리오인지, revision diff 알고리즘이 O(N²) 인지 확인. 필요 시 forge-agents 에서 streaming/batched 처리로 리팩터링. entities count 를 tesla 측에서 프리체크하여 임계치 초과 시 job 을 pre-emptively split.
- status-board scope 개선:
svc:*::unknown크래시가 단일 이벤트로 부풀려지지 않도록, ChildProcessManager fingerprint 를 stack-trace 라인이 아닌 close-signal 기준으로 정규화 (수집기 레벨에서Child process stderr:prefix + numeric frame id 를 stripping 하는 정규화 룰) — error-sweeper collector 개선.
Monitoring#
- 자식 프로세스 fatal OOM 발생률 (Datadog log-based metric):
service:cupixworks-any-bimrevision-agent status:error @environment:production "JavaScript heap out of memory"
- BIM 비교 job 파라미터 규모 분포 (entities count 로 alert):
service:cupixworks-any-bimrevision-agent @environment:production "BimRevisionService::runBimCompare | params"
- Child process 비정상 종료 이벤트:
service:cupixworks-any-bimrevision-agent status:error @environment:production "Child process closed"
- Node process RSS/heap 메트릭 (Datadog agent metrics):
avg:runtime.node.mem.heap_used{service:cupixworks-any-bimrevision-agent,env:production}
avg:runtime.node.gc.pause.max{service:cupixworks-any-bimrevision-agent,env:production}
Risk Assessment#
- Risk level: medium — 프로덕션 워크로드가 이미 heap 상한에 도달했다는 신호. 유사 규모의 BIM revision 이 추가 유입되면 반복 발생 가능성이 높음. 단, 자식 프로세스 격리 덕분에 부모 agent 는 살아있고 다른 job 은 영향받지 않음 (blast radius = 해당 1건).
- 예상 복잡도: standard — stderr 로깅 다운그레이드는 trivial (
child-process.manager.ts1-2줄), 근본 완화(workload 분할, heap 명시 지정, ErrorCode 분리)는 forge-agents 및 tesla 측 조율 필요.