ES /docs

ChildProcessManager::setupEventHandlers | Child process stderr: 9: 0x10ea385 v8::internal::HeapAllo

RCA: BIM Revision Child Process V8 Heap OOM

Overview#

What Happened#

2026-07-21 21:00:39 KST, cupixworks-any-bimrevision-agent 서비스에서 BIM 비교(bim-compare) 자식 프로세스가 V8 힙 한계(약 8 GB)에 도달하여 FATAL ERROR: Ineffective mark-compacts near heap limit 로 SIGABRT 종료되었다. 부모 BimRevisionService::run 이 이를 감지해 revision 작업(bim_id=17216, revision V5 vs V4)을 실패 처리했다. 오류는 단발성이지만 동시 시간대의 25개 클러스터를 포괄하는 서비스 저하 인시던트(2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1)의 일부로 감지되었다.

Quick Facts#

Field Value
exception.class FatalProcessOutOfMemory (V8 native)
exception.message FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
top_frame child-process.manager.ts:140 (stderr 로깅 지점)
root_frame v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPathRuntime_NewArray
runtime Node.js /usr/bin/node, --max-old-space-size=8192
env production, us-west-2, tenant cupix, team clark-vdc
agent_version 10.98.0 (@cupix/forge-agents)

Affected Teams#

Team / Domain Error Count Impact
clark-vdc / cupixworks-any-bimrevision-agent 1 (이 클러스터) / 25 (전체 인시던트) BIM revision 실패 1건 (bim_id 17216, revision V5) — 재실행 필요. 사용자 단에서는 revision 상태가 error 로 남음.

Timeline#

  1. 2026-07-21 20:47:02 KSTBimRevisionService::run 시작 (si_trace_id a052780a-c69b-457d-b72d-3aa6332272a3, bim_id 17216, facilityKey 3fq7tj).
  2. 2026-07-21 20:53:08 KSTloadAllElements 완료: 211,677 cpElements 로드 (created 154,331 + deleted 57,346).
  3. 2026-07-21 20:53:10 KSTrunBimCompare 시작. query.entities.length=154331, checkExistence.length=57346 로 자식 프로세스 execute 호출.
  4. 2026-07-21 21:00:39 KST — 자식 프로세스가 V8 heap ~7916 MB / 8233 MB 에서 Ineffective mark-compacts 로 OOM 크래시. mu = 0.006 (0.6% mutator time) 로 GC 데드락 직전 상태.
  5. 2026-07-21 21:01:45 KST — 자식 프로세스 close/exit 이벤트. BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)" 로 부모 서비스가 실패 확정.
  6. 2026-07-21 21:01:46 KSTBimRevisionService::REVISE-END 로 elapsed_ms=883421 (14.7 분) 기록 후 revision 실패 응답.
  7. 2026-07-21 21:01:45 KST — 인시던트 자동 resolved (status-board 2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1).

Error Log#

Datadog Logs

text
ChildProcessManager::setupEventHandlers | Child process stderr:  9: 0x10ea385 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]

Impact#

  • Service: cupixworks-any-bimrevision-agent
  • Team: clark-vdc
  • 발생 횟수: 1 (이 클러스터). 같은 크래시 이벤트가 만든 stderr 스택 프레임 클러스터 25개가 동일 시각 인시던트로 묶임.
  • 최초 발생: 2026-07-21 21:00:39 KST
  • 최근 발생: 2026-07-21 21:00:39 KST

Root Cause Summary#

BIM revision 자식 프로세스가 154,331 개 entity + 57,346 개 removed entity(총 211,677 cpElements) 로 이루어진 대형 IAD157 Coordination Model 을 비교하다가 V8 old-space 한계인 약 8 GB(--max-old-space-size=8192)에 도달하여 OOM 으로 종료되었다. 크래시 직전 Mark-Compact 7916.1 -> 7916.1 MB 로 회수되지 않는 상태가 반복되고 current mu = 0.006 로 GC가 실질적으로 진전을 못 냈다. ChildProcessManager 는 stderr 를 그대로 로거로 흘리므로 V8 크래시 스택 프레임 각각이 별도의 error 로그로 남아 여러 fingerprint 클러스터로 나뉘어 감지된 것이 서비스 저하 인시던트로 묶인 이유이다.

Technical Analysis#

Code Path#

  • Entry point: applications/agents/docker_services/80_run-agent.sh:3 — 부모 Node 프로세스 시작 (heap 8 GB)
  • Parent orchestration: applications/agents/packages/cupix-tesla-bim-revision-agent/src/service/bim-revision-service.tsrunBimCompare
  • Child fork: applications/agents/packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:27,65
  • Child spawn: applications/agents/packages/base/src/manager/child-process.manager.ts:74
  • Child execution: applications/agents/packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:72ForgeAgent.extract(...)
  • Failure point: 자식 프로세스 V8 heap OOM 도중. stderr 로깅은 child-process.manager.ts:140

부모가 --max-old-space-size=8192 로 실행되고 fork()execArgv 을 override 하지 않으므로 자식도 같은 8 GB 한계를 상속한다:

applications/agents/docker_services/80_run-agent.sh:3bash
node --max-old-space-size=8192 /tmp/agent/dist/app.cjs \
applications/agents/packages/base/src/manager/child-process.manager.ts:73-77typescript
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}`);

자식 프로세스의 stderr 는 pipe 될 때 아래에서 로그로 전달된다. V8 OOM 크래시 시 fatal handler 가 stderr 로 <--- Last few GCs --->, native stack, FATAL ERROR: ... 를 각 라인 단위로 출력하는데 그것들이 각각 error 로그로 흘러가면서 다수의 fingerprint 클러스터를 생성한다:

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

부모는 close/exit 이벤트로 실패를 감지해 pending IPC 를 reject 한다:

applications/agents/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
        // ...
    this.process = undefined;
    this.emit('close', code, signal);
});

자식이 실행하는 작업은 forge-agents의 compare-extractor:

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

기대 동작: ForgeAgent.extract 가 query.entities(154K)를 스트리밍 또는 청크 단위로 처리해 힙 사용량이 상한선 아래에 유지된다. 실제 동작: 전체 entity/model-data 를 메모리에 적재하다가 8 GB 한계를 넘어 GC 가 회수 실패 → OOM.

Log Evidence#

Datadog query (BimRevisionService trace):

text
service:cupixworks-any-bimrevision-agent "BimRevisionService"

작업 시작과 규모:

text
2026-07-21 20:47:02 KST | BimRevisionService::run | si_trace_id: a052780a-c69b-457d-b72d-3aa6332272a3
2026-07-21 20:47:03 KST | BimRevisionService::loadAllElements | begin - bimId: 17216, facilityKey: 3fq7tj
2026-07-21 20:53:08 KST | BimRevisionService::loadAllElements | end - 211677 cpElements loaded - created: 154331, deleted: 57346

Compare 시작 파라미터:

json
{
  "bim_id": 17216,
  "src_revision_id": 25433,
  "src_revision_name": "V5",
  "prev_revision_id": 21647,
  "prev_revision_name": "V4",
  "agent_version": "10.98.0",
  "cp_elements_count": 211677,
  "cp_elements_created_count": 154331,
  "cp_elements_deleted_count": 57346
}

Datadog query (V8 크래시):

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

크래시 직전 GC 상태 (핵심 증거):

text
[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
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

Native stack 상단 (V8 OOM handler):

text
 1: 0xb78db3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/usr/bin/node]
 2: 0xee8300 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/usr/bin/node]
 3: 0xee85e7 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/usr/bin/node]
...
16: 0x12cbb55 v8::internal::ArrayConstructInitializeElements(v8::internal::Handle<v8::internal::JSArray>, v8::internal::Arguments<...)1>*) [/usr/bin/node]
17: 0x1512628 v8::internal::Runtime_NewArray(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]

부모 서비스 최종 결과:

json
{
  "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}
}

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 자식 프로세스가 대규모 BIM 비교(154K entities)를 처리하다가 V8 8 GB 힙 한계에 도달하여 OOM Datadog stderr 로그의 Mark-Compact 7916.1 (8233.8) -> 7916.1 (8233.8) MB, FATAL ERROR: Ineffective mark-compacts, Runtime_NewArray 프레임. runBimCompare 파라미터 query.entities.length: 154331. 80_run-agent.sh:3--max-old-space-size=8192 Confirmed
H2 외부 의존성(Autodesk Forge API) 장애 같은 시간대에 ForgeUtilsError: Resource not found (API_NOT_FOUND) 로그 다수 존재 (21:42:22, 21:42:24 KST 등) 해당 로그는 인시던트 발생 42분 뒤 별개 revision 의 것 (revision URN w3cEko2vSNuSmggy9xLMHQ — bim_id 17216 의 IAD157_Coordination URN 과 다름). OOM 크래시 시각 21:00:39 KST 에는 Forge 4xx/5xx 로그 없음. status-board 는 dep:* 스코프가 아닌 svc:* 스코프로 분류 Rejected
H3 ChildProcessManager IPC timeout 또는 disconnect 문제 close/exit 이벤트가 발화됨 실제 종료 원인은 자식 자체의 SIGABRT (V8 OOM abort). child-process.manager.ts 에는 timeout 로직이 파라미터로만 있으며 (executeTimeoutMs) 이번 호출은 timeout 설정 없이 elapsed 883 초 후 자식 크래시로 종료 Rejected
H4 컨테이너 메모리 한계(Linux OOM killer)에 의한 종료 signal SIGABRT 로 종료됨 OOM killer 는 SIGKILL(9) 을 보내지 SIGABRT(6) 을 보내지 않는다. stderr 에 V8 자체가 발행한 FATAL ERROR: Ineffective mark-compacts near heap limit 문구가 있으므로 프로세스 스스로 abort 한 것 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 재실행 및 사용자 안내: bim_id=17216 (revision V5 vs V4) revision 을 재시도. 규모가 동일한 이상 이대로 재시도해도 실패 가능성이 높으므로 아래 단기 개선 후 재실행할 것. 접근 방식: clark-vdc 팀에 IAD157 Coordination Model 규모(211K cpElements)와 실패 원인을 공유하고, 해당 revision 재시도 정책 결정 (수동 재시도 vs 자동 재시도 skip).
  • 로그 상 원인이 명확하고(단발성) 파일 코드 변경이 없어도 즉시 부가 조치는 없음.

단기 개선 (1주 이내)#

  • 자식 프로세스 힙 상향: bim-compare 자식 프로세스는 대형 BIM 을 위해 격리 설계된 것 (bim-compare.process.ts 상단 주석: "isolation prevents memory-intensive Forge operations from affecting the main service"). ChildProcessManager.start() (applications/agents/packages/base/src/manager/child-process.manager.ts:73-76) 의 fork() 호출에 execArgv: ['--max-old-space-size=<N>'] 옵션을 추가할 수 있게 하고, BimCompareManager 생성자(bim-compare.manager.ts:27)에서 예를 들어 12–16 GB 를 지정. 컨테이너 메모리 여유 및 EC2 인스턴스 사이즈 검증 후 결정.
  • 엔티티 스트리밍/청크 처리: forge-agents extract 가 154K entities 를 in-memory 로 처리하는 것이 근본 원인. @cupix/forge-agents@10.98.0CompareExtractor.compare (compare-extractor.js:62) 와 GridAlignment.computeGridCorrection (grid-alignment.js:25) 경로에서 entity 배열을 배치 처리하도록 라이브러리 개선 검토.
  • 대형 revision 사전 감지 및 게이팅: BimRevisionService::runBimCompare 진입 시 query.entities.length 가 임계치(예: 100K) 를 초과하면 warn 로그 + 별도 큐/워커 라우팅 또는 사용자 안내. 코드 위치: bim-revision-service.tsrunBimCompare 함수 시작부 (라인 번호는 워크트리에서 확인 필요).
  • stderr 로그 정리: V8 크래시가 20+ 개의 개별 error 로그를 생성해 클러스터를 인위적으로 부풀린다. child-process.manager.ts:140 에서 stderr 라인을 버퍼링해 하나의 로그로 묶거나, "FATAL ERROR" 감지 시 요약 로그 1회만 emit 하는 방식을 고려.

장기 개선 (재발 방지)#

  • BIM 크기 기반 리소스 프로파일링: 파일 크기 · entity 수 · level 개수로 예상 메모리 사용량을 추정하는 회귀 모델 또는 룰 테이블을 만들고, revision 큐가 적절한 인스턴스/컨테이너로 라우팅되도록 함.
  • 점진적 diff 알고리즘: 전체 154K entities 를 한 번에 로드/비교하는 대신 level(층)/model 단위로 chunked diff 를 수행하고 결과를 스트리밍 병합.
  • heap snapshot on OOM: 재발 시 원인 진단을 위해 --heapsnapshot-near-heap-limit=1 node 옵션을 자식에 추가. S3 등에 자동 업로드 파이프라인 필요.

Monitoring#

  • OOM 크래시 발생률:
text
service:cupixworks-any-bimrevision-agent status:error "FATAL ERROR: Ineffective mark-compacts near heap limit"
  • SIGABRT 로 인한 revision 실패:
text
service:cupixworks-any-bimrevision-agent "Process killed by signal SIGABRT"
  • BIM revision 대형 파라미터 조기 감지:
text
service:cupixworks-any-bimrevision-agent "runBimCompare | params" "query.entities.length"
  • BIM revision 실패 결과:
text
service:cupixworks-any-bimrevision-agent "BimRevisionService::REVISE-END" "\"status\":\"error\""

Risk Assessment#

  • Risk level: medium — 개별 revision 실패는 사용자 데이터 손실 없음(재시도 가능)이나, 대형 BIM 을 다루는 clark-vdc 등 특정 고객에서 반복될 가능성이 있음. 자동 재시도가 걸려 있으면 동일 크래시가 되풀이되어 시간·CPU 낭비 및 Datadog 로그 인플레이션 유발.
  • 예상 복잡도: standard — 힙 상향(fork execArgv 추가)은 trivial 이나 근본 개선(스트리밍/청크 diff)은 forge-agents 라이브러리 변경으로 critical 급 작업. 단계적 접근 권장.