ES /docs

Process killed by signal SIGSEGV (code: null)

RCA: Process killed by signal SIGSEGV (code: null)

Overview#

What Happened#

2026-07-16 03:54:53 KST에 cupixworks-capture-preprocessor-agent (us-west-2, production, tenant=cupix) 에서 native N-API addon (scenemapperutils_api.node) 을 로드해 실행 중이던 forked 자식 프로세스가 SIGSEGV 로 즉시 종료됐다. ChildProcessManagerclose 이벤트에서 pending IPC 요청들을 rejct 해 부모 프로세스가 Error: Process killed by signal SIGSEGV (code: null) 를 throw 했고, 결과적으로 진행 중이던 SQS 메시지 740624a0-f9f4-46e0-aca5-bd703e088157 가 실패 처리됐다. 14일 retention 내 유일한 발생 건이다.

Quick Facts#

Field Value
exception.class Error (Node.js)
exception.message Process killed by signal SIGSEGV (code: null)
top_frame /tmp/agent/dist/app.cjs:1459:33 (bundled child-process.manager.ts:113-114close handler)
native lib scenemapperutils_api.node (loaded from Environment.SCENEMAPPERUTILS_API_PATH)
runtime Node.js child process forked via fork(actualScriptPath, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'] })
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
flintco (capture preprocessing) 1 단일 SQS 메시지 실패 — 해당 job 은 ApproximateReceiveCount:"1" 로 SQS 가 자동 재큐잉해 재처리 가능

Timeline#

  1. 2026-07-16 03:54:41 KSTJobManager::loadJob | begin - job id: 1199825 (직전 정상 job)
  2. 2026-07-16 03:54:53 KST — 자식 프로세스 SIGSEGV. ChildProcessManagerclose/exit 이벤트를 emit 하고 pending 을 reject
  3. 2026-07-16 03:54:53 KSTCupixAuth::handleError | Undefined response (스택 포함 warn 로그)
  4. 2026-07-16 03:54:53 KST — SQS 메시지 740624a0-f9f4-46e0-aca5-bd703e088157 삭제 시작 (실패 처리)
  5. 2026-07-16 03:54:58 KSTBaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"740624a0-f9f4-46e0-aca5-bd703e088157","Attributes":{"ApproximateReceiveCount":"1"}}}
  6. 2026-07-16 03:54:58 KSTBaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/735964 (crashed job 의 워크스페이스 정리)
  7. 2026-07-16 03:54:58 KSTJobManager::loadJob | begin - job id: 1199831 — agent 즉시 다음 job 처리 재개 (자체 회복)

Error Log#

Datadog Logs

text
Process killed by signal SIGSEGV (code: null)

동일 시각의 warn 로그에는 full stack 이 남아 있다:

text
CupixAuth::handleError | Undefined response: {
  "stack": "Error: Process killed by signal SIGSEGV (code: null)
    at ChildProcess.<anonymous> (/tmp/agent/dist/app.cjs:1459:33)
    at ChildProcess.emit (node:events:536:35)
    at maybeClose (node:internal/child_process:1104:16)
    at ChildProcess._handle.onexit (node:internal/child_process:304:5)
    at Process.callbackTrampoline (node:internal/async_hooks:130:17)",
  "message": "Process killed by signal SIGSEGV (code: null)"
}

app.cjs:1459 는 아래 Code Path 에 나오는 child-process.manager.tsclose 핸들러가 bundling 된 위치 (line 113-114 의 signal ? ... : 표현식 line).

Impact#

  • Service: cupixworks-capture-preprocessor-agent
  • Team: flintco
  • 발생 횟수: 1
  • 최초 발생: 2026-07-16 03:54:53 KST
  • 최근 발생: 2026-07-16 03:54:53 KST
  • Blast radius: 단일 SQS 메시지/job (14일 retention 내 유일한 SIGSEGV — Datadog 쿼리 service:cupixworks-capture-preprocessor-agent "SIGSEGV" 결과 3건 모두 같은 03:54:53 이벤트의 error/warn 중복 로그)
  • 자동 회복: 5초 후 다음 job (1199831) 을 정상 로드해 처리를 계속함

Root Cause Summary#

