Skip to content

bugoverdose/MyS3

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MyS3: 이미지 호스팅 서버

배경

  • 원래 해당 프로젝트는 우테코에서 S3 사용이 금지됨에 따라 만들어본 간단한 토이 프로젝트였습니다.
  • 학습 목적으로 정적 데이터를 제공하는 서버측에서 할 수 있는 성능 개선 기법들을 몇 가지 적용해보았습니다.

API 개요

  • 이미지 조회 API : GET /images/{uploadPath}/{fileName}
  • 이미지 저장 API : POST /api/images/{uploadPath}
    • image 필드의 값으로 업로드할 이미지를 추가해줘야 합니다.
    • (선택) 파라미터 값으로 fileName, version 설정할 수 있습니다.
      • filename을 명시하지 않으면 업로드하는 파일의 이름을 그대로 활용하되, 확장자만 webp로 변경됩니다.
      • version은 파일을 filename-version.webp 형식으로 저장해줍니다. 버전에 따른 URL 변경을 통한 캐쉬 버스팅에 활용될 수 있습니다.
  • 이미지 삭제 API : DELETE /api/images/{uploadPath}/{fileName}

캐쉬 기반 성능 개선

1) ETag 헤더

  • ETag/If-None-Match 헤더를 통해 웹 사이트의 성능 개선을 시도하였습니다.
  • 특정 클라이언트의 캐쉬 저장소에 저장되어있던 파일을 서버로 다시 요청한 경우, 서버에서는 etag 값의 비교를 통해 캐쉬 저장소의 데이터와 서버의 데이터가 동일한지를 확인합니다.
  • 만일 클라이언트가 서버에서 전송하려는 데이터와 동일한 데이터를 이미 지니고 있다면 304 Not Modified를 응답하여 해당 캐쉬를 재사용하도록 합니다.
  • 이를 통해 네트워크 상에서 오가는 데이터의 양(bandwidth)을 줄임으로써 응답 속도를 크게 감소시킬 수 있습니다.
  • 다만, etag를 생성하기 위해 해당 이미지 파일을 조회하는 작업에 따른 서버 측의 부담 자체는 여전히 존재합니다.

ShallowEtagHeaderFilter 동작 방식

  1. 이미지 조회시, 서버는 응답하기 직전에 HTTP 요청의 If-None-Match 헤더 값과 응답하려는 이미지 파일에 해당되는 etag 값을 비교합니다.

    • 이를 위해 서버에서는 클라이언트로 보내려는 이미지 파일의 내용을 토대로 etag 값을 생성합니다.
    • 이미지 파일의 내용이 변하지 않았다면 매번 동일한 etag 값이 생성됩니다.
    • 해당 경로에 저장된 파일의 내용이 다른 파일로 변경된 경우, 응답될 때 다른 etag 값이 생성됩니다.
  2. HTTP 요청의 If-None-Match 헤더 값이 없는 경우 해당 클라이언트에서는 해당 이미지를 최초로 보낸 것으로 간주하며, 200 OK 로 응답합니다.

    • HTTP 응답에는 요청한 이미지 파일의 내용이 담기며, ETag 헤더 값에 해당 이미지 파일에 대해 생성된 etag 값이 담깁니다.
    • 브라우저에서는 캐쉬 저장소에 ETag 헤더의 값을 해당 캐쉬에 대해 함께 저장합니다.
    • 이후 브라우저에서는 캐쉬 저장소에 등록된 해당 데이터를 서버에 다시 요청할 때 If-None-Match 헤더에 해당 etag 값을 담아 요청하게 된다.
  3. HTTP 요청의 If-None-Match 헤더의 etag 값이 서버에서 응답하려는 이미지 파일의 etag 값과 동일한 경우 304 Not Modified를 응답합니다.

    • 이때 응답에는 요청한 이미지 파일이 담기지 않으므로, 네트워크 상을 오가는 데이터의 양은 훨씬 적고 응답은 더 빠릅니다.
    • 브라우저에서는 304 응답 코드를 받게 되면 자동으로 캐쉬 저장소의 데이터를 계속 재사용하게 됩니다.
  4. HTTP 요청의 If-None-Match 헤더의 etag 값이 서버에서 응답하려는 이미지 파일의 etag 값과 다른 경우 200 OK를 응답합니다.

    • 이 경우 해당 이미지 파일이 이전과는 달라졌으므로 HTTP 응답에는 요청한 이미지 파일의 내용과 그에 대응되는 ETag 헤더 값이 담깁니다.
    • 브라우저에서는 응답 내용을 토대로 캐쉬 저장소의 캐쉬를 갱신한다.

2) Cache-Control 헤더

  • 기본적으로 이미지 조회시 응답에는 다음과 같은 헤더값이 추가됩니다.

    • Cache-Control: max-age=600, public
    • 이는 캐쉬 저장소에 해당 URI에 대한 응답 정보가 존재하는 경우 10분 동안 해당 캐쉬 값을 그대로 사용하라는 의미입니다.
    • 브라우저와 서버 사이의 컴포넌트들로부터 Shared Cache의 이점을 볼 수 있도록 기본적으로 public 옵션을 설정하였습니다.
  • max-age 값은 application.yml 파일의 cache.max-age 프로퍼티의 값에 해당하므로 쉽게 수정할 수 있습니다.

