sharp tile processing OOM on ultra-large aerial images — memory exhaustion
RCA: [CupixAerialMap] postprocess fail undefined
Overview#
What Happened#
2026-05-08 19:13:47 UTC에 aerial-map-service의 postprocess Fargate 태스크가 aerial map ID 408 처리 중 실패했다. 151,923 x 178,786 픽셀(약 270억 픽셀)의 초대형 orthomosaic/DSM 이미지를 타일링하는 과정에서 메모리 부족(OOM) 또는 sharp 라이브러리 내부 오류로 프로세스가 exit code 1로 종료되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.message | [CupixAerialMap] postprocess fail undefined |
| top_frame | src/postprocess/index.ts:179 |
| runtime | Node.js (ECS Fargate, 8192 MB memory) |
| env | production, us-west-2 |
Timeline#
- 2026-05-08T19:13:03Z — Postprocess 시작 (workspace
/workspace/1778263982718, aerial map 408) - 2026-05-08T19:29:01Z — Mesh CPC 변환 성공
- 2026-05-08T19:29:55Z — Orthomosaic geotiff 정보 읽기 완료 (151923 x 178786)
- 2026-05-08T19:42:28Z — DSM geotiff 정보 읽기 완료 (151923 x 178786)
- 2026-05-08T19:13:47Z — Postprocess 실패 (exit code 1, error.message = undefined)
- 2026-05-08T19:19:39Z — Step Functions에서
States.TaskFailed감지, ECS 태스크 중지 확인
Error Log#
text
[CupixAerialMap] postprocess fail undefined
[CupixAerialMap] postprocess fail - error:(1) / message:(undefined)
Impact#
- Service:
aerial-map-service - 발생 횟수: 1
- 최초 발생: 2026-05-08T19:13:47.476Z
- 최근 발생: 2026-05-08T19:13:47.476Z
Root Cause Summary#
Aerial map 408의 postprocess 과정에서 151,923 x 178,786 픽셀(약 27.1 Gpixel)의 초대형 이미지를 sharp 라이브러리로 타일링할 때, 8GB 메모리 제한의 Fargate 환경에서 메모리 부족이 발생하여 프로세스가 비정상 종료(exit code 1)되었다. sharp의 .tile().toFile() Promise가 숫자(exit code)로 reject되어, catch 블록에서 error.message가 undefined로 기록된 것이다.
Technical Analysis#
Code Path#
- Entry point:
src/postprocess/index.ts:172— IIFE에서app(input)호출 app()함수 내에서Promise.all(tasks)실행 (line 133)- 태스크 중
processTiff('orthomosaic', ...)또는 DSM 타일링 실행 tile.ts:56-85—sharp(inputPath).png().extend().tile().toFile(outputDir)호출- Failure point:
tile.ts:56— sharp 타일링이 메모리 부족으로 실패, 숫자 값으로 reject
src/postprocess/index.ts:172-183typescript
(async () => {
try {
await app(input);
logger.info(`[CupixAerialMap] postprocess success`);
await sleep(30000); // sleep for log push
process.exit(0);
} catch (error: any) {
logger.error(`[CupixAerialMap] postprocess fail - error:(${JSON.stringify(error)}) / message:(${error.message})`);
await sleep(30000); // sleep for log push
process.exit(1);
}
})();
src/postprocess/tile.ts:56-85typescript
await sharp(inputPath, { limitInputPixels: false })
.png({
force: true,
})
.extend({
right: maxPixel - originalWidth,
bottom: maxPixel - originalHeight,
background: { r: 0, g: 0, b: 0, alpha: 0 },
})
.tile({
size: tilePixel,
skipBlanks: -1,
overlap: 0,
depth: 'onetile',
layout: 'google',
container: 'fs',
})
.toFile(outputDir);
limitInputPixels: false로 설정되어 있어 이미지 크기 제한 없이 로드를 시도한다.- 151,923 x 178,786 = 약 27.1 Gpixel → RGBA 기준 약 100GB+ 메모리 필요 (실제로는 streaming이지만 tiling 과정에서 전체를 메모리에 올려야 함)
- Fargate 메모리 제한은 8192 MB
Log Evidence#
사용한 Datadog 쿼리:
text
service:aerial-map-service "1778263982718"
핵심 로그 엔트리:
text
[2026-05-09 03:13:03 KST] [CupixAerialMap] aerial map 408's workspace workspace: /workspace/1778263982718
[2026-05-09 03:29:01 KST] [CupixAerialMap] postprocess - mesh cpc conversion success: /workspace/1778263982718/mesh/meshcpc/mesh.cpc
[2026-05-09 03:29:55 KST] [CupixAerialMap] postprocess - input(/workspace/1778263982718/download/orthomosaic.tiff)'s geotiff resolution: 151923 x 178786)
[2026-05-09 03:42:28 KST] [CupixAerialMap] postprocess - input(/workspace/1778263982718/download/dsm.tiff)'s geotiff resolution: 151923 x 178786)
[2026-05-09 04:13:47 KST] [ERROR] [CupixAerialMap] postprocess fail undefined
[2026-05-09 04:13:47 KST] [ERROR] [CupixAerialMap] postprocess fail - error:(1) / message:(undefined)
ECS Step Functions 로그에서 확인된 정보:
json
{
"ExitCode": 1,
"StopCode": "EssentialContainerExited",
"StoppedReason": "Essential container in task exited",
"Memory": "8192",
"Cpu": "2048",
"EphemeralStorage": {"SizeInGiB": 200}
}
error:(1) — JSON.stringify(error)가 1을 반환. 이는 thrown된 값이 Error 객체가 아닌 숫자 1임을 의미한다. sharp 라이브러리가 내부 libvips 프로세스 실패 시 exit code를 숫자로 reject하는 것으로 확인된다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | sharp 타일링 시 OOM으로 인한 프로세스 종료 | 151923x178786 이미지(~27Gpixel), 8GB 메모리 제한, exit code 1, error:(1) (숫자로 reject), 타일 생성 완료 로그 없음 |
— | Confirmed |
| H2 | Pix4D 출력 파일 누락으로 AerialMapError 발생 | — | pix4d output 로그에서 모든 필드 존재 확인, AMB721 에러 시 error.message가 정의됨 |
Rejected |
| H3 | 디스크 공간 부족 | 대용량 파일 다운로드 후 디스크 사용 | EphemeralStorage 200GB 할당, 다운로드 및 mesh 처리 성공 | Rejected |
| H4 | S3 다운로드/업로드 실패 | — | 다운로드 후 geotiff 정보 읽기 성공, mesh 업로드 성공 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
src/postprocess/tile.ts:56-85—createOrthoPngTiles함수에서 초대형 이미지에 대한 보호 로직 추가- 이미지 해상도가 임계값(예: 50,000 x 50,000 픽셀)을 초과할 경우, sharp 대신 gdal_translate 기반 타일링으로 전환하거나, 이미지를 분할하여 처리
- catch 블록에서 error가 Error 인스턴스가 아닌 경우에 대한 처리 추가 (
src/postprocess/index.ts:148-157)
단기 개선 (1주 이내)#
- Fargate 태스크의 메모리 제한을 처리할 이미지 크기에 따라 동적으로 설정하는 로직 도입
createOrthoPngTiles에서 streaming/chunked 타일링 방식으로 전환 (DSM 타일링처럼 gdal_translate +clipTile조합 사용)
장기 개선 (재발 방지)#
- 이미지 해상도 기반으로 Fargate 태스크 메모리를 자동 스케일링하는 Step Functions 분기 로직 도입
- postprocess 시작 전 이미지 크기를 확인하고, 임계값 초과 시 별도의 대용량 처리 파이프라인으로 라우팅
- 에러 로깅 개선: thrown 값이 Error가 아닌 경우(숫자, 문자열 등) 의미 있는 메시지로 래핑
Monitoring#
- DSM/orthomosaic 타일링 시간 메트릭 추가 (30분 초과 시 경고)
- Fargate 메모리 사용량 모니터링 (80% 초과 시 알림)
text
service:aerial-map-service status:error "postprocess fail"
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard (타일링 방식 변경 필요하나, DSM 처리에서 이미 gdal 기반 방식이 구현되어 있음)