cupixworks-capture-preprocessor-agent 는 native N-API addon scenemapperutils_api.node 를 자식 프로세스에 로드해 preprocessing 을 수행한다. 이 native 라이브러리 내부에서 발생한 segmentation fault 로 자식 프로세스가 SIGSEGV 로 즉시 종료됐고, ChildProcessManagerclose 이벤트 핸들러가 pending IPC 요청을 Error: Process killed by signal SIGSEGV (code: null) 로 reject 하면서 상위의 SQS 메시지 처리가 실패했다. JavaScript 레벨에서 재현되는 코드 결함이 아니라 native (C++) 코드 경로 — 대부분 scenemapperutils_api.node 내부의 특정 video/photo 입력에 대한 memory 관련 결함 — 이 근본 원인이다. 14일 내 단발성이며 서비스는 즉시 자동 재개됐다.

Technical Analysis#

Code Path#

Entry point: packages/cupix-capture-preprocessor-agent/src/manager/preprocessor.manager.ts:14

PreprocessorManager 생성자는 ChildProcessManager 를 만들어 process/preprocessor.process 스크립트를 fork 하도록 지정한다:

applications/agents/packages/cupix-capture-preprocessor-agent/src/manager/preprocessor.manager.ts:8-15typescript
export class PreprocessorManager {
	private childProcessManager: ChildProcessManager;

	setJobErrorCode?: ((errorCode: string) => void);

