ES /docs

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#

  1. 2026-07-21 20:53:10 KSTBimRevisionService::runBimCompare | begin — 비교 시작, matchingStrategy: treePath
  2. 2026-07-21 20:53:11 KSTrunBimCompare | params 로그. query.entities.length: 154331, checkExistence.length: 57346, levels 3개
  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 포함)
  4. 2026-07-21 21:01:45 KSTChild process exited / Child process closedProcess killed by signal SIGABRT (code: null)
  5. 2026-07-21 21:01:45 KSTBimRevisionService::run | error: "Process killed by signal SIGABRT (code: null)" — revision 상태 Error 로 업데이트
  6. 2026-07-21 21:01:46 KSTREVISE-ENDelapsed_ms: 883421, status: error, workspace /tmp/workspace/25433 cleanup, SQS 메시지 삭제

Error Log#

Datadog Logs

text
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 process executeForgeAgent.extract)
  • Child fork: applications/agents/packages/base/src/manager/child-process.manager.ts:74fork(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.
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}`);
});

부모는 자식 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) 이다.

applications/agents/packages/base/src/manager/child-process.manager.ts:107-127typescript
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 쿼리 (재현용):

text
service:cupixworks-any-bimrevision-agent status:error @environment:production "ChildProcessManager::setupEventHandlers"
text
service:cupixworks-any-bimrevision-agent "FATAL ERROR" OR "JavaScript heap out of memory"

핵심 로그 항목 (원문):

text
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 결과:

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

비교 작업의 규모:

text
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 부팅 스크립트):

applications/agents/docker_services/80_run-agent.sh:3bash
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=25433Error 상태로 남아 있다. clark-vdc 팀과 협의해 재실행 여부 결정 (heap 상향 없이 다시 돌리면 재현 확률이 높음). 이 항목은 데이터/운영 조율이 필요하므로 코드 자동 수정 대상에서 제외.
  • 자식 프로세스 heap 상한 명시적 상향: packages/base/src/manager/child-process.manager.ts:74fork() 호출에 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.lengthcheckExistence.length 를 검사해 임계치 (예: 200K entity) 초과 시 즉시 명시적 error code (BimRevisionInputTooLarge 같은 신규 코드) 로 fail-fast 하고 SQS message 를 dead-letter 처리. 15분씩 heap 을 태우다 SIGABRT 로 끝나는 현재 동작은 리소스 낭비.
  • OOM detection 로깅 개선: ChildProcessManagerclose 핸들러에서 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 를 필요로 하는지 검증. treePath matching 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.length p95/p99 (입력 크기 추세)
    • REVISE-END status=error 카운트 (bim-revision-agent)

Datadog 쿼리 예시:

text
service:cupixworks-any-bimrevision-agent status:error "JavaScript heap out of memory"
text
service:cupixworks-any-bimrevision-agent status:error "Process killed by signal SIGABRT"
text
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 공용 코드라 회귀 테스트 필요.