[CupixAerialMap] postprocess fail VipsJpeg: Corrupt JPEG data: premature end of data segment
RCA: [CupixAerialMap] postprocess fail VipsJpeg: Corrupt JPEG data: premature end of data segment
Overview#
What Happened#
2026-07-22 00:03:35 KST 에 production aerial-map-service 의 postprocess Fargate task 가 sharp/libvips JPEG 디코딩 단계에서 VipsJpeg: Corrupt JPEG data: premature end of data segment 로 실패했다. aerial map 546 (pix4d project 2814054) 의 후처리 파이프라인이 report, flight_path 단계까지 정상 완료된 뒤 aerial photo 썸네일 생성 단계에서 중단되었으며 해당 postprocess task 는 exit code 1 로 종료되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Error (libvips 로부터 전달됨) |
| exception.message | VipsJpeg: Corrupt JPEG data: premature end of data segment |
| top_frame | applications/aerial-map-service/src/code/src/common/util.ts:290-306 (createThumbnail) |
| runtime | Node.js on Fargate, sharp (libvips) |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| aerial-map-service | 2 (본 클러스터 + 41be567a-8c5e-4e93-8086-00695852a09e) |
aerial map 546 후처리 실패 — 사용자에게 결과 미노출, AMB720 코드로 상태 저장 |
두 클러스터는 동일 실행에서 발생한 서로 다른 log line (top-level catch vs 내부 catch) 이며 실제 후처리 실패 건수는 1건이다.
Timeline#
- 2026-07-22 00:02:17 KST — postprocess Fargate task 기동, workspace
/workspace/1784646137444생성, memory limit8192 MB(Datadog info log) - 2026-07-22 00:02:20 KST — pix4d 결과 목록 수신 (
report_pdf,report_json,flight_path,thumbnail등 포함) - 2026-07-22 00:02:23 KST —
pix4d report output post-processed - 2026-07-22 00:02:24 KST —
pix4d flight path output post-processed - 2026-07-22 00:03:35 KST — sharp/libvips 가 JPEG stream 을 디코딩하는 도중
VipsJpeg: Corrupt JPEG data: premature end of data segment발생,processAerialPhoto루프 실패 (pix4d aerial photo post-processedinfo 로그 없음) - 2026-07-22 00:03:35 KST — inner
catch에서[CupixAerialMap] postprocess fail ${error.message}로깅 후saveError(aerialMapId, 'AMB720')호출 - 2026-07-22 00:03:35 KST — 예외가 재-throw 되어 outer IIFE 의
catch에서[CupixAerialMap] postprocess fail - error:(...) / message:(...)로 재로깅,process.exit(1)
Error Log#
[CupixAerialMap] postprocess fail VipsJpeg: Corrupt JPEG data: premature end of data segment
Impact#
- Service:
aerial-map-service - 발생 횟수: 1 (동일 실행에서 발생한 상위 catch 재로깅 클러스터
41be567a-8c5e-4e93-8086-00695852a09e를 합쳐도 실제 실행 실패 1건) - 최초 발생: 2026-07-22 00:03:35 KST
- 최근 발생: 2026-07-22 00:03:35 KST
Root Cause Summary#
aerial map 546 의 후처리 실행 중 processAerialPhoto 가 각 aerial photo 의 원본 이미지를 axios stream (downloadStreamByUrl) 으로 받아 createThumbnail (sharp/libvips) 로 파이프하는데, 원본 JPEG stream 이 완결되기 전에 EOF 가 도달하여 libvips 가 Corrupt JPEG data: premature end of data segment 를 던졌다. 코드 경로에는 스트림 완결성(Content-Length 대비 수신 바이트 수) 검증, 재시도, corrupt 이미지 skip 로직이 전혀 없어 단일 손상/절단된 사진 하나가 전체 aerial map 후처리를 실패시키는 구조다. 최근 7일 내 동일 scope (svc:aerial-map-service::unknown) 로 이미 resolved 된 incident (2026-07-16-svc-aerial-map-service--unknown-1) 가 존재해 재발성 이슈로 보인다.
Technical Analysis#
Code Path#
- Entry point:
applications/aerial-map-service/src/code/src/postprocess/index.ts:172-183— IIFE 가app(input)을 실행하고 실패 시 outer log emit - Main orchestration:
postprocess/index.ts:36-158(app) - Sub-step call:
postprocess/index.ts:101—processAerialPhoto(cupixApi, aerialMapId, report!, calibration!, aerialPhotos) - Failure point:
applications/aerial-map-service/src/code/src/postprocess/process.ts:66-73(stream download → sharp pipe) 를 통해common/util.ts:290-306의createThumbnail에서 libvips 오류 발생
Outer/inner 이중 로깅으로 인해 클러스터가 두 개로 쪼개진다:
} catch (error: any) {
if (error instanceof AerialMapError) {
await cupixApi.saveError(aerialMapId, error.code, error.reason);
} else {
logger.error(`[CupixAerialMap] postprocess fail ${error.message}`);
await cupixApi.saveError(aerialMapId, ERROR_CODE['AMB720'].code, ERROR_CODE['AMB720'].reason);
}
throw error;
}
// ...
(async () => {
try {
await app(input);
logger.info(`[CupixAerialMap] postprocess success`);
await sleep(30000);
process.exit(0);
} catch (error: any) {
logger.error(`[CupixAerialMap] postprocess fail - error:(${JSON.stringify(error)}) / message:(${error.message})`);
await sleep(30000);
process.exit(1);
}
})();
- 내부
catch(line 152) → 현재 클러스터의 대표 메시지 생성 - 외부 IIFE
catch(line 179) → 자매 클러스터41be567a-...의 대표 메시지 생성 - 동일 예외가 두 번 로그되어 서로 다른 fingerprint 로 클러스터링됨
실제 실패 지점:
for (let i = 0; i < aerialPhotos.length; i++) {
const aerialPhoto = aerialPhotos[i];
const aerialPhotoUploadCredential = await cupixApi.createAerialPhotoS3UploadCredential(aerialMapId, aerialPhoto.id);
// ...
const stream = await downloadStreamByUrl(aerialPhoto.downloadUrl);
const thumbnailKey = `${aerialPhotoUploadCredential.basepath}/thumbnail/thumbnail`;
const thumbnailStream = await createThumbnail(stream);
await uploadS3StreamByKey({
credential: aerialPhotoUploadCredential,
imageKey: thumbnailKey,
stream: thumbnailStream,
});
// ...
}
downloadStreamByUrl은 axiosresponseType: 'stream'만 사용 — 완결성 검증/재시도 없음:
export const downloadStreamByUrl = async (downloadUrl: string) => {
const response = await axios({ method: 'get', url: downloadUrl, responseType: 'stream' });
return response.data;
};
createThumbnail이 sharp 인스턴스에 원본 stream 을 pipe:
export const createThumbnail = async (stream: NodeJS.ReadableStream): Promise<NodeJS.ReadableStream> => {
return new Promise((resolve, reject) => {
const webpStream = stream.pipe(
sharp()
.resize(512, 512, {
fit: 'cover',
position: 'center',
})
.webp({
effort: 0,
quality: 85,
}),
);
webpStream.on('finish', () => resolve(webpStream));
webpStream.on('error', (err) => reject(err));
});
};
기대 동작: for 루프가 aerialPhotos 를 모두 순회, 각 사진의 손상/네트워크 문제는 개별 handling 하고 pix4d aerial photo post-processed info 로그 출력. 실제 동작: 한 장의 사진 stream 이 조기 EOF → libvips 예외 → 루프 중단 → 전체 postprocess 실패, 성공한 사진들의 metadata 저장까지 롤백 없이 중단.
Log Evidence#
Datadog query (본 클러스터):
service:aerial-map-service status:error @environment:production "[CupixAerialMap] postprocess fail VipsJpeg: Corrupt JPEG data: premature end of data segment"
주변 컨텍스트 query:
service:aerial-map-service @environment:production
(2026-07-21T15:02:00 ~ 2026-07-21T15:05:00 UTC)
수집된 로그 (KST 로 정렬):
00:02:17 info [CupixAerialMap] postprocess fargate memory limit: 8192 MB
00:02:17 info [CupixAerialMap] aerial map 546's workspace workspace: /workspace/1784646137444
00:02:20 info [CupixAerialMap] postprocess - pix4d output:({... "thumbnail":{...".jpg"} ...})
00:02:23 info [CupixAerialMap] pix4d report output post-processed
00:02:24 info [CupixAerialMap] pix4d flight path output post-processed
00:03:35 error [CupixAerialMap] postprocess fail VipsJpeg: Corrupt JPEG data: premature end of data segment
00:03:35 error [CupixAerialMap] postprocess fail - error:({"message":"VipsJpeg: Corrupt JPEG data: premature end of data segment"}) / message:(VipsJpeg: Corrupt JPEG data: premature end of data segment)
핵심 관찰:
pix4d aerial photo post-processed(process.ts:81) 로그가 없음 →processAerialPhoto의for루프 완료 전 예외pix4d thumbnail output post-processed(process.ts:119) 로그도 없음 →Promise.all(tasks)대기 중processAerialPhoto실패로 인해 즉시 rethrow (index.ts:101는Promise.all이전에 sequential 호출)- 실행 대상 pix4d project:
2814054, run id1784643225971, aerial map id546
Status board:
{
"scope": "svc:aerial-map-service::unknown",
"active": { "id": "2026-07-21-svc-aerial-map-service--unknown-1", "cluster_ids": ["d913e019-...", "41be567a-..."] },
"recent": [ { "id": "2026-07-16-svc-aerial-map-service--unknown-1", "resolved_at": "2026-07-16T22:47:24.990Z" } ]
}
5일 전 동일 scope 로 resolved 된 incident 가 있어 재발성 패턴이다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | processAerialPhoto 가 개별 aerial photo JPEG stream 을 sharp 로 디코딩하다가 premature EOF 로 libvips 예외를 던졌고, 재시도/skip 로직이 없어 전체 postprocess 가 실패했다 |
로그 순서상 report/flight_path 완료 후, aerial photo post-processed 로그 없이 VipsJpeg 오류; sharp/libvips 는 JPEG 절단 시 정확히 이 메시지를 emit; downloadStreamByUrl (s3.ts:164-167) 는 완결성 검증 없음; createThumbnail (util.ts:290-306) 은 error handler 만 있고 재시도 없음 |
— | Confirmed |
| H2 | pix4d thumbnail output (project_thumb.jpg) 자체가 손상되어 processThumbnail 이 실패했다 |
pix4d output 목록에 thumbnail .jpg 존재 |
processThumbnail (process.ts:103-120) 은 sharp 를 사용하지 않고 단순히 download → S3 upload 만 함 — libvips 를 거치지 않아 VipsJpeg 를 던질 수 없음; 또한 Promise.all(tasks) 이전에 sequential processAerialPhoto 가 먼저 실패했다는 로그 순서와 일치하지 않음 |
Rejected |
| H3 | AerialMapError (AMB721 등 코드 매핑) 로 인한 실패 | inner catch 는 AerialMapError 를 분기 처리 | 로그가 postprocess fail ${error.message} 형식 (line 152) 이므로 non-AerialMapError 분기; AerialMapError 라면 message 대신 code/reason 이 saveError 로만 저장됨 |
Rejected |
| H4 | Fargate memory 부족으로 sharp 가 실패 | sharp 는 대용량 이미지에서 OOM 가능 | 로그에 memory limit 8192 MB 명시, 실패 메시지는 OOM 이 아닌 명확한 Corrupt JPEG data: premature end of data segment (libvips 의 데이터 완결성 오류) |
Rejected |
| H5 | 외부 dependency 전체 outage (pix4d 또는 S3) | 재발성 (7/16 resolved incident 존재) | status board scope 가 dep:* 이 아닌 svc:aerial-map-service::unknown; 같은 실행에서 pix4d output 조회/다운로드는 정상 (report, flight_path 완료); pix4d 원본 photo download URL 은 aerialPhoto 별이라 단일 사진만 실패했을 가능성 |
Rejected as blanket outage; 단일 asset 문제로 좁혀짐 |
Fix Recommendation#
즉시 조치 (Critical)#
applications/aerial-map-service/src/code/src/postprocess/process.ts:56-79의processAerialPhoto루프 안에서 개별 사진 실패를 격리 (try/catch per iteration). 실패한 사진은imageError로 표기하고 다음 사진 진행 — 한 장 손상으로 전체 aerial map 이 실패하는 blast radius 를 축소.applications/aerial-map-service/src/code/src/common/s3.ts:164-167downloadStreamByUrl에 stream 완결성 검증 추가 방향:Content-Length헤더 존재 시 실제 수신 바이트와 비교,response.data의error/aborted이벤트를 명시적으로 reject. 필요 시axios-retry로 재시도 (idempotent GET).- 손상/절단 이미지 처리 정책 결정: skip 후 aerial map 을 partial 성공으로 마무리 vs 명확한 AerialMapError code 를 새로 정의 (예:
AMB726 aerial_photo_corrupted) 하여 UI/API 상에서 재-업로드 유도.
단기 개선 (1주 이내)#
postprocess/index.ts:148-183의 이중 catch 로깅 정리 — 동일 예외가 서로 다른 fingerprint 로 클러스터링되어 alert noise 를 유발. inner catch 에서 이미 log & saveError 를 처리했다면 outer catch 는 rethrow 만 하거나, 반대로 outer 만 남기고 inner 는 saveError 만 수행.processAerialPhoto도tasks배열의Promise.all에 합류시키지 않는 이유(현재 sequential 호출) 를 재검토. sequential 이 필수라면 실패한 사진의 partial 상태에 대한 문서화 필요.- Datadog monitor:
VipsJpeg문자열 및postprocess failcount 를 24h rolling 로 추적하여 재발 즉시 인지.
장기 개선 (재발 방지)#
- 업로드 파이프라인(사용자 사진 → S3) 자체에서 이미지 유효성 검증 (sharp
metadata()호출 성공 여부, EXIF 파싱) 을 postprocess 이전 단계로 이동.common/util.ts:261에 이미 metadata 조회 로직이 있으므로 원본 업로드 완료 시점에 재사용 가능. - Fargate 후처리 재실행 정책 수립: transient network 로 인한 truncated stream 일 경우 자동 재시도 (aerialMap 단위 backoff), corrupt 원본이면 즉시 사용자에게 알림.
aerial-map-service::unknownroot_cause_type 을 좀 더 세분화 (예:image_decode,pix4d_download) 하여 status board 가 재발성을 조기 인식하도록 함 — 7/16 사건과 이번 사건 모두unknown으로 묶여 있어 재발 신호가 약함.
Monitoring#
- Datadog log alert — postprocess VipsJpeg 실패 count
service:aerial-map-service status:error @environment:production "VipsJpeg"
- Datadog log alert — postprocess 전체 실패 count (fingerprint 무관)
service:aerial-map-service status:error @environment:production "postprocess fail"
- Datadog log query — aerial photo 처리 완료율 (성공 signal 로 baseline)
service:aerial-map-service @environment:production "pix4d aerial photo post-processed"
- Datadog log query — 후처리 실행 규모 (분모)
service:aerial-map-service @environment:production "postprocess fargate memory limit"
Risk Assessment#
- Risk level: medium (사용자당 aerial map 결과 미노출, 데이터 손실은 없음; 재발성 있음)
- 예상 복잡도: standard — 개별 사진 격리 + stream 완결성 검증은 국소 변경이며 downstream API 변경 불필요