	constructor() {
		this.childProcessManager = new ChildProcessManager(__dirname, 'process/preprocessor.process');
	}

Child fork: packages/base/src/manager/child-process.manager.ts:74-77

applications/agents/packages/base/src/manager/child-process.manager.ts:73-84typescript
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}`);
} catch (error: any) {
    logger.error(`ChildProcessManager::start | Fork failed: ${error.message}`, {
        actualScriptPath,
        error: error.stack
    });
    throw error;
}

Native addon load (crash 발생 지점): packages/cupix-capture-preprocessor-agent/src/process/preprocessor.process.ts:262-289

자식 프로세스는 scenemapperutils_api.noderequire 로 로드해 scenemapperutils 클래스를 인스턴스화하고 process() 를 호출한다. SIGSEGV 는 이 native 호출 경로 (process() 또는 setter/getter) 내에서 발생한다:

applications/agents/packages/cupix-capture-preprocessor-agent/src/process/preprocessor.process.ts:262-290typescript
// eslint-disable-next-line  @typescript-eslint/no-require-imports
const PreprocessorApi = require(this.libPath);
const Preprocessor = new PreprocessorApi.scenemapperutils();

// Set photo items
photoPaths.forEach(path => {
    Preprocessor.append_photo_filepath(path);
});

// Set video items
videoInputs.forEach(it => {
    Preprocessor.append_timelapse_video_filepath(it.inputFile, it.name, it.outputDir);
});

// Set APP Data
const processingOptionsFilePath = params.processingOptionsFilePath;
if (typeof processingOptionsFilePath === 'string' && processingOptionsFilePath.length > 0) {
    Preprocessor.set_processing_options_filepath(processingOptionsFilePath);
}

// Set extract video frame image
const enableExtractVideoFrame = params.enableExtractVideoFrame;
if (typeof enableExtractVideoFrame === 'boolean') {
    Preprocessor.set_extract_video_frame_images(enableExtractVideoFrame);
}

await CPUtils.sleep(100);
Preprocessor.process();
await CPUtils.sleep(100);

Preprocessor.process() 는 native (SCENEMAPPERUTILS_API_PATH = lib/scenemapperutils/scenemapperutils_api.node) 호출이므로 이 지점에서 SIGSEGV 가 발생하면 자식 프로세스 전체가 죽고 부모에는 close (code=null, signal='SIGSEGV') 로만 통보된다.

Failure surface (부모 프로세스 관찰 지점): packages/base/src/manager/child-process.manager.ts:107-120

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

이 문자열 (Process killed by signal ${signal} (code: ${code})) 이 그대로 클러스터의 대표 에러 메시지가 됐다. code: null 인 이유는 프로세스가 정상 exit code 를 반환하지 못하고 signal 로 종료됐기 때문 — Node.js 문서에 명시된 signal-terminated 시 code=null, signal='SIGSEGV' 관례다.

기대 동작 vs 실제 동작:

  • 기대: Preprocessor.process() 가 정상 반환 → photo_count(), timelapse_video_frame_count() 등에서 결과 수집 → IPC success:true 응답 → SQS 메시지 delete.
  • 실제: process() (또는 인접 native 메서드) 내부에서 native 크래시 → 자식 프로세스 SIGSEGV → ChildProcessManager 가 pending IPC 를 reject → BaseService::handlingMessageErrors 로 전파 → 메시지 실패 처리.

stdioMode: 'inherit' (default) 이므로 native 라이브러리가 stderr 로 남긴 crash trace 는 컨테이너 로그로 흘렀을 뿐 Datadog 로그 파이프라인에는 잡히지 않았다 — Node.js 레벨 로그만 남는다.

Log Evidence#

Datadog 쿼리 (cluster URL 그대로):

text
service:cupixworks-capture-preprocessor-agent status:error @environment:production "Process killed by signal SIGSEGV (code: null)"

주변 로그 확인 쿼리:

text
service:cupixworks-capture-preprocessor-agent
(시간 범위: 2026-07-15T18:54:00Z ~ 2026-07-15T18:56:00Z)

핵심 로그 시퀀스 (KST 기준, 시간 오름차순):

text
03:54:41  info   JobManager::loadJob | begin - job id: 1199825
03:54:42  info   PreprocessorService::loadCaptureResources | capture resource count: 1
03:54:42  info   PreprocessorService::loadPanos | pano count: 0
03:54:42  info   PreprocessorService::loadVideos | video count: 1
03:54:42  info   JobManager::loadJob | end - job id: 1199825
03:54:53  error  ChildProcessManager::setupEventHandlers | Child process exited
03:54:53  error  ChildProcessManager::setupEventHandlers | Child process closed
03:54:53  error  Process killed by signal SIGSEGV (code: null)
03:54:53  warn   Process killed by signal SIGSEGV (code: null)
03:54:53  warn   CupixAuth::handleError | Undefined response: {"stack":"Error: Process killed by signal SIGSEGV (code: null)\n    at ChildProcess.<anonymous> (/tmp/agent/dist/app.cjs:1459:33)...","message":"Process killed by signal SIGSEGV (code: null)"}
03:54:53  info   AwsQueueManager::deleteMessage | begin - queue url: https://sqs.us-west-2.amazonaws.com/002596530511/cupix-capture-preprocessor-agent-production
03:54:53  info   AwsQueueManager::deleteMessage | end - message id: 740624a0-f9f4-46e0-aca5-bd703e088157
03:54:58  info   BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/735964
03:54:58  error  BaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"740624a0-f9f4-46e0-aca5-bd703e088157","Attributes":{"ApproximateReceiveCount":"1"}}}
03:54:58  info   BaseService::runByMessage | id: 1199831   ← 다음 job 자동 재개

Datadog 14일 retention 내 SIGSEGV 발생 건 (service:cupixworks-capture-preprocessor-agent "SIGSEGV"):

text
Found 3 logs (모두 같은 03:54:53 이벤트의 error/warn 중복 로그).

/tmp/workspace/735964 — crash 당시 처리 중이던 capture 의 로컬 워크스페이스 경로. capture ID 는 735964 로 추정되나, 크래시로 job-scoped 로그가 flush 되기 전에 종료돼 runByMessageloadJob 라인에 명시적으로 나오지 않는다 — cleanup path 가 유일한 단서다 (uncertain — needs verification: 실제 capture 735964 인지 확인하려면 Kibana captures 인덱스나 tesla job_id -> capture_id 매핑 조회 필요).

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Native N-API addon scenemapperutils_api.node 내부에서 SIGSEGV 발생 스택이 child_process internals 로만 이어지고 JS 프레임 없음; 자식 프로세스가 require('scenemapperutils_api.node')Preprocessor.process() 호출 (preprocessor.process.ts:263-289); Node.js 자체가 SIGSEGV 를 self-raise 하는 경우는 매우 드묾 Confirmed
H2 OOM killer (SIGKILL) 로 착각 시그널이 명시적으로 SIGSEGV (signal 11) 이고 SIGKILL (signal 9) 이 아님; OOM 은 container-level 로 오면 보통 SIGKILL 로 표시됨 Rejected
H3 JS 레벨 unhandled exception 이 자식을 죽였다 스택에 JS 사용자 프레임이 없음 (ChildProcess.<anonymous>, emit, maybeClose, _handle.onexit 만); close 이벤트에서 signal='SIGSEGV' 로 도착 — Node.js 프로세스 자체가 signal 로 종료됐다는 의미 Rejected
H4 executeTimeoutMs timeout 으로 SIGKILL 발송 후 SIGSEGV 로 오탐 ChildProcessManagerexecuteTimeoutMs 초과 시 this.process.kill('SIGKILL') 만 호출 (child-process.manager.ts:221) signal 이 SIGKILL 이 아니라 SIGSEGV; SIGKILL 로 죽였으면 로그가 Process killed by signal SIGKILL 로 남아야 함 Rejected
H5 Deploy 로 인한 회귀 (native 라이브러리 버전 변경) Datadog 로그만으로는 확인 불가 14일 retention 내 유일 발생 — 배포 회귀라면 반복될 것으로 예상 (unverified — 배포 이력 대조 필요) Inconclusive

Fix Recommendation#

즉시 조치 (Critical)#

  • 없음. 단발성 SIGSEGV 이고 서비스가 자동 회복해 다음 job 을 계속 처리했다. SQS 메시지는 실패 처리되어 delete 됐으므로 해당 job 은 재큐잉 로직에 따라 재시도 대상이 될 수 있음 — worker/queue 측 재시도 정책 (DLQ, retry count) 확인이 필요하지만 (uncertain — needs verification) 이는 이번 클러스터의 root cause 밖.

단기 개선 (1주 이내)#

  • Crash 재현/디버깅 가시성 확보: ChildProcessManager 를 native crash 가능성이 있는 workload 에 대해서는 stdioMode: 'pipe' 로 시작하도록 옵션을 넘겨 native 라이브러리의 stderr (V8/GLibc backtrace, address, segfault reason) 를 부모 프로세스가 캡처해 Datadog 에 흘리도록 조정. 현재 코드는 이미 pipe 지원 (packages/base/src/manager/child-process.manager.ts:29-30, 70-71, 138-141) — PreprocessorManager 생성자에서 new ChildProcessManager(__dirname, 'process/preprocessor.process', { stdioMode: 'pipe' }) 로 바꾸는 것만으로 활성화 가능. 근거: 재발 시 SIGSEGV 발생 위치와 입력 파일을 특정할 최소 evidence 를 남기기 위함.
  • 실패 메시지의 입력 컨텍스트 로깅 강화: SQS 메시지에서 파싱한 job_id/capture_idBaseService::runByMessage 진입 직후에 반드시 info 로 출력하도록 확인. 현재 로그를 보면 crash 직전에 runByMessage | id: <job_id> 는 남지만, 해당 job 에 매핑되는 capture ID 는 별도 lookup 이후에만 나와 crash 로 truncate 되면 유실됨. sqsMessage 파싱 직후 attribute 를 flush 하도록 로깅 순서 검토.

장기 개선 (재발 방지)#

  • Scenemapperutils native 안정성 개선 절차: SIGSEGV 가 반복 발생하기 시작하면 (1) native lib version (lib/scenemapperutils/version.txt) 을 Datadog tag 로 emit 하고, (2) 크래시된 입력 (video/photo file) 을 격리 저장소로 백업해 로컬 재현이 가능하도록 pipeline 을 확장. 근거: native 코드는 JS 수정으로 우회 불가 — 재현 케이스가 없으면 근본 수정 불가능.
  • Circuit breaker / retry budget: 같은 자식 프로세스 (또는 같은 input 파라미터) 에서 SIGSEGV 가 반복되면 자동 격리하고 Slack 알림을 발화하는 감시 로직. 현재는 단발 실패 후 재큐잉 → 무한 crash-loop 가능성 있음 (uncertain — needs verification: DLQ/maxReceiveCount 설정 확인 필요).

Monitoring#

  • 지표 아이디어: SIGSEGV / non-zero signal exit 빈도, agent 별 job 실패율.
  • Release dashboard 용 Datadog timeseries 쿼리 (writing-datadog-monitoring-queries 룰 준수 — pipe/stats/monitor-only 문법 없음):
text
sum:datadog.estimated_usage.logs.ingested_events{service:cupixworks-capture-preprocessor-agent,status:error}.as_count()

SIGSEGV 문자열이 실제 대표적으로 잡히는 쿼리 (log-based metric 을 만들어 두는 편이 안정적이지만, 임시 관찰용):

text
sum:logs.hits{service:cupixworks-capture-preprocessor-agent,status:error,@message:*SIGSEGV*}.as_count()

BaseService::handlingMessageErrors 로 잡히는 상위 실패 카운트:

text
sum:logs.hits{service:cupixworks-capture-preprocessor-agent,@message:*handlingMessageErrors*}.as_count()
  • 알림: 최근 10분 SIGSEGV 카운트 > 0 이면 warning, > 3 이면 alert. 알림 embed 는 monitor 쿼리 (| stats) 를 별도로 정의해야 하며, 위 timeseries 쿼리와 혼동 금지.

Risk Assessment#

  • Risk level: low (14일 내 단발성, 자동 회복 확인, blast radius = 단일 SQS 메시지).
  • 예상 복잡도: trivial (JS 레벨 수정 없음; native crash 근본 원인은 재현 케이스가 없어 즉시 수정 불가; 개선 항목은 관찰성 강화에 국한).