ES /docs

PixGenieProcess | process error: The operation was aborted

RCA: PixGenieProcess | process error: The operation was aborted

Overview#

What Happened#

2026-07-02 22:52 KST 에 cupixworks-pano-postprocessor-instance 서비스에서 tenant cana capture 를 처리하던 4개 인스턴스가 동시에 PixGenieProcess | process error: The operation was aborted 로그를 남기고 실패했다. 이 로그는 pixgenie.process.ts:116 의 child process 'error' 이벤트 핸들러에서 발생하며, 원인은 동일 파일 line 18 에 하드코딩된 TIMEOUT_MS = 2 * 60 * 60 * 1000 (2 시간) AbortSignal.timeout 이 만료되어 _abortController.abort(new Error('Process timeout')) 가 호출되었기 때문이다. 본 클러스터는 자매 클러스터 608b2052-c042-4bec-afcd-052a31b82fd0 (PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}) 과 동일한 abort 이벤트에서 함께 발생한다 — 하나의 abort 가 error 리스너와 execute() catch 블록 두 곳에서 각각 로깅된다.

Quick Facts#

Field Value
exception.class AbortError
exception.message The operation was aborted
top_frame packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:116
runtime Node.js child_process (spawn python3) on Ubuntu 22.04
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
cana (tenant cupix) 4 PixGenie 전처리 대상 capture 의 처리 중단 — 자매 클러스터 608b2052 에서 확인된 capture 724274 와 동일한 abort 사건으로 판단됨

Timeline#

  1. 2026-07-02 20:52:33 KSTPixGenieProcess::execute | begin (4개 인스턴스가 동시에 Python 자식 프로세스 spawn; UTC 11:52:33)
  2. 2026-07-02 22:52:33 KST — 2 시간 경과, timeoutSignal 발화 → logger.warn('PixGenieProcess | process timeout, aborting...') (4건)
  3. 2026-07-02 22:52:33 KST_abortController.abort(new Error('Process timeout')) 호출 → Node.js 가 자식 프로세스에 SIGTERM 발송
  4. 2026-07-02 22:52:33 KST — ChildProcess 'error' 이벤트 발화 → logger.error('PixGenieProcess | process error: %s', error.message) → 본 클러스터 대표 에러 로그 (4건)
  5. 2026-07-02 22:52:33 KST — 동일 abort 로 spawn Promise 가 AbortError reject → 상위 execute() catch 가 PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"} 로그 발생 (자매 클러스터 608b2052)

Error Log#

Datadog Logs

text
PixGenieProcess | process error: The operation was aborted

Impact#

  • Service: cupixworks-pano-postprocessor-instance
  • Team: cana
  • 발생 횟수: 4
  • 최초 발생: 2026-07-02 22:52 KST
  • 최근 발생: 2026-07-02 22:52 KST

4건 모두 동일 시각(22:52:33 KST)에 발생했으며, warn 레벨의 PixGenieProcess | process timeout, aborting... 로그도 동일 시각에 정확히 4건이 관찰된다. 즉 하나의 abort 이벤트가 4개 인스턴스 각각에서 한 번씩 발생한 단일 사건이며, 자매 클러스터 608b2052 의 4건과 함께 총 8개의 error 로그가 4개의 원인(=aborted process) 에서 파생됐다. 최근 14일 내 다른 발생 이력: 2026-06-21 19:39 KST (3건), 2026-06-20 08:04 KST (1건) — 하드코딩된 2 시간 상한이 주기적으로 재발한다는 신호.

Root Cause Summary#

