ES /docs

ChildProcessManager::setupEventHandlers | Child process stderr: 16: 0x12cbb55 v8::internal::ArrayCon

RCA: BimCompareProcess child killed by V8 OOM during BIM V4→V5 comparison

Overview#

What Happened#

cupixworks-any-bimrevision-agent production instance가 bim_id 17216 (IAD157 Coordination Model, V4 → V5) revision 을 처리하던 중, BimCompareProcess child process 가 heap 약 7.9 GB 에서 V8 FATAL ERROR: Ineffective mark-compacts near heap limit 로 SIGABRT 종료되었다. 부모 ChildProcessManager 는 stderr pipe 로 흘러들어온 V8 crash native stack trace 라인 각각을 logger.error(...) 로 기록하여 25개의 개별 error cluster 로 파편화되었다. 사용자 영향은 해당 revision 작업 1건 실패 (elapsed 883 s).

Quick Facts#

Field Value
exception.class V8 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
top_frame v8::internal::ArrayConstructInitializeElementsv8::internal::Runtime_NewArray
child stderr sink applications/agents/packages/base/src/manager/child-process.manager.ts:138-141
child entry applications/agents/packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67
child heap limit --max-old-space-size=8192 (inherited from parent, docker_services/80_run-agent.sh:3)
bim_id 17216
src_revision_id / prev_revision_id 25433 (V5) / 21647 (V4)
query size entities=154331, checkExistence=57346, cpElements total=211677
env production, us-west-2, tenant=cupix, team_domain=clark-vdc

Affected Teams#

Team / Domain Error Count Impact
clark-vdc (BIM revision, bim_id 17216) 1 job failed (25개 error 로그 클러스터로 파편화) V4→V5 revision 결과 미생성; 사용자는 재시도 필요

Timeline#

  1. 2026-07-21 20:47:02 KSTBaseService::runByMessage 시작, si_trace_id: a052780a-c69b-457d-b72d-3aa6332272a3
  2. 2026-07-21 20:52:10 KSTBimRevisionService::loadRemovedElements | begin - bimId: 17216
  3. 2026-07-21 20:53:08 KSTloadAllElements | end - 211677 cpElements loaded - created: 154331, deleted: 57346
  4. 2026-07-21 20:53:10 KSTREVISE-BEGIN (V4→V5), runBimCompare | begin
  5. 2026-07-21 20:53:11 KST — child forked, forge-agents extract 호출 시작 (query.entities=154331)
  6. 2026-07-21 21:00:39 KST — child V8 Mark-Compact 두 차례 실패 (7909.6 MB → 7916.1 MB, heap cap 8231 MB), FATAL ERROR: Ineffective mark-compacts near heap limit, native stack dump (frames 1..18) 를 stderr 로 flush
  7. 2026-07-21 21:01:45 KST — parent 가 child close/exit 수신, Process killed by signal SIGABRT, BimRevisionService::run | error
  8. 2026-07-21 21:01:46 KSTREVISE-END status=error, elapsed_ms=883421, /tmp/workspace/25433 정리, SQS 메시지 삭제

Error Log#

Datadog Logs

text
ChildProcessManager::setupEventHandlers | Child process stderr: 16: 0x12cbb55 v8::internal::ArrayConstructInitializeElements(v8::internal::Handle<v8::internal::JSArray>, v8::internal::Arguments<(v8::internal::ArgumentsType)1>*) [/usr/bin/node]

Impact#

  • Service: cupixworks-any-bimrevision-agent
  • Team: clark-vdc
  • 발생 횟수: 1 (본 클러스터). 동일 crash 로부터 파생된 관련 클러스터 24개가 status-board 인시던트 2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1 로 묶여 있음
  • 최초 발생: 2026-07-21 21:00:39 KST
  • 최근 발생: 2026-07-21 21:00:39 KST

Root Cause Summary#

BIM V4→V5 비교 작업이 query.entities.length: 154331, checkExistence.length: 57346, 총 cpElements: 211677 규모로 forge-agents ForgeAgent.extract 에 전달되어 child process 에서 실행되었다. child 는 부모의 --max-old-space-size=8192 를 상속받아 old-space 상한 ~8 GB 로 동작했지만, forge-agents CompareExtractor.compare / GridAlignment.computeGridCorrection 경로에서 대용량 요소 집합을 처리하는 도중 heap 이 상한에 도달했고 V8 Mark-Compact 가 연달아 회수에 실패(Ineffective mark-compacts near heap limit)하여 Runtime_NewArray 시점에 FATAL OOM 으로 abort 되었다. Node 는 SIGABRT 로 종료되었고, 부모 ChildProcessManager::setupEventHandlers 가 stderr pipe 를 logger.error 로 소비하는 구조 때문에 V8 crash dump 의 각 라인이 개별 error 로그가 되어 fingerprint 25개로 파편화되었다.

Technical Analysis#

