FileSystemManager::loadProcessOutput | zip fail
RCA: FileSystemManager::loadProcessOutput | zip fail
Overview#
What Happened#
2026-07-23 19:31 KST, cupixworks-capture-postprocessor-agent 가 capture 737236 (workspace /efs/38b6e1868151d946/) 을 처리하던 중 FileSystemManager::loadProcessOutput 단계에서 archiver 로 skatmaster/results/process_output/ 디렉토리를 zip 으로 묶는 작업이 실패해 zip fail error 로그가 남았다. 실제로는 파이프라인 흐름이 중단되지 않고 후속 단계로 진행됐다 — try/catch 로 감싼 뒤 zip 부재를 정상 흐름으로 처리하는 로직이 존재하기 때문. 14 일 window 안에서 발생 횟수는 2 건으로 매우 낮다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | (no exception class — logger.error string only) |
| exception.message | FileSystemManager::loadProcessOutput | zip fail |
| top_frame | packages/cupix-capture-postprocessor-agent/src/manager/file-system.manager.ts:461 |
| deploy | archiver ^7.0.1, package cupix-capture-postprocessor-agent 1.11.0 |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| capture-postprocessor-agent (wmblanchard) | 1 (14d: 2) | 특정 capture 에서 process_output.zip 이 생성되지 않음. 파이프라인은 계속 진행되지만 해당 capture 의 process_output artifact 는 서버로 업로드되지 않음. |
Timeline#
- 2026-07-23 19:30:52 KST — 이전 capture(740326) 정리, SQS 메시지 삭제, S3 PUT retry 3/3
- 2026-07-23 19:31:03 KST —
BaseService::runByMessage id: 1219574, capture key-nD7JPxEH(capture 737236) 처리 시작 - 2026-07-23 19:31:06 KST —
FileSystemManager::loadProcessOutput | zip fail(error) — 동시에 debug 레벨로not found file - /efs/38b6e1868151d946/skatmaster/results/process_output.zip기록 - 2026-07-23 19:31:06 KST — 이후
copySourceDir는 정상 실행 — 파이프라인은 계속 진행
Error Log#
FileSystemManager::loadProcessOutput | zip fail
Impact#
- Service:
cupixworks-capture-postprocessor-agent - Team: wmblanchard
- 발생 횟수: 1 (14 일 window 에서 2 건)
- 최초 발생: 2026-07-23 19:31 KST
- 최근 발생: 2026-07-23 19:31 KST
기능적 영향은 국소적이다. loadProcessOutput 는 try/catch 로 감싸져 있고, catch 이후 getFileSize(...) === -1 분기가 zip 부재를 정상 흐름으로 처리하기 때문에 상위 파이프라인은 abort 되지 않는다. 다만 해당 capture 는 process_output.zip 이 서버에 업로드되지 않아, 나중에 process output artifact 를 참조하는 후속 단계에서 데이터 부재가 나타날 수 있다.
Root Cause Summary#
Postprocessor 가 skatmaster 단계의 산출물인 /efs/38b6e1868151d946/skatmaster/results/process_output/ 디렉토리를 zip 으로 아카이빙하려 했으나, 해당 소스 디렉토리가 존재하지 않아 archiver 가 error 이벤트를 emit → zipDir promise 가 reject 됐다. loadProcessOutput 는 catch 블록에서 logger.error('... zip fail') 만 남기고 실제 error 객체 (파일 경로, ENOENT 등) 는 로그에 포함하지 않는다. 결과적으로 "무엇을 zip 하려 했는지", "왜 실패했는지" 가 error 로그만으로는 판단 불가하고, debug 레벨 후속 로그 (not found file - .../process_output.zip) 로만 원인이 유추된다. 근본 원인은 (1) upstream skatmaster 파이프라인이 process_output/ 디렉토리를 생성하지 않은 케이스에 대한 방어가 부족하고, (2) 실패 로그에 컨텍스트 (source path, error message) 가 누락된 두 가지 layered defect 이다.
Technical Analysis#
Code Path#
- Entry point:
packages/cupix-capture-postprocessor-agent/src/postprocessor-service.ts:102 FileSystemManager.loadProcessOutput진입:packages/cupix-capture-postprocessor-agent/src/manager/file-system.manager.ts:447- Failure point:
packages/cupix-capture-postprocessor-agent/src/manager/file-system.manager.ts:461 - Zip 작업:
packages/cupix-capture-postprocessor-agent/src/manager/file-system.manager.ts:473-489
loadProcessOutput = async (cpCapture: CPCapture): Promise<void> => {
if (!cpCapture.workspaceDirPath || !this.skatMasterDirPath) {
logger.error('FileSystemManager::loadProcessOutput | undefined directory path');
return;
}
const processOutputZipFileName = `${Constants.DefaultProcessOutputDirName}.zip`;
const processOutputDirPath = path.join(this.skatMasterDirPath, Constants.DefaultResultsDirName, Constants.DefaultProcessOutputDirName);
const processOutputZipFilePath = path.join(this.skatMasterDirPath, Constants.DefaultResultsDirName, processOutputZipFileName);
try {
await this.zipDir(processOutputDirPath, processOutputZipFilePath);
logger.debug('FileSystemManager::loadProcessOutput | zip success');
} catch (err) {
logger.error('FileSystemManager::loadProcessOutput | zip fail');
}
if (CPUtils.getFileSize(processOutputZipFilePath) === -1) {
logger.debug('FileSystemManager::loadProcessOutput | not found file - %s', processOutputZipFilePath);
} else {
const localProcessOutputZipFilePath = path.join(cpCapture.workspaceDirPath, processOutputZipFileName);
logger.debug('FileSystemManager::loadProcessOutput | copy - source: %s, target: %s', processOutputZipFilePath, localProcessOutputZipFilePath);
fs.copyFileSync(processOutputZipFilePath, localProcessOutputZipFilePath);
}
};
private zipDir = (sourcePath: string, outputPath: string): Promise<number> => {
const archive = archiver('zip', { zlib: { level: 9 } });
const outputStream = fs.createWriteStream(outputPath);
archive.pipe(outputStream);
archive.directory(sourcePath, 'process_output');
archive.finalize();
return new Promise((resolve, reject) => {
outputStream.on('close', () => {
const byte = archive.pointer();
resolve(byte);
});
archive.on('error', (err) => reject(err));
});
};
기대 동작: skatmaster 결과 디렉토리 <skatMasterDirPath>/results/process_output/ 가 존재하고 그 내용을 zip 으로 만들어 같은 위치에 process_output.zip 을 생성.
실제 동작: 해당 시점에 소스 디렉토리 (/efs/38b6e1868151d946/skatmaster/results/process_output/) 가 존재하지 않았거나 접근할 수 없어 archiver v7 이 error 이벤트를 emit, zipDir promise 가 reject, catch 블록에서 컨텍스트 없이 zip fail 만 로깅. 이후 getFileSize(...) === -1 분기가 zip 미생성을 정상 흐름으로 흡수 (not found file debug 로그 기록) 하고 파이프라인 계속 진행.
catch 블록에서 원본 err 를 로그에 담지 않아, error 원인 (ENOENT / EACCES / disk full / archiver 내부 상태 등) 을 Datadog 만으로는 특정할 수 없다는 것이 이 클러스터의 핵심 관찰점.
Log Evidence#
Datadog 쿼리 (에러 시점 ±1 분):
service:cupixworks-capture-postprocessor-agent
(time range: 2026-07-23T10:30:30Z — 2026-07-23T10:31:10Z)
해당 capture 처리 시퀀스 (Datadog, info/warn/error 만):
19:31:03 info BaseService::runByMessage | id: 1219574
19:31:03 info CupixAuth::setSession | session_id: 3e4d7e645436b7c60a1384c129b6b086dd5bc3e6
19:31:03 info JobManager::loadJob | begin - job id: 1219574
19:31:03 info JobManager::loadJob | end - job id: 1219574
19:31:03 info PostprocessorService::run | skip update running action job - waiting_actions: [], running_actions: [ 'postprocessor' ]
19:31:03 info CPCapture::fromMeta | key: -nD7JPxEH, version: 1
19:31:03 info CPCapture::updateVideosfromMeta | begin
19:31:03 info CPCapture::updateVideosfromMeta | end
19:31:03 info CPCapture::fromSkatMasterResults | {"error_code":"","skatsdk_version":{"ver":"3.30.26\t2c6644a7e2192c73c3ce86276ac08e29b3df4123\t20260708_060625\tarm64\tUbuntu 22.04.5 LTS"},"task_name":"..."}
19:31:06 error FileSystemManager::loadProcessOutput | zip fail
19:31:10 warn FinalizationService::syncFromServer | begin - retry detected (receiveCount: 22), capture id: 737236
fromSkatMasterResults 의 error_code 는 빈 문자열이라 skatmaster 자체는 error 로 마킹되지 않았지만, process_output/ 디렉토리 산출 여부는 skat 결과 코드와 별개다.
Watch (Kibana) debug 로그, 같은 순간 (동일 capture workspace /efs/38b6e1868151d946/):
19:31:06 debug FileSystemManager::loadProcessOutput | not found file - /efs/38b6e1868151d946/skatmaster/results/process_output.zip
19:31:06 error FileSystemManager::loadProcessOutput | zip fail
19:31:06 debug FileSystemManager::copySourceDir | copy - source: /efs/38b6e1868151d946/preprocessor/results, target: /tmp/workspace/737236
debug 로그가 confirmed 하는 사실: (1) zipDir 실패 직후 zip 파일은 생성되지 않음 (getFileSize === -1), (2) 파이프라인은 abort 되지 않고 다음 단계 (copySourceDir) 로 진행. 따라서 이 error 는 실제로 downstream 작업을 멈추지 않는 "loud warning" 성격이다.
14 일 window 발생 빈도:
service:cupixworks-capture-postprocessor-agent "zip fail"
결과: 2 건 (2026-07-20 17:17 KST, 2026-07-23 19:31 KST) — 매우 낮은 빈도.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | archiver 가 존재하지 않는 소스 디렉토리 (skatmaster/results/process_output/) 에 대해 error 이벤트 emit 하여 zipDir promise reject |
debug 로그 not found file - .../process_output.zip 가 catch 직후 emit; skatmaster 파이프라인이 process_output/ 을 생성하지 않은 케이스로 설명 가능; 파이프라인이 abort 되지 않고 getFileSize === -1 분기로 흡수되는 code path 일치 |
catch 블록이 원본 err 를 로그에 담지 않아 정확한 ENOENT 여부는 로그로 재확인 불가 — 다만 debug 로그의 "not found file" 이 강력한 정황 증거 | Confirmed (정황 근거) |
| H2 | EFS 마운트 이슈 / permission 오류로 zip 쓰기 실패 | write 대상은 같은 EFS (/efs/38b6e1868151d946/skatmaster/results/) 인데 앞뒤 로그에서 copySourceDir 는 정상 수행됨 → mount/permission 은 정상 |
copySourceDir 는 /efs/38b6e1868151d946/preprocessor/results 를 읽고 /tmp/workspace/737236 에 씀 — postprocessor 가 skatmaster EFS 서브트리에 쓰기 권한이 있는지는 별개; 그러나 write 실패시 getFileSize 도 특정 값을 반환했을 텐데 -1 (fs.statSync ENOENT 로 추정) 이므로 write 문제라기보다 source read 문제에 가까움 |
Rejected |
| H3 | archiver v7 라이브러리 자체의 regression / race | package 는 archiver: ^7.0.1 로 최근 major, 이전에도 유사 로그 1 건 (7/20) 존재 |
14d window 2 건은 라이브러리 결함이라기엔 너무 낮은 빈도; 다른 capture 는 정상 처리됨 | Rejected |
| H4 | 상위 파이프라인이 이미 abort 된 상태에서의 부수적 로그 | postprocessor run 은 정상 진행되어 copySourceDir 실행됨; upload/finalization 로그가 이후에 나타남 |
실제로 파이프라인이 abort 되지 않았음 (Log Evidence 참조) → 이 가설은 사실이 아님 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 대상:
packages/cupix-capture-postprocessor-agent/src/manager/file-system.manager.ts:460-462 - 접근: catch 블록에서 원본 error 를 로그에 포함해 후속 트리아지 가능하도록 개선. 소스 디렉토리 부재는 skatmaster 산출물이 없는 정상 케이스일 수 있으므로 severity 를
error에서warn으로 다운그레이드하는 것을 검토. 원본 error 를 로그 argument 로 전달해 파일 경로, ENOENT 여부, archiver 내부 오류를 후속 트리아지에서 확인할 수 있게 한다. - 근거: 현재는 원본
err를 완전히 삭제하여 root cause 를 debug 로그에 의존해서만 유추 가능. 실제 파이프라인 abort 는 없으므로warn이 semantic 에 더 맞고, cupixworks agents 코드베이스의 cplogger 컨벤션 (Error 객체를 직접 넘김) 을 따르는 것이 team convention 과 일치.
단기 개선 (1주 이내)#
- 대상:
packages/cupix-capture-postprocessor-agent/src/manager/file-system.manager.ts:447-471 - 접근:
zipDir호출 전에fs.existsSync(processOutputDirPath)guard 를 추가해 소스 디렉토리가 없을 때는 zip 을 시도하지 않고 debug 로그만 남기고 조기 return. 이렇게 하면 "정상 케이스 (skatmaster 가 process_output 을 생성하지 않은 flow)" 와 "실제 zip 실패 (권한/디스크/archiver 내부 오류)" 를 로그 상에서 분리할 수 있다. - 근거: 현재는 두 케이스가 모두
zip failerror 하나로 뭉쳐 있어 alert 노이즈와 실제 장애의 구분이 어렵다.
장기 개선 (재발 방지)#
- skatmaster 파이프라인 계약을 명시화:
results/process_output/이 언제 생성되고 언제 생략되는지 CPCapture / skatmaster 결과 스펙에 문서화. 필요하다면fromSkatMasterResults단계에서has_process_output플래그를 노출해 postprocessor 가 명시적으로 skip 결정하도록 한다. - agents 공통 error logging convention 강제: catch 블록에서 원본 err 를 절대 삭제하지 않도록 lint rule 또는 shared logger helper 도입 (예:
logCaughtError(logger, 'context', err)).
Monitoring#
- Datadog 쿼리 예시 (release dashboard timeseries widget 용):
service:cupixworks-capture-postprocessor-agent status:error "loadProcessOutput"
service:cupixworks-capture-postprocessor-agent "zip fail"
- 알람 임계: 현재 14 일 window 2 건 baseline. 시간당 5 건 초과 or 하루 20 건 초과 시 alert — 그 이하는 skatmaster 정상 skip 케이스일 가능성이 높으므로 noise.
- 함께 볼 지표:
service:cupixworks-capture-postprocessor-agent "FileSystemManager"info/warn 로그 볼륨 대비 error 비율 — 비율 급증 시 EFS 또는 archiver 라이브러리 이슈 의심.
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial
- 근거: (1) 발생 빈도가 14 일에 2 건으로 매우 낮고, (2) 상위 파이프라인은 abort 되지 않으며, (3) 수정 범위가 단일 파일의 catch 블록과 pre-check guard 로 좁게 한정. 다만 skatmaster 산출물 계약을 실제로 확인하지 않고 코드만 수정하면 "정상 skip 인 줄 알았는데 실은 skat 결함" 을 놓칠 수 있으므로, warn 다운그레이드 전에 최근 발생 capture (예: capture 737236) 의 skatmaster 로그를 함께 확인하는 것을 권장.