Django 이미지 마이그레이션 마스터하기: 로컬에서 S3, CDN까지, 그리고 그 이상!

발행: (2026년 1월 12일 오후 06:54 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

해당 링크에 포함된 본문을 번역하려면, 번역하고자 하는 텍스트 전체를 제공해 주시기 바랍니다. 현재는 링크만 제공되어 있어 실제 내용이 없으므로 번역을 진행할 수 없습니다. 텍스트를 복사해서 붙여 주시면 바로 한국어로 번역해 드리겠습니다.

문제: “미디어 혼란”

모든 개발자는 결국 “미디어 혼란”에 직면합니다. 로컬 파일 업로드로 시작해 정리되지 않은 S3 버킷으로 이동하고, 마지막으로 ImageKit이나 CloudFront 같은 CDN을 통합하려 할 때 벽에 부딪히게 됩니다.

흔히 겪는 문제점은 다음과 같습니다:

  • 이름 일관성 부족 – 일부 파일은 slug_icon.png이고, 다른 파일은 slug.ico입니다.
  • 로컬 스테이징 – 중첩된 /downloads 폴더에 10 GB 규모의 수집된 이미지가 있으며, 이를 즉시 S3에 올려야 합니다.
  • 데이터베이스 비동기 – Django ImageField는 파일이 존재한다고 생각하지만, S3는 404를 반환합니다.
  • CDN 제한 – ImageKit 무료 플랜은 외부 S3 소스를 하나만 허용하지만, 데이터가 흩어져 있습니다.

이 가이드에서는 Global Publication Archive (샘플 프로젝트)용 자산을 마이그레이션하고, 연결하며, 표준화하는 견고한 시스템을 구축합니다. 저장을 위해 Primary 버킷을, CDN 전달을 위해 Public 버킷을 사용할 것입니다.

데이터 흐름 개요

Local machine → Primary S3 bucket → Public CDN bucket → User’s browser

로컬 파일 구조는 거의 정돈되어 있지 않습니다. 예시에서는 출판사의 아이콘이 다음 위치에 있을 수 있습니다:

downloads/slug/google_favicon/google_icon.png

도전 과제: 디렉터리를 파일처럼 열기

If you try to open the google_favicon folder directly, Python raises:

[Errno 21] Is a directory

로직

  1. 폴더가 존재하는지 확인합니다.
  2. **glob**를 사용하여 중첩된 경로 안의 실제 이미지 파일을 찾습니다.
  3. Django의 File 래퍼를 이용해 S3에 업로드하면 스토리지 백엔드가 자동으로 처리됩니다.
  4. 파일이 이미 S3에 있다면, 다운로드/재업로드를 건너뛰고 ImageField 이름 속성을 업데이트하여 단순히 “링크”합니다.

최적화

  • s3.head_object를 사용합니다 – 전체 GET 요청보다 훨씬 빠르고 비용이 적게 드는 메타데이터 호출입니다.
  • 예측 가능한 URL을 가진 CDN을 통해 이미지를 제공합니다. 예: images.com/icons/nytimes.png (대신 images.com/icons/nytimes_icon_v2_final.png).

S3 주의점

S3에는 기본적인 “rename” 명령이 없습니다. 객체를 새 키로 Copy한 뒤 기존 객체를 Delete해야 합니다.

중요한 수정

현대 S3 버킷은 종종 ACL을 비활성화합니다. AccessControlListNotSupported 오류가 발생하면 Boto3 호출에서 ACL='public-read'를 제거하고 대신 버킷 정책을 사용하세요.


모델 및 설정 구성

# models.py
class PublicationSource(models.Model):
    slug = models.SlugField(unique=True)
    masthead = models.ImageField(upload_to="mastheads/", null=True)
    icon = models.ImageField(upload_to="icons/", null=True)
# settings.py
INSTALLED_APPS = ['storages']
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_STORAGE_BUCKET_NAME = 'my-primary-bucket'

ImageKit (Free)에서는 하나의 S3 소스만 허용하므로, 기본 버킷을 ImageKit이 감시하는 전용 Sector 버킷으로 동기화합니다:

aws s3 sync s3://primary-bucket s3://cdn-delivery-bucket --delete

중첩된 로컬 파일 처리

스크래퍼가 아이콘을 중첩된 폴더 깊숙이 저장할 때(예: downloads/slug/google_favicon/icon.png), 일반 루프는 IsADirectoryError를 발생시킵니다. 우리 업로드 스크립트는 glob와 재귀 경로 검사를 사용하여 깊이에 관계없이 올바른 자산을 찾습니다. 또한 마스터 CSV의 audit_status를 확인하여 검증된 콘텐츠만 버킷에 전송되도록 합니다.

🔗 스크립트 보기: Local to S3 Uploader

기존 S3 파일을 Django에 연결하기

때때로 파일이 이미 S3에 존재하지만 데이터베이스는 이를 인식하지 못합니다. 다시 업로드하면 대역폭과 시간이 낭비됩니다. 링크 스크립트는 “드라이런” 호환 검사를 수행합니다: Boto3의 head_object를 사용해 S3에 ping을 보냅니다. 파일이 존재하면 Django ImageField 경로를 직접 업데이트합니다—실제 파일을 건드리지 않고 프로덕션 데이터베이스를 동기화하는 데 필수적입니다.

🔗 스크립트 보기: S3 to Django Linker


S3 키 표준화 및 이름 변경

레거시 명명 규칙(예: slug_fav.ico)은 깔끔한 CDN URL을 방해합니다. ImageKit이나 CloudFront를 사용할 경우 favicons/slug.png와 같은 표준을 원합니다.

스크립트는 필요한 Copy‑Delete 사이클을 처리하고, 불필요한 ACL 헤더를 생략함으로써 AccessControlListNotSupported 오류를 우아하게 우회합니다.

🔗 스크립트 보기: S3 Standardizer & Renamer

ImageKit 무료 티어를 위한 개인 버킷 동기화

ImageKit 무료 티어를 사용한다면 외부 소스 연결이 하나뿐일 가능성이 높습니다. 자산이 개인 “Admin” 버킷에 있을 경우, ImageKit이 감시하는 공개 “Sector” 버킷으로 동기화하세요:

aws s3 sync s3://my-private-admin-bucket s3://my-public-sector-bucket --delete

결론

미디어 마이그레이션은 단순히 바이트를 옮기는 것이 아니라 데이터베이스와 스토리지 간의 무결성을 유지하는 것입니다. bucket‑first 스캔 방식을 채택하고, 중첩 디렉터리를 처리하며, 위의 스크립트를 사용하면 혼란스러운 로컬 폴더를 전문적인 CDN‑기반 미디어 라이브러리로 변환할 수 있습니다. 일관되고 표준화된 S3 키는 Django ImageField가 항상 유효한 자산을 가리키도록 보장하여 프런트‑엔드가 더 빨라지고 백엔드 유지 관리가 쉬워집니다.

Back to Blog

관련 글

더 보기 »