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#
- 2026-07-21 20:53:10 KST —
BimRevisionService::REVISE-BEGIN(bim_id 17216, src_revision 25433, cp_elements_count 211,677, created 154,331, deleted 57,346, agent_version 10.98.0) - 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 - 2026-07-21 21:01:45 KST — Parent
ChildProcessManager가exit/close이벤트 수신,Process killed by signal SIGABRT (code: null)Error로 pending IPC reject,BimRevisionService::runcatch에서 error log 방출 (cluster event) - 2026-07-21 21:01:46 KST —
BimRevisionService::REVISE-END(status: error, elapsed_ms 883,421), workspace/tmp/workspace/25433정리, SQS 메시지 삭제
Error Log#
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#
BimCompareManager가 ChildProcessManager.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_NewArray → ArrayConstructInitializeElements → FactoryBase::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)한 결과, 부모 ChildProcessManager의 close 이벤트가 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.runBimCompare→BimCompareManager.execute→ChildProcessManager.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 atpackages/base/src/manager/child-process.manager.ts:107-120
Child process가 죽자 부모의 close 핸들러가 pending Promise를 SIGABRT Error 로 reject 한다:
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 이 상한이었다:
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 를 수행한다:
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 은 없다:
} 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로 재현 가능):
service:cupixworks-any-bimrevision-agent status:error @environment:production "BimRevisionService::run"
보조 쿼리 (SIGABRT 발생 시각 주변 전체 로그):
service:cupixworks-any-bimrevision-agent @environment:production
# time range: 2026-07-21T11:55:00Z ~ 2026-07-21T12:05:00Z
REVISE-BEGIN — 입력 규모 (원문):
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, 원문):
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):
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):
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일):
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_NewArray → NewFixedArrayWithFiller, 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 상향:
ChildProcessManager가fork()시 옵션(예: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 대신 즉시 실패시켜 사용자에게 명확한 원인 전달. - 관측성 강화:
ChildProcessManager가close이벤트에서 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 발생 빈도
sum:logs.hits{service:cupixworks-any-bimrevision-agent,env:production,status:error} by {kube_cluster_name}.as_count()
- SIGABRT 문자열 매칭 (알림용 candidate — dashboard 에도 그대로 사용 가능)
sum:logs.hits{service:cupixworks-any-bimrevision-agent,env:production,@message:"Process killed by signal SIGABRT"}.as_count()
- Heap OOM stderr 마커 발생 추이
sum:logs.hits{service:cupixworks-any-bimrevision-agent,env:production,@message:"JavaScript heap out of memory"}.as_count()
- Revision 처리 실패율 (REVISE-END status:error 비율)
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 분리)은 별도 프로젝트 규모.