ES /docs

ChildProcessManager::setupEventHandlers | Child process stderr: 10: 0x10c69a6 v8::internal::Factory:

RCA: BIM revision compare child process OOM (v8 Factory::AllocateRaw)

Overview#

What Happened#

2026-07-21 21:00:39 KST, cupixworks-any-bimrevision-agent(clark-vdc, us-west-2)에서 BimCompareProcess child process 가 v8 heap OOM 으로 SIGABRT 종료했다. bim_id 17216, src_revision 25433(V5) vs prev_revision 21647(V4) 비교 작업 중 154,331 created + 57,346 deleted = 211,677 개 element 를 로드한 뒤, forge-agents 의 ForgeAgent.extract 호출이 8GB 힙 한도를 넘어서서 "Ineffective mark-compacts near heap limit — Allocation failed" 로 종료했다. 이 25-cluster 이벤트는 동일 job 하나에서 발생한 stack trace/GC 로그의 각 라인이 서로 다른 fingerprint 로 클러스터링된 결과이며, 실제 impact 는 단일 BIM revision job 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 v8::internal::Factory::AllocateRaw (child_process pid=45)
runtime Node.js on compute-base:22.04, --max-old-space-size=8192
deploy cupix-tesla-bim-revision-agent v1.10.9, @cupix/forge-agents@10.21.0, agent_version 10.98.0
env production, us-west-2, tenant cupix, team clark-vdc

Affected Teams#

Team / Domain Error Count Impact
clark-vdc (BIM revision) 1 job (bim_id 17216, revision 25433) Revision compare 실패 → bim_comparison_state=Error. si_trace_id a052780a-c69b-457d-b72d-3aa6332272a3.

Timeline#

  1. 2026-07-21 20:47:03 KSTloadAllElements begin (bim_id 17216, facilityKey 3fq7tj)
  2. 2026-07-21 20:53:08 KSTloadAllElements end (211,677 elements: created 154,331, deleted 57,346)
  3. 2026-07-21 20:53:10 KSTREVISE-BEGIN src 25433(V5) vs prev 21647(V4)
  4. 2026-07-21 20:53:11 KSTrunBimCompare params (query.entities.length 154,331, checkExistence.length 57,346) — child process 로 넘김
  5. 2026-07-21 21:00:39 KST — child process stderr flush: Last GC Mark-Compact 7916.1 → 7916.1 MB (0 회수), FATAL OOM
  6. 2026-07-21 21:01:45 KST — Child process close SIGABRT, BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)". elapsed_ms 883,421 (약 14.7 분)
  7. 2026-07-21 21:01:45 KSTREVISE-END status=error, bim_comparison_state=Error

Error Log#

Datadog Logs