본 클러스터의 에러 로그는 pixgenie.process.ts:116spawn 으로 띄운 Python 자식 프로세스의 'error' 이벤트 핸들러에서 발생한다. Node.js 는 spawn(..., { signal }) 로 넘긴 AbortController.signal 이 abort 되면 자식 프로세스에 SIGTERM 을 보낸 뒤 ChildProcess 인스턴스에 AbortError'error' 이벤트를 emit 한다. 트리거는 pixgenie.process.ts:56 에서 설정한 AbortSignal.timeout(2 * 60 * 60 * 1000) 이며, 2 시간이 지나면 timeoutSignal 이 발화하여 _abortController.abort(new Error('Process timeout')) 를 호출한다 (line 60). 즉 이 클러스터는 "PixGenie 처리 시간이 하드코딩된 2 시간 타임아웃을 초과했다" 가 근본 원인이며, 자매 클러스터 608b2052-c042-4bec-afcd-052a31b82fd0 (ABORT_ERR / AbortError) 과 동일한 abort 이벤트의 다른 로그 지점에 불과하다. 로그 메시지가 The operation was aborted 로만 표시되고 실제 원인("Process timeout") 이 사라진 것은 error.message 만 포맷하는 로거 호출 방식의 부수적 관찰성 이슈다.

Technical Analysis#

Code Path#

  • Entry: pixgenie.process.ts:24 (PixGenieProcess.execute) — 호출자는 PixGenieManager 를 통해 PreprocessorService::run 에서 진입
  • Timeout 설정: pixgenie.process.ts:52-61 (runPythonProcessAbortSignal.timeout 등록)
  • Spawn 호출: pixgenie.process.ts:65-70spawn('python3', ..., { signal: this._abortController.signal })
  • Failure point (본 클러스터 로그): pixgenie.process.ts:115-118 — ChildProcess 'error' 리스너
  • 동시 발생 (자매 클러스터 로그): pixgenie.process.ts:36-39execute() catch 블록의 JSON.stringify(error)

Timeout 등록 로직:

packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:52-70typescript
private runPythonProcess(params: PixGenieProcessParams): Promise<void> {
    return new Promise((resolve, reject) => {
        this._abortController = new AbortController();

        const timeoutSignal = AbortSignal.timeout(this.TIMEOUT_MS);

        timeoutSignal.addEventListener('abort', () => {
            logger.warn('PixGenieProcess | process timeout, aborting...');
            this._abortController!.abort(new Error('Process timeout'));
        }, { once: true });

        logger.debug('PixGenieProcess::runPythonProcess | executing: python3 %s', PixGenieProcess.PIXGENIE_SCRIPT_PATH);

        this._process = spawn('python3', [PixGenieProcess.PIXGENIE_SCRIPT_PATH], {
            env: { ...process.env, ...this.buildEnvironment(params) },
            stdio: ['ignore', 'pipe', 'pipe'],
            cwd: params.workspaceDir,
            signal: this._abortController.signal
        });

        this.setupProcessHandlers(resolve, reject);
    });
}

하드코딩된 2 시간 상수:

packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:18typescript
private readonly TIMEOUT_MS = 2 * 60 * 60 * 1000; // 2 hours

본 클러스터 로그가 발생하는 정확한 지점 (child process 'error' 이벤트):

packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:115-118typescript
this._process.on('error', (error) => {
    logger.error('PixGenieProcess | process error: %s', error.message);
    reject(error);
});

기대 동작: Python 자식 프로세스가 정상 실행 후 'close' 이벤트에서 exit code 0 으로 resolve() (line 108-109).

실제 동작: 2 시간 시점에 AbortSignal.timeout 이 발화 → _abortController.abort(new Error('Process timeout')) → Node.js child_process 가 signal 옵션에 반응해 SIGTERM 을 보내고 ChildProcess 에 AbortError(code: ABORT_ERR, message: 'The operation was aborted') 로 'error' 이벤트 emit → 본 클러스터의 logger.error('PixGenieProcess | process error: %s', error.message) 실행. reject(error) 로 인해 상위 execute() catch 도 실행되어 자매 클러스터 608b2052execute | failed - {"code":"ABORT_ERR",...} 로그가 함께 남는다.

Log Evidence#

Datadog 쿼리 (본 클러스터 대표 로그 재현):

text
service:cupixworks-pano-postprocessor-instance status:error @environment:production "PixGenieProcess"

