BaseService::handlingMessageErrors | sqsMessage - {"MessageId":"29feac74-a0ed-4837-bd3b-ec22ee9b6cdd
RCA: BaseService::handlingMessageErrors ENOSPC (capture-preprocessor-agent)
Overview#
capture-preprocessor-agent 가 특정 capture 의 video 를 전처리하다가 컨테이너의 /tmp 디스크가 가득 차 ENOSPC: no space left on device 로 실패했다. 실패 지점은 checkSupportedVideo 가 이미 다운로드된 원본 video 를 검증용 임시 폴더로 다시 복사하는 fs.copyFileSync 단계다. 이 에러는 SQS 시스템 에러로 분류되어 메시지가 삭제되지 않고, ApproximateReceiveCount 가 1 에서 9 까지 반복 재시도되며 매번 같은 지점에서 실패했다.
What Happened#
2026-08-06, production 의 capture-preprocessor-agent 가 team southlandind 의 video capture (job 749769, capture 708445) 를 처리하던 중 /tmp/workspace 디스크 부족으로 원본 .insv 파일 복사에 실패했다. 실패한 message 는 SQS 로 반환되어 9 회까지 재처리되었고, 매 재시도마다 동일한 ENOSPC 로 종료됐다. 같은 원인의 실패가 14일 창에서 4개의 서로 다른 capture 에 걸쳐 138건 관측됐다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Error (Node.js system error, code: "ENOSPC", errno: -28) |
| exception.message | ENOSPC: no space left on device, copyfile '/tmp/workspace/749769/videos/708445/original/708445.insv' -> '/tmp/workspace/749769/supported_videos/VID_20260803_094123_00_004.insv' |
| top_frame | PreprocessorService.checkSupportedVideo (preprocessor-service.ts:846 fs.copyFileSync) |
| syscall | copyfile |
| runtime | Node.js agent, workspace /tmp/workspace (컨테이너 ephemeral disk) |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| southlandind | 138 ENOSPC 이벤트 (14d, 4 captures) | 해당 video capture 들의 preprocessor action 이 완료되지 못하고 반복 실패 |
에러 로그에서 확인된 team 은 southlandind 하나이며, 실패한 capture 는 706551, 706595, 707510, 708445 4개다.
Timeline#
- 2026-08-06 19:17 KST — job
749769message 재시도 시작 (ApproximateReceiveCount:1),checkSupportedVideo에서 첫ENOSPC발생. - 2026-08-06 19:57 KST — 클러스터 대표 로그 기록 (
ApproximateReceiveCount:9), 동일 지점에서 반복 실패. - 2026-08-06 19:57 KST — first_seen = last_seen (collector 가 이 시점 샘플을 클러스터로 수집).
Error Log#
BaseService::handlingMessageErrors | sqsMessage - {"MessageId":"29feac74-a0ed-4837-bd3b-ec22ee9b6cdd","Attributes":{"ApproximateReceiveCount":"9"}}, error:
대표 로그의 error: 뒤가 비어 있는 이유는 아래 Root Cause Summary 및 H2 에서 설명한다. 같은 message 의 실제 에러 내용은 인접 로그에 온전히 남아 있다.
Impact#
- Service:
cupixworks-capture-preprocessor-agent - Team: southlandind
- 발생 횟수: 1 (클러스터 기준) / 138 ENOSPC 이벤트 (14d 로그 실측)
- 최초 발생: 2026-08-06 19:57 KST
- 최근 발생: 2026-08-06 19:57 KST
Root Cause Summary#
capture-preprocessor-agent 는 capture 의 원본 video 를 /tmp/workspace/{job}/videos/{video}/original/ 로 다운로드한 뒤, checkSupportedVideo 단계에서 검증 도구에 넘기기 위해 같은 원본을 /tmp/workspace/{job}/supported_videos/ 로 한 번 더 fs.copyFileSync 복사한다. 이 복사가 원본 크기만큼의 디스크를 추가로 요구하는데, .insv (Insta360) 원본이 큰 경우 컨테이너 ephemeral /tmp 용량을 초과해 ENOSPC: no space left on device 로 실패한다. 이 에러는 HTTP status 가 없는 Node.js system error 라서 getApiErrorToDeleteMessage 가 undefined 를 반환한다(system error 조기 반환). 그 결과 message 는 ApproximateReceiveCount 가 MaxReceiveCount(10) 에 도달하기 전까지 SQS 로 계속 반환되어, 같은 capture 가 매 재시도마다 동일 지점에서 실패하는 결정론적 반복 실패가 된다. 즉 코드가 큰 video 를 위한 디스크 여유를 확보하지 못한 리소스 결함이며, 재시도가 문제를 해소하지 못한다.
Technical Analysis#
Code Path#
- Entry point:
base-service.ts:108—checkingQueue가runByMessages를 호출, 예외 시handlingMessageErrors로 위임. - 실행 흐름:
runByMessage(base-service.ts:153) →PreprocessorService.run(preprocessor-service.ts:93) → video 다운로드 (downloadVideoFiles,preprocessor-service.ts:436) →checkSupportedVideo(preprocessor-service.ts:824). - Failure point:
preprocessor-service.ts:846— 다운로드된 원본을supported_videos/로 복사하는fs.copyFileSync.
run 은 원본 video 다운로드 후 검증을 수행한다.
await this.downloadVideoFiles(cpCapture);
...
await this.loadEntityParameter(cpCapture);
this.checkCameraModel(cpCapture);
this.setScenemapperOptions(cpCapture);
await this.checkSupportedVideo(cpCapture);
checkSupportedVideo 는 이미 로컬에 있는 각 원본 video (it.path) 를 검증용 임시 폴더로 다시 복사한다. 이 시점에 원본과 사본이 동시에 디스크를 점유한다.
cpCapture.cpVideos.forEach(it => {
if (it.path) {
fs.copyFileSync(it.path, path.join(inputDirPath, it.name));
}
});
workspace 는 컨테이너의 ephemeral /tmp 이다.
export const DefaultWorkspacePath = '/tmp/workspace';
에러를 받은 handlingMessageErrors 는 getApiErrorToDeleteMessage 로 삭제 여부를 판단한다. Node.js system error (errno/code/syscall 필드 존재) 는 조기에 undefined 를 반환한다.
if (error.errno != undefined && error.code != undefined && error.syscall != undefined) {
logger.warn('BaseService::getApiErrorToDeleteMessage | nodejs common system error', error);
return;
}
apiErrorObject 가 undefined 이므로 메시지 삭제는 오직 checkReceiveCountToDeleteMessage 가 true 일 때, 즉 ApproximateReceiveCount >= MaxReceiveCount(10) 일 때만 일어난다. 그 전까지는 message 가 SQS 로 반환되어 재처리된다.
if (receiveCount == undefined) return true;
if (receiveCount >= Constants.MaxReceiveCount) return true;
return false;
MaxReceiveCount 는 10 이다.
export const MaxReceiveCount = 10;
기대 동작: 지원 video 검증을 위해 임시 복사가 필요하다면, 복사 전에 여유 디스크를 확인하거나 복사를 피하고(예: 원본 경로를 검증 도구에 직접 넘김) 실패 시 재시도가 무의미한 리소스 에러를 조기 종료해야 한다. 실제 동작: 원본 크기만큼 복사를 시도해 /tmp 를 넘기고, 재시도가 같은 지점에서 반복 실패한다.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-capture-preprocessor-agent "29feac74-a0ed-4837-bd3b-ec22ee9b6cdd"
service:cupixworks-capture-preprocessor-agent "ENOSPC"
대표 로그와 같은 message 의 인접 로그(ApproximateReceiveCount:8)에 실제 에러 payload 가 온전히 남아 있다.
{
"timestamp": "2026-08-06 17:57:30",
"status": "error",
"message": "BaseService::handlingMessageErrors | Error and message object - {\"error\":{\"errno\":-28,\"code\":\"ENOSPC\",\"syscall\":\"copyfile\",\"path\":\"/tmp/workspace/749769/videos/708445/original/708445.insv\",\"dest\":\"/tmp/workspace/749769/supported_videos/VID_20260803_094123_00_004.insv\"},\"sqsMessage\":{\"MessageId\":\"29feac74-a0ed-4837-bd3b-ec22ee9b6cdd\",\"Attributes\":{\"ApproximateReceiveCount\":\"8\"}}}"
}
full stack trace 가 담긴 warn 로그가 실패 지점을 checkSupportedVideo 의 copyFileSync 로 확정한다.
CupixAuth::handleError | Undefined response: {"stack":"Error: ENOSPC: no space left on device, copyfile '/tmp/workspace/748923/videos/707510/original/707510.insv' -> '/tmp/workspace/748923/supported_videos/VID_20260803_094123_00_004.insv'
at Object.copyFileSync (node:fs:3047:11)
...
at PreprocessorService.checkSupportedVideo (/tmp/agent/dist/app.cjs:9919:26)
at PreprocessorService.run (/tmp/agent/dist/app.cjs:9312:20)"}
같은 message (29feac74...) 가 ApproximateReceiveCount 1 → 9 로 반복되며 매 재시도마다 setVideoImageMatchData | begin - capture_id: 749769 직후 동일 ENOSPC 로 종료된다. 결정론적 재시도 실패다.
14일 창에서 ENOSPC 는 138건 발생했고, 서로 다른 4개 capture 로 나뉜다.
/tmp/workspace/747932/videos/706551
/tmp/workspace/747973/videos/706595
/tmp/workspace/748923/videos/707510
/tmp/workspace/749769/videos/708445
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 원본 video 를 supported_videos/ 로 다시 복사하는 checkSupportedVideo 의 fs.copyFileSync 가 /tmp ephemeral 디스크를 초과해 ENOSPC 발생 |
stack trace 최상단 Object.copyFileSync → PreprocessorService.checkSupportedVideo (preprocessor-service.ts:846); error payload code:ENOSPC errno:-28 syscall:copyfile path=.../original/708445.insv dest=.../supported_videos/...; workspace /tmp/workspace (constants.ts:21) |
— | Confirmed |
| H2 | 대표 로그의 error: 가 비어 있는 것은 별도 에러가 아니라 system-error 분류로 errorAndMessage.error 가 undefined 로 덮여 로깅된 것 |
getApiErrorToDeleteMessage system error 조기 return(base-service.ts:245-247); handlingMessageErrors 가 errorAndMessage.error = apiErrorObject 로 대입(base-service.ts:303); 같은 message 의 인접 로그에 full ENOSPC payload 존재 |
— | Confirmed |
| H3 | 특정 capture 의 손상/비지원 video 로 인한 결정론적 애플리케이션 에러 (디스크와 무관) | 같은 capture 가 매 재시도 실패 | 에러가 ENOSPC 시스템 에러이며 파일 검증 로직 이전 복사 단계에서 발생; 4개의 서로 다른 capture 에서 동일 발생 → capture 데이터가 아니라 디스크 리소스 문제 |
Rejected |
| H4 | 외부 의존성 (S3/SQS) 장애 | ApproximateReceiveCount 반복 | status-board svc:cupixworks-capture-preprocessor-agent::unknown active/recent 모두 없음; 에러 payload 가 로컬 filesystem ENOSPC 로 명확 |
Rejected |
| H5 | SQS message 삭제 실패로 무한 재시도 | 재시도 1 → 9 관측 | MaxReceiveCount(10) 도달 시 삭제 로직 정상(base-service.ts:285) — 무한이 아니라 10회 상한, 설계된 재시도 소진 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 대상:
applications/agents/packages/cupix-capture-preprocessor-agent/src/preprocessor-service.ts:844-848(checkSupportedVideo), 동일 패턴이checkLicenseValiditypreprocessor-service.ts:893-897에도 존재. - 방향: 원본 video 를
supported_videos/로 전체 복사하는 대신 복사를 제거하고 검증 도구에 원본 경로를 직접 전달하거나 심볼릭 링크(fs.symlinkSync)로 대체하는 방향을 검토한다. 검증 후deleteFolderRecursive로 정리하지만, 복사 시점에 원본 크기만큼의 여유가 없으면 실패하므로 복사 자체를 줄이는 것이 근본 대응이다. 검증 도구의 입력 형식 제약이 있어 복사가 불가피하면, 복사 전checkAndCreateFolder(inputDirPath)직후 여유 공간(fs.statfs)을 확인하고 부족 시 재시도 무의미한 리소스 에러로 조기 실패시킨다. - 병행:
ENOSPC같은 디스크 리소스 에러는 SQS 재시도로 해소되지 않으므로,handlingMessageErrors경로에서 시스템 리소스 에러(code === 'ENOSPC'등)를 재시도 불가로 판단해 조기 정리/에러 상태 전이하도록 분류를 좁힌다 (base-service.ts:290-313, 프런트/운영 계약 영향 없으므로 agent-side 처리).
단기 개선 (1주 이내)#
- capture-preprocessor-agent 의 ECS task 정의에서 큰
.insv(Insta360) 원본과 그 임시 사본을 동시에 수용하도록 ephemeral storage 용량을 상향한다. 현재/tmp/workspace가 원본 + 사본을 담지 못한다는 것이 로그로 확인됐다. - video 다운로드 완료 직후, 검증/전처리 단계 진입 전에 사용 가능 디스크와 원본 총 크기를 비교해 부족 시 명시적 에러 코드로 종료하도록 가드를 추가한다.
장기 개선 (재발 방지)#
- 전처리 파이프라인에서 대용량 미디어를 여러 번 물리 복사하는 지점을 전수 조사해 (
downloadVideoFiles,checkSupportedVideo,checkLicenseValidity) 스트리밍 또는 참조 전달로 전환한다. - agent 컨테이너에 디스크 사용률 메트릭을 emit 하고, 특정 임계치 초과 시 스케줄러가 큰 job 을 여유 있는 인스턴스로 배치하도록 한다.
Monitoring#
ENOSPC 실패 발생 추이 (fix 배포 후 0 으로 수렴해야 함):
service:cupixworks-capture-preprocessor-agent status:error "ENOSPC"
checkSupportedVideo 복사 실패 로그 추이:
service:cupixworks-capture-preprocessor-agent "no space left on device" "copyfile"
handlingMessageErrors 최종 에러 로그 추이 (agent 처리 실패 전반):
service:cupixworks-capture-preprocessor-agent status:error "handlingMessageErrors"
Risk Assessment#
- Risk level: medium — 데이터 손상은 없으나 해당 team 의 큰 video capture 전처리가 완료되지 못하고 재시도를 소진한 뒤 error 상태로 종료된다.
- 예상 복잡도: standard — 복사 제거/여유 확인 가드 또는 ephemeral storage 상향 중 선택. 검증 도구의 입력 형식 제약 확인이 필요.