php
ChildProcessManager::setupEventHandlers | Child process stderr: 10: 0x10c69a6 v8::internal::Factory::AllocateRaw(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [/usr/bin/node]

Impact#

  • Service: cupixworks-any-bimrevision-agent
  • Team: clark-vdc
  • 발생 횟수: 1 (단일 job — cluster 25건은 동일 stderr flush 의 라인별 fingerprint)
  • 최초 발생: 2026-07-21 21:00:39 KST
  • 최근 발생: 2026-07-21 21:00:39 KST

Root Cause Summary#

BimRevisionService.runBimCompare 는 bim_id 17216 의 대규모 revision(154,331 개 created element + 57,346 개 removed element)을 comparison 하기 위해 전체 element 목록을 그대로 query.entitiesquery.checkExistence 로 packing 하여 BimCompareManager.executeChildProcessManager.execute('execute', params) 로 child process 에 IPC 전달한다. child process(BimCompareProcess.execute)는 forge-agents 10.21.0 의 ForgeAgent.extract 를 호출하여 두 개 Forge SVF2 URN (V4/V5 IAD157 Coordination Model) 을 다운로드/파싱하고 tree-path 매칭을 수행한다. 이 워크로드가 child process 의 --max-old-space-size=8192 (8GB) 한도를 초과하여 mark-compact 가 회수량 0MB 로 실패하고 (current mu = 0.006) v8 이 FatalProcessOutOfMemory 로 abort 했다. 즉, 알고리즘 단일 job 의 workload 가 heap ceiling 을 초과한 케이스이며, code bug 는 아니지만 element 수가 큰 경우 (>150k) 를 다루는 방어 로직 (스트리밍/청킹/조기 exit) 이 없어서 재발 위험이 높다.

Technical Analysis#

Code Path#

  • Entry point: packages/cupix-tesla-bim-revision-agent/src/bim-revision-service.ts:150 (BimRevisionService.run)
  • Element 로딩: bim-revision-service.ts:328-350 (loadAllElements) — 211,677 개 element 를 메모리에 CPElement 객체로 보유
  • Child process invoke: bim-revision-service.ts:647 (this.bimCompareManager.execute(params))
  • Manager → IPC: packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:65 (this.childProcessManager.execute<ForgeAgent.Result>('execute', params))
  • Child fork 지점: packages/base/src/manager/child-process.manager.ts:74 (fork(actualScriptPath, { stdio: [...]})execArgv override 없음 → parent 의 --max-old-space-size=8192 를 상속)
  • 실제 OOM 발생: packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:72 (await ForgeAgent.extract(...) inside child)
  • 파열 지점: v8 native Factory::AllocateRaw — Forge model download/tree traversal 중 array/object allocation 실패

Parent 서비스가 element 전체 목록을 그대로 넘기는 부분:

packages/cupix-tesla-bim-revision-agent/src/bim-revision-service.ts:617-638typescript
const params: BimComparerParams = {
    apiConfig: {
        clientId: Environment.ADF_CLIENT_ID,
        clientSecret: Environment.ADF_CLIENT_SECRET,
    },
    urn: cpBimRevision.forgeUrn,
    formatType: cpBimRevision.forgeFormatType,
    region: cpBimRevision.forgeRegion as 'US' | 'EMEA' | undefined,
    bimOrigin: bimSetting.origin,
    previousUrn: cpPreviousBimRevision.forgeUrn,
    previousFormatType: cpPreviousBimRevision.forgeFormatType,
    previousRegion: cpPreviousBimRevision.forgeRegion as 'US' | 'EMEA',
    query: {
        entities: cpBim.getBimEntities(),        // 154,331개
        checkExistence: checkExistenceList,      // 57,346개
        levels: this.iLevels,
        bimSetting: bimSetting,
        propNames: ['Volume'],
        customPropNames: cpBim.getCustomPropNames(),
        configuration: { boxTolerance }
    }
};

Child process 는 이 params 을 그대로 forge-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
            }
        });

Fork 는 execArgv 를 상속하므로 child 도 8GB 한도 (--max-old-space-size=8192) 를 가지지만, workload 자체가 이 한도를 초과했다:

packages/base/src/manager/child-process.manager.ts:73-84typescript
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;
}

Parent 는 child 종료 시 pending IPC 를 모두 reject 하여 runBimCompare 가 throw 하고, run() 의 catch 에서 상태를 Error 로 업데이트한다:

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

기대 동작: 대용량 revision(≥150k element)도 안정적으로 comparison 완료. 실제 동작: child process 가 힙 한도에 도달하여 mark-compact 회수량 0MB → v8 FatalProcessOutOfMemory → SIGABRT.

Log Evidence#

Datadog query (재현):

text
service:cupixworks-any-bimrevision-agent status:error @environment:production "ChildProcessManager::setupEventHandlers"
text
service:cupixworks-any-bimrevision-agent (17216 OR "runBimCompare" OR "loadAllElements")

로드 규모 및 job 시작:

text
2026-07-21 20:53:08  info  BimRevisionService::loadAllElements | end - 211677 cpElements loaded - created: 154331, deleted: 57346
2026-07-21 20:53:10  info  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":"dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dGVzbGEtcHJvZHVjdGlvbi8xNzIxNl8xNzg0NjMzMjM0OTY3X0lBRDE1N19Db29yZGluYXRpb24lMjBNb2RlbF9DdXBpeC5ud2Q=","prev_forge_urn":"dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dGVzbGEtcHJvZHVjdGlvbi8xNzIxNl8xNzcyODEyMzI4NTA3X0lBRDE1N19Db29yZGluYXRpb24lMjBNb2RlbF9DdXBpeC5ud2Q="}
2026-07-21 20:53:11  info  BimRevisionService::runBimCompare | params - query.entities.length: 154331, checkExistence.length: 57346, ...