동일 시각(22:52:33 KST)에 나타난 8개 error 로그 (본 클러스터 4건 + 자매 클러스터 4건):

text
[2026-07-02 22:52:33] error  PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}
[2026-07-02 22:52:33] error  PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}
[2026-07-02 22:52:33] error  PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}
[2026-07-02 22:52:33] error  PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}
[2026-07-02 22:52:33] error  PixGenieProcess | process error: The operation was aborted
[2026-07-02 22:52:33] error  PixGenieProcess | process error: The operation was aborted
[2026-07-02 22:52:33] error  PixGenieProcess | process error: The operation was aborted
[2026-07-02 22:52:33] error  PixGenieProcess | process error: The operation was aborted

Abort 트리거 증거 (warn 레벨의 timeoutSignal 발화 로그) — 정확히 동일 시각에 4건:

text
service:cupixworks-pano-postprocessor-instance @environment:production "PixGenieProcess | process timeout"
text
[2026-07-02 22:52:33] warn   PixGenieProcess | process timeout, aborting...
[2026-07-02 22:52:33] warn   PixGenieProcess | process timeout, aborting...
[2026-07-02 22:52:33] warn   PixGenieProcess | process timeout, aborting...
[2026-07-02 22:52:33] warn   PixGenieProcess | process timeout, aborting...

동일 로직으로 재발한 과거 사건 (지난 14일):

text
[2026-06-21 19:39:39] warn   PixGenieProcess | process timeout, aborting... (3건)
[2026-06-20 08:04:22] warn   PixGenieProcess | process timeout, aborting... (1건)

자매 클러스터 608b2052 의 상위 PreprocessorService::run 스택 (동일 abort 이벤트, 완전한 cause 포함):

json
{
  "stack": "AbortError: The operation was aborted\n    at abortChildProcess (node:child_process:725:27)\n    at EventTarget.onAbortListener (node:child_process:795:7)\n    at AbortController.abort (node:internal/abort_controller:392:5)\n    at timeoutSignal.addEventListener.once (/tmp/agent/dist/app.cjs:6613:31)",
  "message": "The operation was aborted",
  "cause": {
    "message": "Process timeout",
    "name": "Error"
  },
  "code": "ABORT_ERR",
  "name": "AbortError"
}

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 하드코딩된 2 시간 AbortSignal.timeout 이 만료되어 Node 가 SIGTERM 을 보냈고, ChildProcess 'error' 이벤트가 AbortError 로 emit 되어 pixgenie.process.ts:116 이 로그를 남김 동일 시각에 `PixGenieProcess process timeout, aborting...warn 로그 4건; 자매 클러스터의 상위 stack 이abortChildProcesstimeoutSignal.addEventListener.once로 이어짐;cause: { message: "Process timeout" }`
H2 Python 프로세스가 자체 크래시로 exit code ≠ 0 종료 → 'close' 리스너에서 reject(new Error('PixGenie process failed with exit code ...')) 발생 해당 메시지가 로그에 없음; 대신 The operation was aborted (AbortError) 가 발생. code === 0 이 아닌 close 는 line 111 로 흐르며 별도 메시지를 남기게 되어 있음 Rejected
H3 본 클러스터와 자매 클러스터 608b2052 는 서로 다른 원인의 별도 사건 두 클러스터 모두 정확히 4건, 정확히 동일 시각(22:52:33 KST, ms 차이 0.001s), 동일 first_seen. 코드상 하나의 abort 는 'error' 리스너(line 115) 와 execute() catch(line 36) 두 지점에서 각각 로깅됨 Confirmed as duplicate log surface of same event
H4 상위 서비스 shutdown/terminateService 로 인한 abort terminateServicePreprocessorService::run 종료 이후 시작되는 별도 forced-shutdown 타이머로, 2 시간 시점의 abort 와 시점 및 스택이 일치하지 않음 (자매 클러스터 RCA 에서 이미 배제) Rejected
H5 로그 메시지가 The operation was aborted 로만 나오는 것은 logger.error('... %s', error.message)AbortError.message 만 포맷하기 때문 (cause 손실) pixgenie.process.ts:116error.message 만 인자로 넘김; AbortError.cause = Error('Process timeout') 는 로그에서 사라짐 상위 PreprocessorService::run 이 완전한 stack/cause 를 별도로 남기므로 전체 정보 손실은 아님 Confirmed (secondary — 관찰성 개선 필요)