Code Path#

  • Entry point: applications/agents/packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:65 — parent invokes child via IPC
  • Child entry: applications/agents/packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67ForgeAgent.extract 호출
  • Failure point: child V8 heap OOM inside forge-agents (CompareExtractor.compareGridAlignment.computeGridCorrection; 최근 stderr 에서도 동일 stack 관찰됨)
  • Log fan-out point: applications/agents/packages/base/src/manager/child-process.manager.ts:138-141 — stderr 데이터 라인 단위로 logger.error 호출

부모는 fork()execArgv 를 override 하지 않아 child 는 부모의 --max-old-space-size=8192 를 그대로 상속받는다. 실제 로그의 heap upper bound 8231.3 MB 가 이를 확인해 준다.

applications/agents/packages/base/src/manager/child-process.manager.ts:74-77typescript
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}`);

child stderr 는 pipe 로 소비되며 각 chunk 가 별도 error 로그가 된다:

applications/agents/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}`);
});

fork 로 spawn 되는 child 가 처리하는 payload 크기:

applications/agents/packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67-82typescript
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
            }
        });

기대 동작 vs 실제 동작: BIM 요소 수(200K+ elements, 150K query entities)가 heap 8 GB 안에서 처리될 것으로 기대되었으나, forge-agents 의 배열 확장 시점에 heap 이 소진되어 V8 이 abort 하였다. 또한 stderr sink 는 crash 시 V8 native stack dump 를 다수의 개별 error 로그로 변환하여 관측 노이즈를 증폭시킨다.

Log Evidence#

Datadog query used:

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

시간 범위 2026-07-21T11:55:00Z ~ 12:05:00Z 결과 26건. 순서대로 핵심 라인:

text
2026-07-21T12:00:39.174Z  Child process stderr: <--- Last few GCs --->
2026-07-21T12:00:39.175Z  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-21T12:00:39.180Z  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-21T12:00:39.181Z  Child process stderr: <--- JS stacktrace --->
2026-07-21T12:00:39.181Z  Child process stderr: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
2026-07-21T12:00:39.181Z  Child process stderr:  1: 0xb78db3 node::OOMErrorHandler
2026-07-21T12:00:39.196Z  Child process stderr: 16: 0x12cbb55 v8::internal::ArrayConstructInitializeElements
2026-07-21T12:00:39.196Z  Child process stderr: 17: 0x1512628 v8::internal::Runtime_NewArray
2026-07-21T12:01:45.908Z  Child process exited
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)"

Job 규모 확인:

text
2026-07-21T11:53:08.911Z  BimRevisionService::loadAllElements | end - 211677 cpElements loaded - created: 154331, deleted: 57346
2026-07-21T11:53:11.068Z  runBimCompare | params - query.entities.length: 154331, checkExistence.length: 57346, levels: [ROOF, LEVEL 2, LEVEL 1]

최종 결과 (Datadog):

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}
}

Status board 는 동일 crash 를 svc-scope 인시던트로 인식하고 25 개 클러스터를 묶어 2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1 로 이미 resolved 처리하였다 (25개 클러스터 모두 2026-07-21T12:00:39.174Z~12:01:45.911Z 범위, 동일 si_trace_id).

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 forge-agents BIM comparison child process 가 heap 8 GB 상한에 부딪혀 V8 OOM 으로 abort. 대량 elements(154K entities, 57K checkExistence) 처리 중 배열 할당 실패. Datadog stderr: FATAL ERROR: Ineffective mark-compacts near heap limit, Mark-Compact 두 차례 7909.6 (8231.3) MB → 7916.1 (8233.8) MB 실패, native stack top frame ArrayConstructInitializeElements / Runtime_NewArray; runBimCompare params 로그의 규모(entities=154331, checkExistence=57346, cpElements=211677); 부모 --max-old-space-size=8192 (docker_services/80_run-agent.sh:3) 이 child 로 상속 (fork 시 execArgv override 없음, child-process.manager.ts:74-77). 최종 exit signal SIGABRT (Node OOM abort 시 발생). Confirmed
H2 Autodesk Forge API_NOT_FOUND (ForgeUtilsError: Resource not found ...AECModelData.json) 이 root cause. 동일 서비스의 다른 시간대(21:42 KST) 로그에서 API_NOT_FOUND 관찰. 인시던트 시간(20:53–21:00 KST) 창의 로그에는 API_NOT_FOUND 없음. Failure 유형은 FATAL ERROR: heap out of memory (V8 abort) 이며 Forge HTTP 오류가 아님. si_trace_id: a052780a-c69b-457d-b72d-3aa6332272a3 기반 로그 흐름은 정상 fork → 7.5분 실행 후 OOM. Rejected
H3 부모 ChildProcessManager 가 정상 완료된 child 를 잘못 종료(예: waitForReady timeout, SIGKILL). 부모에 SIGKILL 을 유발할 timeout 은 execute 옵션 미설정 시 없음(child-process.manager.ts:39-47). stop() 은 정상 종료 경로만 SIGTERM/SIGKILL. 로그에 부모 발신 kill 흔적 없음. exit signal 이 SIGABRT 로, 이는 V8 이 자체 abort 시 출력. Mark-Compact 실패 native stack 이 child stderr 에 남음 — 외부 kill 이 아닌 self-abort. Rejected
H4 25 개의 개별 fingerprint 는 25 건의 실제 에러이다. occurrence_count 및 클러스터 수. 25 클러스터가 모두 2026-07-21T12:00:39.174Z~12:01:45.911Z 창, 동일 si_trace_id, 동일 V8 crash stack. 원인은 부모의 stderr line-level logger.error (child-process.manager.ts:138-141) 로 V8 crash dump 각 라인이 별개 fingerprint 로 분리된 관측 아티팩트. Rejected (observability artifact, 실제 사건 1건)

