이미지 파이프라인
이미지 파이프라인 — sharp 와 그 친척들
이미지를 다루는 코드는 자주 등장합니다. 업로드 받은 사진의 크기를 줄이고, 형식을 변환하고, 썸네일을 만듭니다.
1. sharp 에 대한 이야기
sharp 는 Lovell Fuller 가 2013 년에 시작한 Node 이미지 처리 라이브러리입니다. 핵심 엔진으로 libvips 를 씁니다. libvips 는 1990 년대 영국 국립갤러리의 이미지 분석 프로젝트에서 시작된 C 라이브러리로, 디스크 캐시·스트리밍 처리에 강하다는 평가가 잦습니다.
특징:
- 비동기·Promise 기반 API.
- 메모리 사용량이 ImageMagick 보다 작다는 보고가 많습니다.
- libwebp · libheif · libpng · libjpeg-turbo · libavif 등을 결합해 형식을 지원합니다.
import sharp from 'sharp';
await sharp(input)
.rotate() // EXIF 기반 자동 회전
.resize({ width: 1200, withoutEnlargement: true })
.toFormat('webp', { quality: 80 })
.toFile('out.webp');
2. Pillow (Python)
Pillow 는 PIL (Python Imaging Library, 1995) 의 friendly fork (2010~) 입니다. Python 진영의 사실상 표준 이미지 라이브러리입니다.
from PIL import Image
with Image.open(path) as im:
im.thumbnail((1200, 1200))
im.save('out.webp', quality=80)
장점은 Python 친화·풍부한 필터·간결한 API. 한계는 큰 이미지 처리 시 메모리·속도가 sharp/libvips 보다 약하다는 보고가 많습니다.
3. ImageMagick · GraphicsMagick · Skia
ImageMagick 은 1987 년 John Cristy 가 시작한 오래된 도구로, CLI (convert · magick) · 라이브러리 (MagickWand) 모두 제공합니다. 풍부한 형식 지원이 강점입니다. 한계로 메모리 사용량 · 보안 이슈 (과거 ImageTragick 등) 가 자주 지적됩니다. GraphicsMagick 은 2002 년 분기로, 안정성·성능을 강조했습니다.
Skia 는 Google 이 인수한 2D 그래픽 엔진 (2005) 입니다. Chrome · Android · Flutter 가 사용합니다. CanvasKit · skia-canvas 를 통해 Node 등에서도 쓸 수 있습니다. 강점은 캔버스·텍스트 렌더링·벡터 도형. 한계는 일반 이미지 처리 (크롭·리사이즈 일괄) 의 편의 기능이 적다는 점입니다.
| 도구 | 언어 | 엔진 | 강점 | 한계 |
|---|---|---|---|---|
| sharp | Node | libvips | 속도·메모리 | 일부 고급 필터는 ImageMagick 가 풍부 |
| Pillow | Python | 자체 | 친화성·필터 | 큰 이미지에서 느림 |
| ImageMagick | CLI · 다언어 | 자체 | 형식·필터 | 메모리·보안 |
| GraphicsMagick | CLI · 다언어 | ImageMagick fork | 안정성 | 커뮤니티 작음 |
| Skia | C++ · 바인딩 | 자체 | 캔버스·렌더링 | 일반 이미지 처리 도구로는 작음 |
4. 리사이즈 알고리즘
가장 많은 비용은 리사이즈입니다. 알고리즘 종류.
- Nearest neighbor — 가장 빠름, 품질 낮음. 픽셀 아트 외에는 부적합.
- Bilinear — 빠름, 부드러움.
- Bicubic — 품질 좋음, 표준.
- Lanczos — 고품질, 느림. 다운스케일에 자주 쓰임.
sharp 는 기본 Lanczos3 (kernel: lanczos3) 을 씁니다.
CSS 의 object-fit 과 비슷한 개념을 라이브러리가 제공합니다.
sharp(input).resize(400, 300, { fit: 'cover' }); // 비율 유지하며 채우고 잘라냄
sharp(input).resize(400, 300, { fit: 'contain' }); // 비율 유지하며 안에 맞춤
5. 형식 변환
| 형식 | 메모 |
|---|---|
| JPEG | 손실 압축의 사실상 표준. EXIF 보유. 알파 채널 없음. |
| PNG | 무손실. 알파 채널. 큰 사진에는 비효율. |
| WebP | Google, 2010. 손실·무손실·알파. 모던 브라우저 지원 광범위. |
| AVIF | AOMedia, 2019. WebP 보다 압축률 우수. 인코딩 비용 높음. |
| JPEG XL (jxl) | 2021 표준화. 점진적 채택. 일부 브라우저 미지원. |
| HEIF/HEIC | Apple 사진 기본. 라이선스·디코더 지원 이슈. |
서비스 자리에서는 사진은 WebP 또는 AVIF, 아이콘은 PNG/SVG, 호환성이 중요한 자리는 JPEG fallback 같은 조합이 흔합니다.
품질 인자 (0100) 가 있습니다. 일반 사진의 시각적 손실은 7585 사이에서 잘 안 보이는 편이라는 경험적 보고가 많습니다. AVIF 는 같은 시각 품질을 더 낮은 수치에서 달성합니다.
6. Build-time vs On-the-fly
Build-time — 빌드 단계에서 모든 변형 (여러 사이즈·형식) 을 생성해 정적 파일로 둡니다. CDN 이 그대로 서빙합니다.
- 장점: 런타임 비용 없음. 캐시 적중률 100%.
- 한계: 빌드 시간 증가. 사용자 업로드 콘텐츠에는 부적합.
On-the-fly — 요청 시점에 원본을 변환하고 결과를 캐싱합니다.
- 장점: 새 사이즈가 필요해도 즉시 대응.
- 한계: 첫 요청은 변환 비용. 캐시 외부 호출 차단·서명 URL 등 보안 고려.
하이브리드 — 자주 쓰는 사이즈 몇 개는 빌드 시점, 임의 사이즈는 on-the-fly. 운영에서 가장 자주 보이는 모양입니다.
7. CDN 이미지 변환
CDN 자체가 변환 기능을 제공하는 흐름이 자리 잡았습니다.
| 서비스 | 메모 |
|---|---|
| Cloudflare Images | 업로드 + 변환 + 글로벌 캐시. 가격 단순. |
| Vercel Image Optimization | Next.js next/image 와 결합. 동적 변환. |
| Imgix | 변환 서비스의 원조. 강력한 URL 파라미터. |
| Cloudinary | 변환·DAM·AI 변환 묶음. |
| AWS CloudFront + Lambda@Edge | 직접 구성하는 자리. |
자체 sharp 서버 vs CDN 의 결정 요인은 트래픽·비용·운영 부담·보안 정책입니다.
8. EXIF 와 Color profile
촬영 정보 (GPS · 카메라 · 시각) 가 EXIF 에 들어 있습니다. 사용자 사진을 그대로 외부에 노출하면 위치 노출 위험이 있습니다. 라이브러리 기본은 EXIF 보존인 경우가 많아 명시적으로 제거하는 편이 안전합니다.
sharp(input).rotate().withMetadata({ orientation: undefined }).toBuffer();
회전 정보 (orientation) 만 적용하고 나머지를 제거하는 흐름이 무난합니다.
iPhone 사진은 P3, 일부 카메라는 Adobe RGB 같은 비표준 색공간을 가질 수 있습니다. 웹 표시용은 sRGB 로 변환하는 편이 색이 안정됩니다.
sharp(input).toColorspace('srgb');
9. 점진 디코딩과 Responsive srcset
JPEG 의 progressive · WebP 의 incremental 옵션은 첫 화면 픽셀이 일찍 보이게 합니다. 같은 비용에 체감 속도 차이가 있습니다.
같은 이미지를 여러 해상도로 만들어 <img srcset="..."> 로 전달합니다. 브라우저가 자기 기기에 맞는 사이즈를 고릅니다.
10. 자주 걸리는 자리
메모리 폭발 — 매우 큰 이미지 (수십 MP) 를 한꺼번에 디코드하면 OOM 입니다. sharp 의 스트리밍·limitInputPixels 옵션, 입력 크기 사전 검증.
EXIF 회전 누락 — 가로/세로가 EXIF 의 orientation 에 따라 다릅니다. 자동 회전 호출을 빠뜨리면 가로 사진이 세로로 표시됩니다.
알파 채널 손실 — PNG → JPEG 변환은 투명 영역이 검정·흰색으로 채워집니다. 의도치 않은 색 변화.
임의 입력의 보안 — 업로드 이미지를 변환할 때 디코더 취약점이 노출 표면이 됩니다. 입력 크기 제한 · 형식 화이트리스트 · 격리 환경.
CPU 사용량 — AVIF 인코딩은 비쌉니다. 대량 변환 자리에서는 워커·큐 분리.
캐시 키 설계 누락 — on-the-fly 변환의 캐시 키가 잘못되면 사용자 A 의 변형이 B 에게 전달되는 사고가 됩니다. 사이즈·품질·형식·서명을 키에 포함합니다.
재생성 성능 — 캐시 만료 후 동일 자원을 여러 클라이언트가 동시에 요청하면 변환이 중복 실행됩니다. 분산 락 또는 request coalescing 이 필요합니다.
하고픈 말
이미지는 입출력이 무겁고 형식 선택지가 많아 처음에는 부담이 큽니다. 그래도 sharp + libvips 한 줄로 90% 이상의 자리가 해결됩니다. 품질·메모리·보안 셋의 균형이 잡히면 운영이 단순해집니다.
Next
- backup-restore
- vitest-philosophy
sharp 공식 문서 · libvips 공식 · Pillow 문서 · WebP 가이드 · AVIF FAQ · Cloudflare Images · Next.js Image Optimization 를 참고합니다.