codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
노트›data

이미지 파이프라인

2026-04-28 게시· 2026-05-18 갱신·0회 조회

이미지 파이프라인 — 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. 업로드 받기 — 검증과 표준 파이프라인

사용자 업로드를 받는 서버 자리는 흐름이 정해져 있습니다 — 받기 → 검증 → 변환 → 저장. 변환 자체는 sharp 한 줄이지만, 검증을 건너뛰면 보안 표면이 그대로 노출됩니다.

const buf = await sharp(input)
  .rotate()                                       // EXIF 회전만 적용
  .resize(maxW, maxH, { fit: 'inside', withoutEnlargement: true })
  .webp({ quality })
  .toBuffer();

fit: 'inside' 는 비율을 유지하며 박스 안에 맞추고, withoutEnlargement 는 원본보다 키우지 않습니다. 작은 이미지를 억지로 확대해 흐려지는 사고를 막습니다.

MIME 스니핑 — 확장자와 클라이언트가 보낸 Content-Type 은 둘 다 위조 가능합니다. 실제 형식은 파일 앞부분의 매직 바이트로 판별합니다. file-type 라이브러리나 sharp 의 metadata() 가 읽은 format 을 신뢰하고, 화이트리스트 (jpeg·png·webp 등) 밖이면 거부합니다. evil.exe 를 photo.jpg 로 올리는 시도가 여기서 걸립니다.

이미지 폭탄 방어 — 작은 파일이 디코드 후 수십억 픽셀로 펼쳐지는 압축 폭탄이 있습니다. sharp 는 sharp(input, { limitInputPixels: 24_000_000 }) 로 픽셀 상한을 둘 수 있고, 상한 초과 시 디코드 전에 던집니다. 그 앞 단계로 Content-Length 를 먼저 확인해 본문을 읽기 전에 과대 요청을 거부합니다.

SVG 주의 — SVG 는 XML 이라 <script>·외부 참조를 품을 수 있는 XSS 벡터입니다. 업로드 이미지로는 거부하거나, 꼭 받아야 하면 sharp 로 래스터화 (PNG/WebP 로 변환) 한 뒤 저장합니다. 원본 SVG 를 그대로 서빙하지 않습니다.

업로드 rate-limit — 변환은 CPU·메모리를 쓰므로 업로드 자체가 자원 고갈 공격이 됩니다. IP·사용자 단위로 분당 업로드 횟수에 상한을 둡니다.

파일명 랜덤화 — 사용자가 준 파일명을 그대로 쓰면 경로 조작 (../../etc/passwd)·덮어쓰기·추측 위험이 있습니다. {uuid}.webp 처럼 서버가 생성한 이름으로 저장하고, 원래 이름이 필요하면 메타데이터 컬럼에만 둡니다.

Node 런타임 — sharp 는 네이티브 바인딩 (libvips) 에 의존합니다. Edge 런타임에서는 동작하지 않으므로 업로드·변환 라우트는 Node 런타임으로 고정합니다.

사이즈 프리셋 — 한 원본에서 용도별 변형을 미리 정의해 두면 호출부가 단순해집니다. 예: 썸네일 (작은 max-dimension + 낮은 quality) · 카드 · 상세 · 원본 보존. 각 프리셋이 자기 최대 변·품질을 갖습니다. 리사이즈 알고리즘·fit 개념은 §4 를 참고합니다.

이 절은 "업로드를 어떻게 안전하게 처리하나" 의 긍정형 흐름입니다. 같은 주제의 실패 사례 (메모리 폭발·EXIF 회전 누락·임의 입력 디코더 취약점·캐시 키) 는 바로 다음 §11 에 모았습니다.

11. 자주 걸리는 자리

메모리 폭발 — 매우 큰 이미지 (수십 MP) 를 한꺼번에 디코드하면 OOM 입니다. sharp 의 스트리밍·limitInputPixels 옵션, 입력 크기 사전 검증 (§10 의 이미지 폭탄 방어).

EXIF 회전 누락 — 가로/세로가 EXIF 의 orientation 에 따라 다릅니다. 자동 회전 호출을 빠뜨리면 가로 사진이 세로로 표시됩니다.

알파 채널 손실 — PNG → JPEG 변환은 투명 영역이 검정·흰색으로 채워집니다. 의도치 않은 색 변화.

임의 입력의 보안 — 업로드 이미지를 변환할 때 디코더 취약점이 노출 표면이 됩니다. 입력 크기 제한 · 형식 화이트리스트 · 격리 환경. 실제 형식 판별·SVG 거부는 §10 참고.

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 를 참고합니다.

data 카테고리의 다른 글

카테고리 전체 보기 →
  • DB 시드 소스를 코드 트리 안에 두지 않는다
  • Supabase Storage — 파일 업로드와 권한
  • Kafka 실무 — 토픽 설계와 메시지 흐름
  • 여러 PostgreSQL 풀 한 앱에서 관리하기
  • 백업과 복구
  • 푸시 알림 — FCM 과 Web Push