Fix Recommendation#

즉시 조치 (Critical)#

  • 재실행 & 관찰: bim_id 17216 (V4→V5) revision 재시도. 동일 payload 재실행 시 재현 여부 확인.
  • 별도 코드 변경 없이 즉시 대응 가능한 항목은 재시도뿐. 실 조치는 아래 단기 개선에서 진행.

단기 개선 (1주 이내)#

  • child 힙 상한 상향applications/agents/packages/base/src/manager/child-process.manager.ts:74fork() 호출에 execArgv: ['--max-old-space-size=<N>'] 를 명시적으로 지정하고, BIM revision agent 는 컨테이너 메모리 여유 범위 안에서 12–16 GB 로 상향 검토. 부모(8 GB)와 child(8 GB)가 상한을 공유하는 구조는 큰 forge 모델을 감당하기 어렵다. 근거: heap cap 8231 MB 로그.
  • 입력 크기 방어applications/agents/packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:53-79 진입 시 params.query.entities.length 또는 checkExistence.length 가 임계치를 넘으면 조기 실패(사용자에게 명확한 에러 코드 반환)하거나 chunk 분할 실행. 154K entities 는 이번 실패 규모의 참고선.
  • stderr 로깅 소음 축소child-process.manager.ts:138-141 stderr 파이프를 라인 단위 error 로그로 fan-out 하지 말고, V8 fatal 패턴(FATAL ERROR, <--- JS stacktrace --->, native stack 프레임)은 하나의 error 로그(문맥 포함)로 집약해 방출. observability 시스템(fingerprinter)이 25 개 개별 클러스터로 파편화되지 않도록 함.
  • 명시적 OOM 사후 진단 로그 — child close/error 핸들러(child-process.manager.ts:107-136)에서 signal === 'SIGABRT' 이거나 stderr 에 FATAL ERROR .* heap out of memory 가 관측된 경우, revision 컨텍스트(bim_id, revision id, entities count, checkExistence count, si_trace_id)를 포함한 단일 error 이벤트를 방출.

장기 개선 (재발 방지)#

  • compare 파이프라인 스트리밍/청크화 — forge-agents ForgeAgent.extractextractCompare.query.entities 를 batch 로 분할 처리하고 결과를 병합. 규모가 큰 BIM(수십만 elements)에서는 all-in-memory 방식이 지속 가능하지 않다.
  • heap dump on OOM — child fork 시 --heapsnapshot-near-heap-limit=1 이나 --heap-prof 를 opt-in 옵션으로 두어 OOM 재발 시 프로파일 확보.
  • 에이전트 인스턴스 사이징 정책 — BIM revision 에이전트에 한해 큰 인스턴스 유형 사용 또는 revision size 기반 라우팅 도입.

Monitoring#

writing-datadog-monitoring-queries 가이드를 따라 timeseries widget 에 그대로 embed 가능한 count 쿼리 형태로 작성.

  • BIM revision agent 의 child OOM abort 발생 카운트:
text
count:cupixworks_any_bimrevision_agent.child_process.oom{env:production}.as_count()

(위 커스텀 메트릭이 없다면 로그 기반):

text
logs("service:cupixworks-any-bimrevision-agent status:error \"FATAL ERROR: Ineffective mark-compacts near heap limit\"").index("*").rollup("count").by("environment")
  • SIGABRT 로 종료된 child process:
text
logs("service:cupixworks-any-bimrevision-agent status:error \"Process killed by signal SIGABRT\"").index("*").rollup("count").by("environment")
  • BIM revision job 결과별 카운트 (REVISE-END):
text
logs("service:cupixworks-any-bimrevision-agent \"REVISE-END\"").index("*").rollup("count").by("@status")
  • Query 규모(entities.length) 추이 (facet 화 필요):
text
logs("service:cupixworks-any-bimrevision-agent \"runBimCompare | params\"").index("*").rollup("count").by("environment")

권장 알람: FATAL ERROR: Ineffective mark-compacts near heap limit OR Process killed by signal SIGABRT 가 1시간 창에서 2회 이상이면 즉시 알림.

Risk Assessment#

  • Risk level: medium — 단발 job 실패이나, BIM 규모 성장 추세에서 동일 원인 재발 가능성 상존. 관측성 파편화로 인해 인시던트 감지·분류가 왜곡될 수 있음.
  • 예상 복잡도: standard — child heap 상향과 stderr 로깅 집약은 소규모 변경. compare 파이프라인 청크화는 별도 프로젝트 규모.