ES /docs

BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"

RCA: BimRevisionService::run — Process killed by signal SIGABRT

Overview#

What Happened#

2026-07-21 21:01:45 KST에 cupixworks-any-bimrevision-agent (production, us-west-2, tenant cupix)에서 BIM revision 25433 (bim_id 17216, V4→V5 비교)의 comparison 처리 중 BimCompareProcess child process가 V8 heap OOM(FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory)으로 SIGABRT를 받아 종료했다. 같은 revision job에서 25개 클러스터가 12:00:39–12:01:45Z 사이에 동시 방출되어 status-board 인시던트 2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1로 묶였다.

Quick Facts#

Field Value
exception.class Error (Node.js)
exception.message Process killed by signal SIGABRT (code: null)
root cause JavaScript heap out of memory in forked child process
top_frame packages/base/src/manager/child-process.manager.ts:107 (close event)
runtime Node.js (/usr/bin/node), --max-old-space-size=8192 (parent)
agent_version 10.98.0
env production / us-west-2
tenant cupix

Affected Teams#

Team / Domain Error Count Impact
clark-vdc / cupixworks-any-bimrevision-agent 25 (같은 job) BIM revision 25433 (bim_id 17216) 비교 실패 → bim_comparison_state = Error 로 업데이트, 사용자가 V4→V5 diff 를 확인할 수 없음

Timeline#

  1. 2026-07-21 20:53:10 KSTBimRevisionService::REVISE-BEGIN (bim_id 17216, src_revision 25433, cp_elements_count 211,677, created 154,331, deleted 57,346, agent_version 10.98.0)
  2. 2026-07-21 21:00:39 KST — Child process stderr에 V8 FATAL ERROR: Ineffective mark-compacts near heap limit ... JavaScript heap out of memory. 직전 Mark-Compact GC 로그: 7916.1 (8233.8) -> 7916.1 (8233.8) MB, 1547.82 ms
  3. 2026-07-21 21:01:45 KST — Parent ChildProcessManagerexit / close 이벤트 수신, Process killed by signal SIGABRT (code: null) Error로 pending IPC reject, BimRevisionService::run catch에서 error log 방출 (cluster event)
  4. 2026-07-21 21:01:46 KSTBimRevisionService::REVISE-END (status: error, elapsed_ms 883,421), workspace /tmp/workspace/25433 정리, SQS 메시지 삭제

Error Log#

Datadog Logs

text
BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"

Impact#

  • Service: cupixworks-any-bimrevision-agent
  • Team: clark-vdc
  • 발생 횟수: 1 (동일 job 안에서 25개 관련 클러스터 동시 발생)
  • 최초 발생: 2026-07-21 21:01:45 KST
  • 최근 발생: 2026-07-21 21:01:45 KST

Root Cause Summary#

BimCompareManagerChildProcessManager.fork()로 띄운 BimCompareProcess child process가 ForgeAgent.extract 실행 중 V8 old-generation heap 한계(--max-old-space-size=8192, 8 GiB)에 도달했다. Datadog stderr에 남은 native stack trace의 마지막 프레임이 v8::internal::Runtime_NewArrayArrayConstructInitializeElementsFactoryBase::NewFixedArrayWithFiller 이므로, forge-agents가 비교 대상 요소 배열(cp_elements_count=211,677, created 154,331, deleted 57,346의 매우 큰 Navisworks coordination model IAD157_Coordination Model_Cupix.nwd)을 처리하다 대형 JS array 할당에 실패했다. V8가 OOMErrorHandler를 호출해 abort()(SIGABRT)한 결과, 부모 ChildProcessManagerclose 이벤트가 Process killed by signal SIGABRT (code: null) Error 를 만들어 pending IPC 를 reject 했고, BimRevisionService::run의 catch 블록이 이를 로그하고 bim_comparison_state를 Error 로 마킹했다. 즉, application-level bug 가 아니라 입력 모델 규모가 forge-agents / V8 8 GiB 한계를 초과한 리소스 exhaustion이다.

Technical Analysis#

Code Path#

  • Entry point: packages/cupix-tesla-bim-revision-agent/src/bim-revision-service.ts:150 (run)
  • Fork trigger: BimRevisionService.runBimCompareBimCompareManager.executeChildProcessManager.execute('execute', params)
  • Child script: packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67 (execute) → ForgeAgent.extract(...)
  • Failure point: V8 heap allocation inside ForgeAgent.extract (child process), reported by parent at packages/base/src/manager/child-process.manager.ts:107-120

Child process가 죽자 부모의 close 핸들러가 pending Promise를 SIGABRT Error 로 reject 한다:

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

fork()는 별도 execArgv를 지정하지 않아 child 도 부모의 --max-old-space-size=8192 를 상속한다 (Node.js fork() 기본 동작). 즉 child 도 8 GiB heap 이 상한이었다:

packages/base/src/manager/child-process.manager.ts:73-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 process는 ForgeAgent.extract 를 호출해 URN 두 개(V4/V5)에 대해 extractCompare 를 수행한다:

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,
                previousRegion: params.previousRegion,
                query: params.query
            }
        });

부모 서비스는 catch에서 error를 로그하고 상태만 Error로 갱신한다 — 재시도 로직이나 heap OOM 감지 후 fallback 은 없다:

packages/cupix-tesla-bim-revision-agent/src/bim-revision-service.ts:188-192typescript
    } catch (ec: any) {
        logger.error('BimRevisionService::run | error: %s', JSON.stringify(ec.message));
        await this.updateBimRevisionBimComparisonState(targetId, TESLA.UpdateBimRevisionRequest.BimComparisonStateEnum.Error, this.errorCode);
    }
};

기대 동작: ForgeAgent.extract 결과를 받아 updateAllElements / updateBim / uploadDataFiles 를 이어서 실행. 실제 동작: extract 도중 child heap OOM → SIGABRT → parent catch → status=Error, 883초의 CPU/네트워크 작업이 버려짐.

Log Evidence#

Datadog 쿼리 (cluster URL로 재현 가능):

text
service:cupixworks-any-bimrevision-agent status:error @environment:production "BimRevisionService::run"

보조 쿼리 (SIGABRT 발생 시각 주변 전체 로그):

text
service:cupixworks-any-bimrevision-agent @environment:production
# time range: 2026-07-21T11:55:00Z ~ 2026-07-21T12:05:00Z

REVISE-BEGIN — 입력 규모 (원문):

text
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":"...IAD157_Coordination Model_Cupix.nwd","prev_forge_urn":"..."}

V8 FATAL — child process stderr (20:00:39 KST, 원문):

text
Child process stderr: <--- Last few GCs --->
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
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
Child process stderr: <--- JS stacktrace --->
Child process stderr: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Child process stderr: ----- Native stack trace -----
Child process stderr:  1: 0xb78db3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/usr/bin/node]
Child process stderr:  2: 0xee8300 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/usr/bin/node]
Child process stderr:  3: 0xee85e7 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/usr/bin/node]
Child process stderr: 12: 0x10b7fb4 v8::internal::FactoryBase<v8::internal::Factory>::NewFixedArrayWithFiller(...)
Child process stderr: 16: 0x12cbb55 v8::internal::ArrayConstructInitializeElements(...)
Child process stderr: 17: 0x1512628 v8::internal::Runtime_NewArray(...)

Parent ChildProcessManager — SIGABRT 관측 (21:01:45 KST):

text
ChildProcessManager::setupEventHandlers | Child process exited
ChildProcessManager::setupEventHandlers | Child process closed
Process killed by signal SIGABRT (code: null)
BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"

REVISE-END — 최종 상태 (21:01:46 KST):

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

7일 이내 동일 서비스 OOM 재발생 여부 (retention 14일):

text
service:cupixworks-any-bimrevision-agent status:error @environment:production "heap out of memory"
# time range: now-7d ~ now  →  결과 1건 (이번 사건만)

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Child process V8 heap OOM (large BIM model 처리 중 array allocation 실패) stderr FATAL ERROR: ... heap out of memory, native stack Runtime_NewArrayNewFixedArrayWithFiller, GC 로그 7916.1 (8233.8) MB (8 GiB 한계 근접), REVISE-BEGIN에 cp_elements_count=211677 / created=154331 Confirmed
H2 External SIGABRT (OOM killer / cgroup memory limit, k8s eviction) Signal이 SIGABRT OOM killer는 통상 SIGKILL, cgroup은 SIGKILL. Child stderr에 V8 self-abort 로그가 남아 있어 프로세스 내부에서 abort() 호출된 것이 명확. Rejected
H3 Forge API 응답 오류 / 네트워크 timeout 883초의 긴 실행 시간 Datadog에 Forge error 로그 없음, runBimCompare catch 는 Forge error를 명시적으로 다른 메시지로 던짐(Forge error: ...) — 실제 메시지는 순수 SIGABRT Rejected
H4 Application-level bug (예: infinite loop, unbounded buffer) 회귀 하나의 revision에서만 발생, 재발 없음 로그는 정상적으로 initialize → execute → GC 단계 진행. 최근 7일 동일 OOM 없고 24 개의 다른 클러스터도 모두 같은 SIGABRT job (동일 revision 25433)에서 발생 → 특정 입력에 대한 resource exhaustion 패턴 Rejected
H5 --max-old-space-size 가 child에 상속되지 않음 → child heap 이 기본값(약 4 GiB)에서 죽음 fork() 호출부에 execArgv 미지정 GC 로그가 8 GiB 근처에서 실패 (7916/8233 MB). Node.js fork()는 기본적으로 부모 execArgv 상속. 상속이 문제였다면 heap 4 GiB 근처에서 죽었어야 함 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 입력 규모 확인 및 사용자 회신: bim_id 17216 / src_revision 25433 (V5) / prev 21647 (V4), 파일 IAD157_Coordination Model_Cupix.nwd에 대해 clark-vdc 팀에 (a) 모델 요소 수(211k) 가 예상 범위인지, (b) 재실행 대신 모델 분할이 가능한지 문의. 즉시 재시도해도 동일 결과가 예상됨.
  • 일시적 완화: bim-revision-agent 컨테이너의 메모리 및 --max-old-space-size 를 상향해 재실행 여부 확인. applications/agents/docker_services/80_run-agent.sh:3에서 --max-old-space-size=8192 를 12288~16384 로 조정하고 (컨테이너 memory limit 도 함께 확장) 이 특정 revision 을 재큐잉. 코드 변경 아님.

단기 개선 (1주 이내)#

  • Child process 전용 heap 상향: ChildProcessManagerfork() 시 옵션(예: execArgv: ['--max-old-space-size=<N>']) 을 명시적으로 받도록 확장하고, BimCompareManager 에서 BIM 비교 workload 크기에 맞춰 값을 지정. 상속 동작에 의존하지 말고 명시 (packages/base/src/manager/child-process.manager.ts:73).
  • 입력 크기 기반 pre-flight guard: BimRevisionService.run 진입 시 cp_elements_count (또는 forge model element count) 를 확인해 임계치 (예: 200k) 초과 시 bim_comparison_state=Error + 명시적 에러 코드 (예: MODEL_TOO_LARGE) 로 조기 종료. 883초 낭비 후 SIGABRT 대신 즉시 실패시켜 사용자에게 명확한 원인 전달.
  • 관측성 강화: ChildProcessManagerclose 이벤트에서 signal 이 SIGABRT/SIGKILL 이면 별도 error_code=CHILD_OOM_LIKELY 를 부착해 알림/집계 분리 (packages/base/src/manager/child-process.manager.ts:107).

장기 개선 (재발 방지)#

  • Forge extract 스트리밍/청크화: forge-agents extract 결과(modified/removed/exist 배열) 를 in-memory 로 다 쥐고 있는 구조라면, 요소를 스트림/페이지 단위로 소비하도록 리팩터. 지금은 요소 20만+ 규모에서 단일 배열 할당 (Runtime_NewArray) 이 heap 을 소진.
  • Compute 격리: BIM revision 처리를 SQS worker 단위가 아니라, 모델 규모에 따라 라우팅 가능한 큐/워크로드 (예: large-model 전용 fleet with 32 GiB) 로 분리. 개별 컨테이너 heap 상향은 다른 소규모 job 의 안정성/비용에 부담.
  • 자동 재시도 정책: SIGABRT/OOM 확률이 높은 경우 즉시 재큐잉하지 않고, ops 검토 필요 상태 (Error(REQUIRES_INVESTIGATION)) 로 두어 무한 재시도 방지.

Monitoring#

  • BIM revision agent 의 child process OOM/SIGABRT 발생 빈도
text
sum:logs.hits{service:cupixworks-any-bimrevision-agent,env:production,status:error} by {kube_cluster_name}.as_count()
  • SIGABRT 문자열 매칭 (알림용 candidate — dashboard 에도 그대로 사용 가능)
text
sum:logs.hits{service:cupixworks-any-bimrevision-agent,env:production,@message:"Process killed by signal SIGABRT"}.as_count()
  • Heap OOM stderr 마커 발생 추이
text
sum:logs.hits{service:cupixworks-any-bimrevision-agent,env:production,@message:"JavaScript heap out of memory"}.as_count()
  • Revision 처리 실패율 (REVISE-END status:error 비율)
text
sum:logs.hits{service:cupixworks-any-bimrevision-agent,env:production,@message:"REVISE-END",@status:error}.as_count()

권장: 첫 두 쿼리에 대해 5분 창 임계 1 초과 시 clark-vdc 채널로 알림. 대시보드에는 위 4개 timeseries 를 배치해 재발 시 즉시 인지.

Risk Assessment#

  • Risk level: medium — 개별 사용자가 특정 BIM revision 을 비교할 수 없어 워크플로우가 막힘. 서비스 전체 장애는 아니고 SQS 메시지도 정상 삭제되어 백프레셔는 없음. 다만 재시도해도 동일 결과가 나올 것이므로 사용자 응답이 필요.
  • 예상 복잡도: standard — 단기 개선(execArgv 명시, pre-flight guard, error 분류)은 기존 클래스에 국한된 변경. 장기 개선(forge-agents 스트리밍, fleet 분리)은 별도 프로젝트 규모.