Fix Recommendation#

즉시 조치 (Critical)#

  • 본 클러스터는 자매 클러스터 608b2052-c042-4bec-afcd-052a31b82fd0 의 duplicate log surface 이므로 별도 수정 아이템을 만들 필요는 없다. 근본 대응은 자매 클러스터의 RCA (content/docs/incidents/608b2052-c042-4bec-afcd-052a31b82fd0/rca.mdx) 에 정의된 방향(2 시간 하드코딩 재평가 + 실제 capture 처리 시간 조사) 을 따른다.
  • Deduplication 관점의 즉시 조치: pixgenie.process.ts:116'error' 리스너는 reject(error) 만 하고 로그를 남기지 않도록 정리하는 방향을 검토한다. 상위 execute() catch 가 이미 동일 error 를 로깅하므로 line 116 의 log 는 하나의 실패에 대해 두 개의 error 로그를 만드는 중복 지점이다.

단기 개선 (1주 이내)#

  • 로그 중복 제거: 'error' 리스너에서 logger.error(...) 를 삭제하거나, 반대로 상위 execute() catch 의 JSON.stringify(error) 로그를 유일한 error surface 로 삼는다. 파일: packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:115-118.
  • 관찰성 수정 (pixgenie.process.ts:37, :116 공통 문제): error.message / JSON.stringify(error) 대신 팀 표준 로거 직렬화(예: stringifyErrorpreprocessor-service.ts 에서 이미 사용) 를 사용해 cause (Process timeout) 까지 로그에 포함되게 한다. 현재는 대표 에러 로그만으로 원인이 "timeout" 임을 확인할 수 없다.
  • 타임아웃 상수 외부화: TIMEOUT_MS 를 환경변수(예: PIXGENIE_TIMEOUT_MS) 로 노출해 코드 배포 없이 조정 가능하도록 한다 (자매 클러스터 RCA 와 동일 권고).

장기 개선 (재발 방지)#

  • 자매 클러스터 RCA 의 장기 권고(재시도/재개 checkpoint, capture-size 기반 동적 타임아웃, 무진행 heartbeat 기반 abort) 를 그대로 적용한다.
  • 클러스터링 개선 (error-sweeper 측): 동일 abort 이벤트가 서로 다른 로그 지점에서 두 개의 클러스터로 분리되지 않도록 fingerprint 규칙 재검토 — 현재 PixGenieProcess | process error: %sPixGenieProcess::execute | failed - %s 가 별도 fingerprint 로 분리된다.

Monitoring#

  • PixGenie 2 시간 타임아웃 발생 카운트 — 하드코딩된 상한이 재발하는지 추적:
text
service:cupixworks-pano-postprocessor-instance @environment:production "PixGenieProcess | process timeout"
  • 본 클러스터의 로그 지점 카운트 — deduplication 이후 0 이 되어야 정상:
text
service:cupixworks-pano-postprocessor-instance @environment:production status:error "PixGenieProcess | process error"
  • PixGenie 성공 대비 실패 비율 — abort 발생률 트렌드:
text
service:cupixworks-pano-postprocessor-instance @environment:production "PixGenieProcess::execute" ("completed successfully" OR "failed")

Risk Assessment#

  • Risk level: low — 자매 클러스터 608b2052 와 동일 abort 이벤트에서 파생된 duplicate log surface. 사용자 영향은 자매 클러스터의 impact 로 이미 커버됨.
  • 예상 복잡도: trivial — pixgenie.process.ts:115-118 의 로그 지점 정리 또는 로거 직렬화 교체. 근본적인 2 시간 타임아웃 문제는 자매 클러스터 트랙에서 처리.