ChildProcessManager::setupEventHandlers | Child process stderr: 18: 0x7f9597eda476
RCA: ChildProcessManager::setupEventHandlers | Child process stderr: 18: 0x7f9597eda476
Overview#
What Happened#
cupixworks-any-bimrevision-agent 서비스에서 bim_id=17216, src_revision_id=25433, prev_revision_id=21647 조합에 대한 BIM revision 비교 작업이 실행 중 자식 프로세스가 FATAL ERROR: Ineffective mark-compacts near heap limit — Allocation failed - JavaScript heap out of memory 로 SIGABRT crash 를 냈다. 로그로 관측된 대표 메시지는 V8 native stack frame 의 한 줄 (18: 0x7f9597eda476) 로, 자식 프로세스가 8 GB heap 상한(--max-old-space-size=8192)에 도달하면서 mark-compact GC 가 반복 실패한 결과다. 이 클러스터는 같은 사건에서 발생한 25개의 관련 클러스터 중 하나이며 (status board incident 2026-07-21-svc-cupixworks-any-bimrevision-agent--unknown-1), 서비스 인시던트는 이후 자동으로 resolved 상태로 종료되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | V8 OOM (native, non-JS) |
| exception.message | FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory |
| top_frame | packages/base/src/manager/child-process.manager.ts:138-141 (stderr passthrough) |
| runtime | Node.js on compute-base:22.04, --max-old-space-size=8192 |
| env | production, us-west-2 |
| tenant | cupix |
| team | clark-vdc |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
clark-vdc / BIM revision (bim_id=17216) |
1 revision job (25 log 클러스터) | 해당 BIM revision (src_revision_id=25433) 이 Compared 로 전이하지 못하고 Error 상태로 기록됨. 사용자에게는 BIM 비교 결과가 표시되지 않음. |
Timeline#
- 2026-07-21 20:53:10 KST —
BimRevisionService::runBimCompare | begin— 비교 시작,matchingStrategy: treePath - 2026-07-21 20:53:11 KST —
runBimCompare | params로그.query.entities.length: 154331,checkExistence.length: 57346, levels 3개 - 2026-07-21 21:00:39 KST — 자식 프로세스에서 V8 OOM 발생.
Mark-Compact 7916.1 (8233.8) -> 7916.1 (8233.8) MB(heap 상한 근접),FATAL ERROR ... JavaScript heap out of memory— native stack trace 및 V8 stack trace 전량이ChildProcessManager::setupEventHandlers | Child process stderr: ...로 병렬 로깅됨 (이 클러스터의 대표 메시지18: 0x7f9597eda476포함) - 2026-07-21 21:01:45 KST —
Child process exited/Child process closed—Process killed by signal SIGABRT (code: null) - 2026-07-21 21:01:45 KST —
BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"— revision 상태Error로 업데이트 - 2026-07-21 21:01:46 KST —
REVISE-END—elapsed_ms: 883421,status: error, workspace/tmp/workspace/25433cleanup, SQS 메시지 삭제
Error Log#
ChildProcessManager::setupEventHandlers | Child process stderr: 18: 0x7f9597eda476
Impact#
- Service:
cupixworks-any-bimrevision-agent - Team: clark-vdc
- 발생 횟수: 1 (같은 사건에서 파생된 25개 클러스터 중 이 fingerprint 는 1건)
- 최초 발생: 2026-07-21 21:00:39 KST
- 최근 발생: 2026-07-21 21:00:39 KST
Root Cause Summary#
BIM revision 비교의 실제 연산을 담당하는 자식 프로세스 (bim-compare.process.ts) 가 154,331 개 entity, 57,346 개 checkExistence 항목을 대상으로 ForgeAgent.extract 를 수행하던 중 Node.js V8 old-space heap 상한 (--max-old-space-size=8192, 8 GB) 에 도달했다. GC 로그는 Mark-Compact 7916.1 (8233.8) -> 7916.1 (8233.8) MB, average mu = 0.050, current mu = 0.006 로 GC 가 사실상 아무 메모리도 회수하지 못하는 상태에서 재시도 되었음을 보여준다. 결과적으로 V8 이 FatalProcessOutOfMemory 를 호출해 SIGABRT 로 프로세스가 종료되었고, 부모 ChildProcessManager 는 자식의 stderr 를 라인 단위로 그대로 logger.error 에 흘려보내 native stack trace 의 각 라인이 개별 로그 항목이 되었다 — 그 중 한 라인 (18: 0x7f9597eda476) 이 이 클러스터의 대표 fingerprint 로 잡힌 것이다. 즉, 표면적 fingerprint 는 V8 stack frame 이지만 원인은 OOM 이고, 트리거는 대형 BIM (150K+ entity) 비교 작업의 메모리 사용량이 heap 상한을 초과한 것이다.
Technical Analysis#
Code Path#
- Entry point:
applications/agents/packages/cupix-tesla-bim-revision-agent/src/bim-revision-service.ts:150(run) - 대형 비교 실행:
applications/agents/packages/cupix-tesla-bim-revision-agent/src/bim-revision-service.ts:603(runBimCompare) →applications/agents/packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:53(BimCompareManager.execute) →applications/agents/packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67(child processexecute→ForgeAgent.extract) - Child fork:
applications/agents/packages/base/src/manager/child-process.manager.ts:74—fork(actualScriptPath, { stdio: [...] }).execArgv가 지정되지 않아 자식은 부모의execArgv(즉--max-old-space-size=8192) 를 상속받는다. - Failure point (child): V8 OOM in
ForgeAgent.extract(BIM compare 라이브러리). 부모 프로세스 관점의 관측 지점은 stderr passthrough —packages/base/src/manager/child-process.manager.ts:138-141.
this.process.stderr?.on('data', (data: Buffer) => {
const text = data.toString();
logger.error(`ChildProcessManager::setupEventHandlers | Child process stderr: ${text}`);
});
부모는 자식 stderr 를 라인 단위로 그대로 error 레벨에 로깅한다. 자식이 crash 하면서 뿜은 V8 stack (FATAL ERROR ..., <--- Last few GCs --->, <--- JS stacktrace --->, ----- Native stack trace -----, 이어지는 20여 줄의 N: 0x... frame) 이 모두 개별 error 로그가 되어 fingerprint 별 클러스터가 다수 생성된다. 이 클러스터가 잡은 라인은 V8 native stack 의 top frame (18: 0x7f9597eda476) 이다.
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));
// ...
});
자식 종료 시 부모는 Process killed by signal SIGABRT (code: null) 을 Error 로 만들어 pendingMessages 를 모두 reject 한다. 이 rejection 은 BimCompareManager.execute 에서 catch 되어 BimRevisionExtractorExecute error code 로 상위에 전달된다 (bim-compare.manager.ts:64-78).
기대 동작 vs 실제 동작:
- 기대: 150K entity 규모의 BIM 도 비교 완료 후
Compared상태 전이. - 실제: 자식 heap 이 mark-compact 로도 회수되지 않을 만큼 라이브 데이터로 가득 차 V8 이 프로세스를 강제 종료. revision 은
Error로 종료.
Log Evidence#
Datadog 쿼리 (재현용):
service:cupixworks-any-bimrevision-agent status:error @environment:production "ChildProcessManager::setupEventHandlers"
service:cupixworks-any-bimrevision-agent "FATAL ERROR" OR "JavaScript heap out of memory"
핵심 로그 항목 (원문):
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: <--- Last few GCs --->
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | 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 ChildProcessManager::setupEventHandlers | 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 ChildProcessManager::setupEventHandlers | Child process stderr: <--- JS stacktrace --->
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | 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 ChildProcessManager::setupEventHandlers | Child process stderr: ----- Native stack trace -----
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: 1: 0xb78db3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/usr/bin/node]
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: 3: 0xee85e7 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/usr/bin/node]
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: 17: 0x1512628 v8::internal::Runtime_NewArray(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]
2026-07-21 21:00:39 error ChildProcessManager::setupEventHandlers | Child process stderr: 18: 0x7f9597eda476
자식 종료 및 revision 결과:
2026-07-21 21:01:45 error ChildProcessManager::setupEventHandlers | Child process exited
2026-07-21 21:01:45 error ChildProcessManager::setupEventHandlers | Child process closed
2026-07-21 21:01:45 error Process killed by signal SIGABRT (code: null)
2026-07-21 21:01:45 error BimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)"
{
"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}
}
비교 작업의 규모:
BimRevisionService::runBimCompare | params - query.entities.length: 154331, checkExistence.length: 57346, levels: [{"id":72846,"name":"ROOF","elevation":19.1262},{"id":72845,"name":"LEVEL 2","elevation":9.2837},{"id":72827,"name":"LEVEL 1","elevation":0}]
부모 서비스의 heap 설정 (Dockerfile 부팅 스크립트):
node --max-old-space-size=8192 /tmp/agent/dist/app.cjs \
ChildProcessManager 는 fork 시 execArgv 를 지정하지 않으므로 (packages/base/src/manager/child-process.manager.ts:74) 자식도 8192 MB 상한을 상속받는다. GC 로그의 ~8233 MB 총 heap 은 이 설정과 일치한다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 자식 프로세스가 V8 OOM 으로 SIGABRT — 대형 BIM (154K entity) 비교가 8 GB heap 을 초과 | FATAL ERROR: ... JavaScript heap out of memory, GC 로그 7916.1/8233.8 MB, mu = 0.006 (GC 가 회수 불가), runBimCompare params entities.length: 154331, Process killed by signal SIGABRT |
— | Confirmed |
| H2 | Forge 원격 API 오류로 인한 crash (예: ForgeUtilsError: Resource not found 계열) |
이후 21:42 시간대에 ForgeUtilsError: Resource not found 로그가 관측됨 |
21:42 로그는 완전히 다른 시각의 별개 revision 이며 사건 발생 시각(21:00:39)에는 stderr 에 FATAL ERROR ... heap out of memory 만 존재. Forge API 401/404 크래시라면 native stack 이 아니라 JS Error 스택이 나옴. |
Rejected |
| H3 | 부모 프로세스가 자식에 timeout 을 걸어 SIGKILL 로 종료 (executeTimeout) | child-process.manager.ts:213-225 에 SIGKILL timeout 로직 존재 |
종료 signal 이 SIGABRT (V8 자체가 abort() 호출) 이지 SIGKILL 이 아님. BimCompareManager.execute 는 timeoutMs 인자를 전달하지 않아 timeout 이 비활성 (manager.ts:65). |
Rejected |
| H4 | 컨테이너/노드 레벨의 memory limit (cgroup OOM killer) 로 SIGKILL 됨 | OOM 성 crash 라는 방향은 같음 | signal 은 SIGABRT (V8 내부 abort) 이며, stderr 에 V8 자체 진단 (Ineffective mark-compacts near heap limit) 이 정상적으로 flush 됨. cgroup OOM 이었다면 이 진단 없이 SIGKILL 로 즉시 종료됨. |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 원인 revision 확인 및 재처리 정책:
bim_id=17216,src_revision_id=25433는Error상태로 남아 있다. clark-vdc 팀과 협의해 재실행 여부 결정 (heap 상향 없이 다시 돌리면 재현 확률이 높음). 이 항목은 데이터/운영 조율이 필요하므로 코드 자동 수정 대상에서 제외. - 자식 프로세스 heap 상한 명시적 상향:
packages/base/src/manager/child-process.manager.ts:74의fork()호출에execArgv: ['--max-old-space-size=<N>']를 넘겨 부모와 분리해 자식 heap 을 명시적으로 관리 (예: 12288 또는 16384 MB — 컨테이너 메모리 여유 확인 후). BIM 규모가 계속 커지는 추세라면 즉시 필요. 값은ChildProcessOptions로 노출해 서비스별 (bim-revision agent 만) 튜닝 가능하게 하는 방향 권장.- 대안:
packages/cupix-tesla-bim-revision-agent컨테이너 부팅 스크립트 (docker_services/80_run-agent.sh대응) 에서NODE_OPTIONS=--max-old-space-size=<N>를 지정 — 이 경우 fork 자식도 자동 상속.
- 대안:
- 컨테이너 메모리 확보 확인: heap 을 8 GB 이상으로 올리려면 EKS/ECS task 의 container memory 를 그에 맞게 조정해야 한다 (cupix-infrastructure). 현재 컨테이너 memory limit 은 이 RCA 범위 밖 — infra 팀과 확인 필요.
단기 개선 (1주 이내)#
- 입력 크기 기반 조기 실패:
runBimCompare진입 시query.entities.length와checkExistence.length를 검사해 임계치 (예: 200K entity) 초과 시 즉시 명시적 error code (BimRevisionInputTooLarge같은 신규 코드) 로 fail-fast 하고 SQS message 를 dead-letter 처리. 15분씩 heap 을 태우다 SIGABRT 로 끝나는 현재 동작은 리소스 낭비. - OOM detection 로깅 개선:
ChildProcessManager의close핸들러에서 signal 이SIGABRT이고 최근 stderr 라인에heap out of memory를 포함하면 별도 error code (ChildProcessOom) 로 태깅해 alerting 을 명확히. 현재는 20여 개의 V8 frame 이 개별 error 클러스터로 튀어 클러스터링/알람이 왜곡된다. - child process stderr 를 debug 로 강등하거나 buffered logging:
child-process.manager.ts:138-141에서 자식 stderr 를 무조건logger.error로 흘리는 대신, buffer 에 모아 종료 시 하나의 error 로 요약 로깅. 이렇게 하면 fingerprint 폭발 (같은 사건이 25개 클러스터로 잡히는 현상) 이 방지된다.
장기 개선 (재발 방지)#
- BIM compare 라이브러리 (
@cupix/forge-agents) 메모리 프로파일링: 154K entity 비교가 정말 8 GB 를 필요로 하는지 검증.treePathmatching strategy 나checkExistence리스트가 O(N²) 메모리를 쓰는 구간이 있는지 확인. - entity 단위 streaming/chunking: 현재
ForgeAgent.extract는 전체 entity 를 in-memory 로 처리. level 별 또는 model 별 chunk 로 나눠 처리하는 API 도입 검토. - agent 별 자원 프로파일 표준화: bim-revision-agent 는 heavy-memory tier, 다른 agent 는 표준 tier 로 컨테이너 스펙과 heap 상한을 서비스 카탈로그에 명시.
Monitoring#
- 추가할 메트릭/알림
- 자식 프로세스 SIGABRT 발생률 (agent 별)
- V8 heap out of memory 발생 횟수 (agent 별)
- BIM revision 별
entities.lengthp95/p99 (입력 크기 추세) REVISE-END status=error카운트 (bim-revision-agent)
Datadog 쿼리 예시:
service:cupixworks-any-bimrevision-agent status:error "JavaScript heap out of memory"
service:cupixworks-any-bimrevision-agent status:error "Process killed by signal SIGABRT"
service:cupixworks-any-bimrevision-agent "REVISE-END" @status:error
Risk Assessment#
- Risk level: medium — 단일 대형 BIM 에 대해 재현되며 다른 BIM 에도 규모가 커지면 재발 가능. 자식 프로세스 crash 는 SQS 메시지 재시도로 무한 loop 를 만들 수 있으므로 fail-fast 게이트가 없으면 지속적으로 자원을 태운다.
- 예상 복잡도: standard — heap 튜닝 + fail-fast 임계치는 소규모 변경. 자식 stderr 로깅 개선은
packages/base공용 코드라 회귀 테스트 필요.