Child process OOM stderr (v8 GC 실패 증거 — 회수량 0MB, mutator utilization 0.6%):

text
2026-07-21 21:00:39  error  Child process stderr: <--- Last few GCs --->
2026-07-21 21:00:39  error  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  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  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  Child process stderr:  3: 0xee85e7 v8::internal::V8::FatalProcessOutOfMemory(...)
2026-07-21 21:00:39  error  Child process stderr: 10: 0x10c69a6 v8::internal::Factory::AllocateRaw(...)

종료 및 상태 업데이트:

text
2026-07-21 21:01:45  error  ChildProcessManager::setupEventHandlers | Child process exited  (signal=SIGABRT)
2026-07-21 21:01:45  error  ChildProcessManager::setupEventHandlers | Child process closed
2026-07-21 21:01:45  error  BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"
2026-07-21 21:01:46  info   BimRevisionService::REVISE-END | {"bim_id":17216,"src_revision_id":25433,"prev_revision_id":21647,"elapsed_ms":883421,"status":"error","error_message":"Process killed by signal SIGABRT (code: null)","counts":{"total":0,"modified":0,"removed":0,"exist":0}}

Heap ceiling 근거 (node --max-old-space-size=8192):

applications/agents/docker_services/80_run-agent.sh:3bash
node --max-old-space-size=8192 /tmp/agent/dist/app.cjs \

fork()execArgv 를 상속하므로 child 도 8GB 한도를 사용 (log 의 (8233.8) — 최대 heap 이 약 8192MB + 소량의 external — 와 일치).

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Child process (BimCompareProcess) 의 forge-agents ForgeAgent.extract 처리량이 8GB heap 한도를 초과하여 v8 OOM 발생 154,331 entities + 57,346 checkExistence 페이로드; GC 로그 7916/8192 MB 도달; Ineffective mark-compacts; Factory::AllocateRaw frame Confirmed
H2 Parent 프로세스가 IPC 로 넘긴 대형 payload 가 child 초기 힙을 팽창시켜 OOM 유발 (IPC serialization 문제) params 에 211k+ element 참조 데이터 포함 (query.entities, checkExistence); Node IPC 는 JSON serialize GC 로그 시각(814987 ms, 816543 ms)은 child 시작 후 ~13.6분에서 발생. 시작 직후가 아니라 comparison 진행 중 누적된 것 → IPC payload 자체보다 ForgeAgent.extract 내부 처리가 주 원인. Payload 는 기여했지만 root 는 아님 Rejected (contributing, not root)
H3 무한 루프 / memory leak (반복적으로 동일 element 를 alloc) Retention loop 관측되면 짧은 시간에 heap 팽창 Job 이 정상 14.7 분 동안 진행되며 runBimCompare 실행 중 점진적으로 heap 소진; leak 시그니처(짧은 시간 급증) 없음. elapsed_ms 883421 는 정상적인 대규모 compare 시간대 Rejected
H4 Forge API 응답이 손상되어 파싱 중 무한 재귀 등이 발생 Forge 오류/재시도 로그 없음; child 는 SIGABRT (OOM) 로 종료, exception 이 아님 Rejected
H5 External dependency 장애 (Forge/S3) status-board 는 svc:cupixworks-any-bimrevision-agent::unknown 스코프로 반환, dep:* 아님; 다른 서비스 동시 에러 없음 Rejected
H6 Cluster 25건 = 실제 25건의 job 실패 25개 fingerprint 가 동시에 발생 이벤트 시각이 모두 2026-07-21T12:00:39.174~196Z 범위 (22ms 내 flush) 이며, 로그 검색 결과 REVISE-END 는 1건, si_trace_id 도 하나. child stderr 의 각 stack line 이 서로 다른 fingerprint 로 clustering 됨 Rejected (실제 impact = 1 job)

Fix Recommendation#

즉시 조치 (Critical)#

  • 재시도 및 알림: 해당 bim_id 17216 V4→V5 revision 은 현재 bim_comparison_state=Error 상태이므로 clark-vdc 담당자와 사용자에게 결과를 안내하고, 필요 시 workload 를 나눠 (예: 분할 model / 부분 URN) 재시도할지 결정. 코드 변경 없이 운영 조치.
  • 런타임 heap 상향 검토: applications/agents/docker_services/80_run-agent.sh:3--max-old-space-size=8192 를 상향 (예: 12288 또는 16384) 하기 전에 반드시 컨테이너 메모리 limit(k8s pod / ECS task) 을 함께 상향해야 함 — 인프라 팀과 조율 필요. Node 프로세스만 상향하면 OOMKilled 로 대체될 뿐 근본 해결 아님.
    • 주의: 이 항목은 인프라 리소스 조율이 필요하므로 자동 code-fix 대상에서 제외 (fix-scope filtering 규칙에 따라 조율 필요 항목).

단기 개선 (1주 이내)#

  • Child process 에 명시적 execArgv 지정: packages/base/src/manager/child-process.manager.ts:74 fork() 호출에 execArgv: ['--max-old-space-size=<child_limit>'] 를 추가하여 child 힙을 parent 와 독립적으로 관리. BIM 비교처럼 무거운 워크로드는 child 를 더 크게, 그 외 agent 는 작게 유지할 수 있음.
  • 워크로드 pre-check + 조기 실패: BimRevisionService.runBimCompare (bim-revision-service.ts:603 이후) 진입 시 query.entities.length + checkExistence.length 가 임계값 (예: 150k) 을 넘으면 ErrorCode.Agent.BimRevisionExtractorInvalidResult 계열의 명시적 코드로 조기 실패시켜 SIGABRT 대신 상태 보고 가능하게 함. 이후 병렬 실행 여부(다른 agent 를 죽이지 않도록)도 함께 검토.
  • Compare 결과 파이프라인의 스트리밍 검토: BimCompareProcess.execute (bim-compare.process.ts:72) 에서 ForgeAgent.extract 가 리턴하는 대형 Result 를 파일/청크로 흘려서 parent 로 IPC 하지 않고 디스크 경유로 넘기는 방식 검토 (파일 경로만 IPC).

장기 개선 (재발 방지)#

  • Chunked/Streaming Forge comparison: forge-agents 팀과 협의하여 ForgeAgent.extract 에 chunked query API (모델별 or level 별 서브셋으로 나눠서 처리) 도입. 현재는 모든 entity 를 한 번에 넘기지만, level(3개) 또는 model 단위로 분할하면 각 파티션의 peak heap 이 낮아진다.
  • 관측성 강화: child process 에 --heap-prof / NODE_OPTIONS=--heapsnapshot-near-heap-limit=3 등 자동 heap dump 수집을 옵션으로 붙여, 재발 시 실제 retention set 파악. S3 로 dump 업로드 후 후속 분석.
  • 워크로드 캐패시티 모니터링: cp_elements_count, entities.length, elapsed_ms 를 Datadog custom metric 으로 emit 하여, 실패 임계값과 성공 분포를 대시보드로 관리.

Monitoring#

Datadog 쿼리 예시 (release dashboard 위젯용 timeseries):

text
service:cupixworks-any-bimrevision-agent status:error "heap out of memory"
text
service:cupixworks-any-bimrevision-agent status:error "Process killed by signal SIGABRT"
text
service:cupixworks-any-bimrevision-agent "ChildProcessManager::setupEventHandlers | Child process closed"
text
service:cupixworks-any-bimrevision-agent "REVISE-END" @status:error

추가 권장:

  • Alert: heap out of memory 발생 1건 이상 5분 within → clark-vdc 채널 알림.
  • Trend: cp_elements_count 분포(P95/P99) 를 log-based metric 으로 만들어 대규모 revision workload 증가 추적.

Risk Assessment#

  • Risk level: medium — 단일 job 실패로 즉각적인 서비스 전체 impact 는 없지만, BIM 규모 증가 추세에서 동일 크기(≥150k element) revision 이 다시 들어오면 재발 가능. Clark VDC 의 IAD157 model (V6+) 후속 revision 이 실패할 위험 존재.
  • 예상 복잡도: standard — pre-check 조기 실패 + child execArgv 명시화는 국소 변경. Chunked comparison 은 forge-agents 협업 필요 (critical 로 격상 가능).