← 목록으로

CDN 비용 폭탄 멈춰! DEV가 bunny.net으로 이미지 파이프라인을 혁신하고 아낀 엄청난 비용

2026. 6. 18.

CDN 비용 폭탄 멈춰! DEV가 bunny.net으로 이미지 파이프라인을 혁신하고 아낀 엄청난 비용

안녕하세요, 10년 차 IT 실무자이자 테크 블로거 벤입니다.

만약 여러분이 DEV와 저희의 오픈소스 프로젝트 Forem의 여정을 지켜봐 오셨다면, 저희가 웹 성능에 얼마나 집착해왔는지 잘 아실 겁니다. 오래전 코드랜드(Codeland)에서 저는 "웹사이트를 일본에서 바이럴 될 정도로 빠르게 만드는 방법"이라는 주제로 강연한 적이 있습니다. 당시 엣지 캐싱의 메커니즘과 페이지 로드를 거의 즉각적으로 유지하는 비결에 대해 깊이 파고들었죠.

{% embed https://www.youtube.com/watch?app=desktop&v=lGQtUDMStnI %}

저희의 핵심 철학은 언제나 단순했습니다. 아키텍처는 최대한 간결하게 유지하고, 엣지에서 적극적으로 캐싱하며, Rails 모놀리식(Forem)은 본연의 역할에 집중하도록 하는 것입니다. 수년간 Fastly는 HTML 엣지 캐싱을 기가 막히게 처리해줬습니다. 대부분의 페이지 요청은 저희 Puma 서버에 닿을 필요조차 없어, RAM 사용량을 낮게 유지하고 응답 시간을 밀리초 단위로 끌어내렸죠. 지금도 DEV의 모든 문서 콘텐츠는 Fastly를 통해 제공됩니다. 저도 실무에서 웹 성능을 최적화할 때 항상 HTML과 정적 파일 캐싱에 먼저 집중하곤 합니다. Fastly 같은 엣지 캐싱 솔루션이 얼마나 강력한지는 두말할 필요 없죠.

하지만 정적 HTML을 엣지 캐싱하는 것은 잘 알려진 문제지만, 사용자가 업로드하는 미디어는 완전히 다른 문제입니다.

DEV가 성장하면서 우리는 이미지의 홍수에 빠져들었습니다. 모든 게시물 커버, 사용자 아바타, 댓글 스크린샷, 챌린지 배너는 커뮤니티가 업로드한 고해상도 자산입니다. 수십억 개의 이미지를 전 세계에 서비스하면서도 페이지 크기를 가볍게 유지하려다 보니, 결국 조용한 스케일링 함정에 빠지고 말았습니다. 뒤죽박죽 얽힌 멀티 CDN 미디어 파이프라인, 막대한 클라우드 송신(egress) 비용, 그리고 눈덩이처럼 불어나는 월별 청구서가 저희를 기다리고 있었죠.

이 글은 우리가 어떻게 멀티 CDN의 혼란을 끝내고, 미디어 아키텍처를 단순화했으며, 엄청난 비용을 절감하고, bunny.net의 엣지 스크립팅을 활용하여 더 스마트하고 빠른 이미지 제공 파이프라인을 구축했는지에 대한 이야기입니다.

멀티 CDN의 혼돈과 냉혹한 청구서 현실

우리가 왜 전환을 결정했는지 이해하려면, 기존의 이미지 파이프라인이 어떤 모습이었는지 살펴봐야 합니다.

오랜 기간 동안 저희의 미디어 스택은 일종의 누더기였습니다. 플랫폼의 여러 부분을 각기 다른 CDN이 처리하고 있었고, 동적 리사이징을 위한 이미지 프록싱 서비스, 그리고 AWS S3와 같은 클라우드 스토리지에 원본 자산이 보관되어 있었습니다.

사용자가 10MB JPEG 파일을 아티클 커버로 업로드하면, 저희 Rails 앱은 이를 수십 가지 다른 크기로 미리 처리하지 않습니다. 대신, 즉석 이미지 변환에 의존하죠. 이론적으로는 훌륭합니다. 브라우저가 image.jpg?width=800을 요청하면, 동적 이미지 최적화 도구가 이를 리사이징하고 WebP 또는 AVIF로 변환하여 제공하는 방식입니다.

하지만 실제 대규모 환경에서 이러한 설정의 경제성과 메커니즘은 매우 가혹합니다. 특히 현대 웹 트래픽의 현실을 고려하면 더욱 그렇습니다.

  • 스크래퍼 & 트래픽 세금: 오픈 웹에서는 사람 사용자만 서비스하는 것이 아닙니다. RSS 리더, 검색 크롤러, 고빈도 스크래퍼의 끊임없는 공격을 받죠. 기존 설정은 이러한 트래픽을 현명하게 처리할 만큼 똑똑하지 못했습니다. 공격적인 스크래퍼들이 캐시되지 않은 다양한 이미지 변형을 요청하거나 표준 쿼리 파라미터를 우회하면, 파이프라인은 자산을 반복적으로 다시 가져오고 다시 처리해야 했고, 컴퓨팅 비용은 천문학적으로 치솟았습니다.
  • 송신(Egress) 비용: 이미지 최적화 도구가 캐시 미스 때문에 (종종 위에서 언급한 트래픽 패턴으로 인해) 클라우드 스토리지 원본에서 원본 이미지를 가져와야 할 때마다, 우리는 엄청난 클라우드 송신 비용을 지불해야 했습니다.
  • 변환 비용: 많은 프리미엄 이미지 CDN은 "처리된 이미지 1000개당" 요금을 부과하거나 복잡하고 징벌적인 사용 등급을 적용합니다. 수백만 개의 활성 게시물과 수백 개의 맞춤 크기 아바타가 있는 피드를 스크롤하는 사용자들이 있다면, 이러한 변환 횟수는 기하급수적으로 증가합니다.
  • 멀티 CDN 마찰: HTML 캐싱과 이미지 전송/최적화를 위한 별도의 제공업체를 운영하는 것은 엄청난 운영 오버헤드를 야기했습니다. 복잡한 헤더 구성, CORS 문제, 그리고 라우팅 규칙이 여러 YAML 파일과 대시보드에 흩어져 있었죠. 수년 간 여러 CDN을 조합해 쓰면서 저도 비슷한 헤더 충돌이나 CORS 문제로 골머리를 앓았던 경험이 있습니다. 복잡한 멀티 CDN 환경은 예상치 못한 곳에서 발목을 잡곤 하죠.

미디어 비용은 눈덩이처럼 불어났고, 믿을 수 없을 만큼 비쌌으며, 예측 불가능한 트래픽 급증을 파이프라인이 왜 현명하게 처리하지 못하는지 디버깅하는 데 너무 많은 시간을 썼습니다. 빠르고, 안정적이며, 고도로 구성 가능하고, 무엇보다 경제적으로 지속 가능한 해결책이 필요했습니다.

우리가 bunny.net으로 갈아탄 이유

사실 저는 개인 프로젝트 여러 개에서 수년 동안 bunny.net을 사용해왔습니다. 간단한 사이드 앱을 빠르게 만들거나 새로운 개념을 테스트할 때마다 항상 bunny.net으로 돌아오곤 했습니다. 정말 잘 설계된 플랫폼이자 합리적이고 직관적인 제품들을 제공했기 때문이죠. 기존 클라우드 벤더들의 복잡하고 비대한 엔터프라이즈 기능을 벗어나, 개발자에게 필요한 깔끔한 인체공학적 기능을 제공해서 그냥 '잘 작동'합니다. 이러한 직접적인 경험 덕분에 저는 DEV와 함께 원활하게 확장할 수 있는 신뢰할 수 있는 플랫폼이라는 것을 확신했습니다.

Forem에 bunny.net을 도입하기로 결정한 결정적인 이유는 단순히 대역폭 절감 효과(프리미엄 엔터프라이즈 CDN 요금의 극히 일부로 대역폭 비용을 대폭 줄인 것은 엄청난 성과였습니다) 때문만은 아니었습니다. 그보다는 **Bunny Optimizer**와 **Edge Scripting**의 조합을 통해 저희의 고유한 아키텍처적 문제점들을 얼마나 아름답게 해결해줬는지에 있었습니다.

1. Bunny Optimizer와 Perma-Cache

Bunny Optimizer는 완전 관리형 동적 이미지 변환 API 역할을 합니다. 간단한 URL 쿼리 파라미터(예: ?width=600&height=300&crop=1:1)를 붙여 쉽게 연결할 수 있었고, Optimizer가 즉석에서 리사이징, 크롭핑, 자동 압축을 처리하도록 했습니다. 브라우저의 Accept 헤더에 따라 WebP나 AVIF 같은 차세대 포맷을 자동으로 협상하여, 육안으로 품질 저하 없이 파일 크기를 최대 80%까지 줄여줍니다.

하지만 진정한 마법의 비결이자 스크래퍼 트래픽에 대한 우리의 궁극적인 무기는 바로 Perma-Cache입니다.

일반적으로 CDN 엣지 서버가 자주 액세스되지 않는 이미지 변형을 삭제하면, 다음 요청은 원본(클라우드 스토리지)으로 다시 이동하여 이를 가져오고 다시 최적화해야 합니다. 이 과정에서 송신 비용이 다시 발생하죠. Perma-Cache는 최적화된 이미지 변형을 Bunny Storage에 영구적으로 복제하여 이 문제를 해결합니다.

이미지가 한 번 처리되면, 엣지에 영구적으로 저장됩니다. AWS 원본에 다시 접근할 필요가 없어, 변동성이 심한 트래픽으로부터 백엔드를 보호하고 클라우드 스토리지 송신 비용을 하룻밤 사이에 사실상 없애버렸습니다.

2. Edge Scripting: TypeScript-Native 제어

Bunny Optimizer가 이미지를 리사이징하는 강력한 기능을 제공했지만, 이미지를 제공하는 방식에 대한 정교한 제어가 필요했습니다. 복잡한 URL 빌딩 로직으로 Rails 뷰를 오염시키고 싶지 않았고, 사용자(또는 봇)가 거대한 원본 이미지를 다운로드하는 것을 막고 싶었습니다.

이때 Edge Scripting이 등장했습니다.

Deno와 V8을 기반으로 구축된 Edge Scripting은 JavaScript와 TypeScript를 엣지에서 직접 실행하여, 밀리초 단위로 실행되는 경량의 타입 안정적인 미들웨어를 작성할 수 있게 해줍니다. 이는 사용자 정의 이미지 프록시나 복잡한 Rails 컨트롤러 라우팅의 필요성을 완전히 대체했습니다.


내부 들여다보기: Forem의 플러그형 Images::Optimizer 서비스

Forem의 코드베이스를 살펴보면, 저희가 이미지 파이프라인을 항상 플러그형으로 설계했음을 알 수 있을 겁니다. 특정 CDN의 쿼리 파라미터를 사용하도록 뷰를 하드코딩하고 싶지 않았습니다. 템플릿이 게시물 커버 이미지를 렌더링하려면, 통합 헬퍼를 호출하여 Images::Optimizer 서비스에 위임하는 방식입니다.

# app/views/layouts/application.html.erb
<%= Images::Optimizer.call(Settings::General.favicon_url, width: 32) %>

app/services/images/optimizer.rb 내부에서는 간단한 전략 패턴을 사용합니다. Optimizer 클래스는 표준화된 파라미터(예: width, height, fit, gravity)를 활성 CDN 제공업체에 필요한 특정 URL 형식으로 매핑하는 라우터 역할을 합니다.

역사적으로 Forem은 Fastly, Cloudflare, Cloudinary와 같은 제공업체를 지원했으며, bunny.net을 추가하는 것은 매우 간단했습니다. 저희 Rails 서비스가 이러한 멀티 CDN 라우팅을 어떻게 처리하는지 살펴보겠습니다.

# app/services/images/optimizer.rb
module Images
  class Optimizer
    def self.call(url, options = {})
      return url if url.blank?
      
      # 환경 구성에 따라 제공업체 전략을 선택합니다.
      case provider
      when :bunny
        BunnyProvider.call(url, options)
      when :cloudflare
        CloudflareProvider.call(url, options)
      when :fastly
        FastlyProvider.call(url, options)
      else
        url
      end
    end

    def self.provider
      ENV.fetch("IMAGE_OPTIMIZATION_PROVIDER", "bunny").to_sym
    end
  end
end

각 제공업체는 자체 URL 재작성 전략을 구현합니다. 예를 들어, 저희 bunny.net 제공업체는 Bunny Optimizer가 파싱하는 표준 쿼리 문자열을 간단히 구축합니다.

# app/services/images/bunny_provider.rb
module Images
  class BunnyProvider
    def self.call(url, options = {})
      uri = URI.parse(url)
      query_params = []
      query_params << "width=#{options[:width]}" if options[:width]
      query_params << "height=#{options[:height]}" if options[:height]
      query_params << "crop=#{options[:crop]}" if options[:crop]
      query_params << "auto=format"
      
      uri.query = [uri.query, query_params.join("&")].compact.join("&")
      uri.to_s
    end
  end
end

이러한 분리된 아키텍처는 Forem과 같은 오픈소스 프로젝트에 매우 유용합니다. 각자 자체 호스팅하는 커뮤니티는 IMAGE_OPTIMIZATION_PROVIDER 환경 변수만 변경하여 원하는 CDN을 구성할 수 있죠.

하지만 Rails 앱이 이러한 최적화된 URL을 생성하는 동안, 흥미로운 운영상의 과제에 직면했습니다. 템플릿이 width 파라미터를 전달하는 것을 잊거나, 레거시 게시물에 원본 외부 URL이 포함되어 있으면 어떻게 될까요?

Rails 측의 URL 빌딩에만 의존한다면, 모든 대체 또는 파싱되지 않은 URL은 여전히 무겁고 최적화되지 않은 이미지 로드를 유발할 것입니다. 바로 이때 엣지 스크립팅 전략이 네트워크 경계에서 전역 안전망 역할을 하기 위해 개입했습니다.


심층 분석: 엣지에서의 스마트 다운사이징

Edge Scripting을 사용하면 CDN 레이어에서 이미지 요청을 바로 가로채고, 요청이 옵티마이저나 스토리지에 도달하기 전에 사용자 정의 비즈니스 로직을 적용할 수 있습니다.

예를 들어, 사용자 프로필 아바타와 피드 썸네일은 원본 업로드나 요청된 쿼리 파라미터와 상관없이 실제 필요한 크기 이상으로 제공되지 않도록 하고 싶었습니다. 클라이언트가 원본의 최적화되지 않은 아바타 URL을 요청하면, 저희 엣지 스크립트가 자동으로 이를 가로채고, 컨텍스트를 확인한 다음, 요청을 재작성하여 엄격한 최대 너비를 적용하고 WebP 압축을 적용합니다.

다음은 간소화된 예시입니다.

// bunny.net 엣지에서 실행되는 경량 미들웨어 스크립트
export default async function handleRequest(request: Request) {
  const url = new URL(request.url);
  
  // 사용자 업로드 경로로 들어오는 요청을 가로챕니다.
  if (url.pathname.startsWith('/uploads/')) {
    const isAvatar = url.pathname.includes('/avatars/');
    const isThumbnail = url.pathname.includes('/thumbnails/');
    
    // 요청에 이미 최적화 파라미터가 있는지 확인합니다.
    const hasWidth = url.searchParams.has('width');
    
    if (isAvatar && !hasWidth) {
      // 모든 아바타 요청을 최대 150px로 강제 다운사이징
      url.searchParams.set('width', '150');
      url.searchParams.set('height', '150');
      url.searchParams.set('crop', '1:1');
    } else if (isThumbnail && !hasWidth) {
      // 썸네일에 모바일 친화적인 엄격한 한도 적용
      url.searchParams.set('width', '400');
    }
    
    // 차세대 포맷(WebP/AVIF) 자동 협상이 활성화되도록 합니다.
    url.searchParams.set('auto', 'format');
    
    // bunny.net의 CDN 파이프라인에서 최적화된 자산을 가져옵니다.
    return fetch(url.toString(), request);
  }
  
  return fetch(request);
}

이것은 엄청나게 강력한 기능입니다. 이미지 전송의 "계산적 사고"를 전적으로 엣지로 넘겨버리기 때문입니다. 저희 Rails 애플리케이션은 반응형 이미지 분기점(breakpoint)을 추적하거나 무겁고 복잡한 마크업을 생성할 필요가 없습니다. 우리는 단순히 논리적인 자산 URL을 요청하고, 엣지 스크립트가 클라이언트 헤더와 컨텍스트에 따라 나머지를 동적으로 처리합니다. 사실 이런 복잡한 이미지 리사이징 로직을 애플리케이션 단에서 모두 처리하려면 코드도 지저분해지고 서버 부하도 상당합니다. 엣지에서 처리하는 것이 훨씬 효율적이라는 것을 저도 비슷한 프로젝트에서 절실히 느꼈죠.

더욱 좋은 점은, 이 모든 것을 표준 개발 워크플로우에 통합했다는 것입니다. GitHub Actions와 세분화된 개인 액세스 토큰을 결합하여 이러한 엣지 스크립트를 자동으로 관리하고 배포합니다. 최적화 규칙을 조정하거나 새로운 레이아웃(예: 빌보드 이미지 최적화 또는 챌린지 페이지 해상도 조정)에 대한 지원을 추가하고 싶을 때, 커밋을 푸시하기만 하면 CI가 실행되고 새로운 엣지 로직이 몇 초 만에 전 세계적으로 반영됩니다.


우아한 실패 처리: 깨진 이미지 대신 스마트 대체 이미지 제공

라우팅과 크기 조절 외에도, 엣지에서 코드를 실행하는 것은 사용자 경험(UX) 측면에서 엄청난 승리를 가져다주었습니다. 바로 누락되거나 깨진 자산을 우아하게 처리할 수 있는 능력입니다.

방대한 커뮤니티 생태계에서는 엣지 케이스가 발생하기 마련입니다. 사용자가 링크했던 외부 이미지를 삭제하거나, 오래된 업로드 경로가 마이그레이션 중에 깨지거나, 잘못된 형식의 요청이 통과될 수 있습니다. 전통적으로 이미지가 로드되지 않거나 404/500 오류를 반환하면, 브라우저는 레이아웃을 망가뜨리고 전체 사이트가 고장 난 것처럼 보이게 하는 거슬리고 보기 흉한 "깨진 이미지" 아이콘을 띄웁니다.

Edge Scripting을 사용하면 이러한 실패를 비행 중에 포착할 수 있습니다. 원본 서버나 스토리지가 오류 상태 코드를 반환하면, 엣지 스크립트는 응답을 가로채고 원활하게 재작성하여 "이미지를 사용할 수 없습니다"라고 표시하거나 UI 테마와 일치하는 아름답게 스타일링된 사용자 정의 플레이스홀더 이미지를 제공합니다.

Rails 애플리케이션의 모든 <img> 태그에 복잡하고 무거운 JavaScript 이벤트 리스너(onError)를 작성하는 대신, 네트워크 레이어에서 기본적으로 처리하는 것입니다. 애플리케이션 레이어는 대체 로직에 대해 전혀 생각할 필요가 없으며, 사용자들은 어떤 일이 벌어지더라도 일관되고 끊김 없는 시각적 경험을 얻게 됩니다.

향후 고려사항

이미지 파이프라인을 안정화하고 최적화하는 것은 단지 첫 번째 단계였습니다. DEV와 Forem이 계속 발전할 방식을 내다볼 때, 비디오는 다음 논리적인 지평선입니다.

비디오 전송은 악명이 높을 정도로 복잡합니다. 적응형 비트레이트 스트리밍(HLS/DASH), 다중 해상도 트랜스코딩, 전문 스토리지, 최적화된 비디오 플레이어가 필요하죠. 레거시 아키텍처에서는 일반적으로 또 다른 파편화된 비싼 타사 비디오 프로세서와 복잡한 통합을 구축해야 합니다.

bunny.net의 미디어 인프라에서 성공을 거두었기에, 비디오에 대한 우리의 생각에서 bunny.net은 확실한 첫 번째 선택지입니다. 그들의 통합 플랫폼 접근 방식은 이미지 옵티마이저와 동일한 합리적이고 개발자 우선 철학을 가진 제품들을 통해 비디오 스트리밍으로 직접 확장됩니다. 예측 불가능한 데이터 전송 비용 없이도 그들의 인프라가 예측 가능하게 확장될 것이라고 믿을 수 있기 때문에, 차세대 리치 미디어를 처리하기 위해 엣지 아키텍처를 확장하는 것은 위협적인 인프라 전면 개편이 아니라 자연스러운 발전처럼 느껴집니다.


마무리 생각

개발자로서 우리는 종종 데이터베이스 쿼리를 최적화하거나, Ruby 코드를 리팩토링하거나, 서버 구성을 미세 조정하는 데 집중합니다. 하지만 때로는 가장 큰 성과가 네트워크 탭 바로 거기에 숨어 있습니다.

송신 비용과 비대해진 미디어 전송은 성장하는 플랫폼에 대한 '조용한 세금'입니다. bunny.net과 같은 엣지 네이티브의 개발자 친화적인 플랫폼으로 전환함으로써, 우리는 아키텍처를 단순화하고, 페이지 로드 속도를 높이며, 그 과정에서 많은 돈을 절약할 수 있었습니다.

만약 여러분이 미디어 중심의 플랫폼을 운영하거나 Forem과 같은 오픈소스 커뮤니티 소프트웨어를 구축하고 있다면, 자신을 위해 한 가지 부탁드립니다. CDN 청구서를 자세히 살펴보고, 클라우드 스토리지 송신 비용을 확인한 다음, 여러분과 함께 성장할 수 있도록 구축된 플랫폼으로 일부 부담을 덜어낼 수 있는지 알아보세요. 여러분의 예산(그리고 사용자들)이 감사할 것입니다.

해피 코딩 ❤️


원문: https://dev.to/devteam/how-we-saved-big-and-simplified-our-image-pipeline-adopting-bunnynet-on-dev-3d53 수집일: 2026-06-18 00:29:40