3) Cache Busting 지원

  • 기본적으로 캐쉬 버스팅이란 캐쉬의 유효기간을 최대한 높게 설정하여 오랜기간 재활용하되, 버전이 변경되었을 때에 즉시 서버에 요청을 보내도록 하는 기법입니다.
    • 이를 위해서는 자원의 URI를 버전에 따라 다르게 설정함으로써 캐쉬 저장소에 관리 중인 캐쉬를 사용하지 않도록 해야 합니다.
  • 이를 구현할 수 있도록 해당 서버에서는 캐쉬의 유효기간을 수정하는 것만이 아니라 이미지를 업로드할 때 version 정보를 받아 이미지의 파일명에 추가하는 기능을 구현했습니다.

구현 방법 예시

  1. 우선 Cache-Control 헤더의 max-age 값을 31536000초로 설정함으로써 1년 동안 캐쉬를 재사용하도록 합니다.
  2. 특정 이미지를 업로드할 때 version 정보를 명시하면 해당 파일명에는 해당 버전 정보가 추가됩니다.
  • 예를 들어 user/profile1라는 파일을 저장할 때 asdfds라는 해쉬값을 버전으로 명시하면, 파일은 user/profile1_asdfds.webp와 같이 저장됩니다.
  1. 특정 클라이언트에서 GET /images/user/profile1_asdfds.webp로 요청을 보내는 경우, 해당 URI에 대한 응답은 1년 동안 재사용됩니다.
  2. 이때 해당 파일을 새로운 버전으로 수정해야 하는 경우, 새로운 버전의 이미지를 업로드하고 사용자에게 이를 사용하도록 강제하도록 하면 됩니다.
  • 예를 들어 기존과 같은 user/profile1라는 파일을 저장하되 bkfefd처럼 다른 해쉬값을 버전으로 명시하면, 파일은 user/profile1_bkfefd.webp와 같이 저장됩니다.
  • 이후 기존 버전의 파일은 불필요해졌으므로 DELETE /api/images/user/profile1_asdfds와 같은 요청으로 제거할 수 있습니다.
  1. 이제 클라이언트에서 GET /images/user/profile1_bkfefd.webp로 요청을 보내도록 한다면 당연히 캐쉬 저장소에 있는 asdfds 버전 데이터를 사용하지 않고 서버로 요청을 보내게 됩니다.

기타 성능 개선

1) .webp

  • 해당 서버는 업로드되는 이미지 파일을 .webp 확장자로 저장하여 제공합니다.

  • 이를 통해 응답 성능 개선만이 아니라, 서버의 부담을 줄이고 하드 디스크에 더 많은 파일을 저장할 수 있도록 해줍니다.

  • 물론 업로드하려는 파일이 이미지 파일이 아닌 경우 400 Bad Request로 응답합니다.

  • cf) webp의 장점

    • 무손실 압축을 통해 이미지 품질은 유지하면서도 파일 크기 자체를 감소시키기 위한 확장자.
    • I.E.를 제외한 모든 브라우저들에서 호환되는 형식. (참고)
    • gif 등 모든 이미지 형식에 대응 가능.

2) HTTP 압축

  • HTTP 응답을 압축함으로써 웹 사이트의 성능을 향상시키고자 하였습니다.
  • server.compression.enabled 프로퍼티를 통해 Spring Boot에서 제공하는 기본 설정인 gzip 압축 알고리즘을 적용하였습니다.
  • HTTP 응답에 다음 헤더들이 추가됩니다.
    • Content-Encoding: gzip
    • Transfer-Encoding: chunked

실습 방법

  • 직접 해당 서버를 실행해보고 싶은 경우, 아래 두 가지 정보를 알아야 합니다.
  • 데모 내용이 궁금한 경우 블로그를 참고하기 바랍니다.
    • 주의. API 및 구현된 기능면에서 현재 서버와 다소 차이가 있습니다.

이미지 파일 저장 경로

  • 디폴트 경로: ~/static/images/{uploadPath}/{fileName}
    • static/imagesapplication.yml 파일의 image.storage.root-directory 프로퍼티의 값에 해당함.
    • 해당 yml 파일의 프로퍼티 값을 수정하거나, 구동 시점에 값을 주입해줌으로써 저장 경로 변경 가능.

인증 기능

  • 이미지를 업로드/제거하려는 경우, 해당 API를 호출할 때 Authorization 헤더 값으로 서버에서 설정한 비밀 키를 추가해줘야 합니다.
  • 비밀 키는 application.yml 파일의 security.authorization-key 프로퍼티 값에 해당합니다.
    • prod 프로파일로 실행하는 경우 SECRET_KEY 환경변수 값을 활용하게 됩니다.

About

이미지 호스팅 서버를 통해 알아보는 웹 성능 개선 기법들

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages