- 원래 해당 프로젝트는 우테코에서 S3 사용이 금지됨에 따라 만들어본 간단한 토이 프로젝트였습니다.
- 학습 목적으로 정적 데이터를 제공하는 서버측에서 할 수 있는 성능 개선 기법들을 몇 가지 적용해보았습니다.
- 이미지 조회 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}
ETag
/If-None-Match
헤더를 통해 웹 사이트의 성능 개선을 시도하였습니다.- 특정 클라이언트의 캐쉬 저장소에 저장되어있던 파일을 서버로 다시 요청한 경우, 서버에서는 etag 값의 비교를 통해 캐쉬 저장소의 데이터와 서버의 데이터가 동일한지를 확인합니다.
- 만일 클라이언트가 서버에서 전송하려는 데이터와 동일한 데이터를 이미 지니고 있다면
304 Not Modified
를 응답하여 해당 캐쉬를 재사용하도록 합니다. - 이를 통해 네트워크 상에서 오가는 데이터의 양(bandwidth)을 줄임으로써 응답 속도를 크게 감소시킬 수 있습니다.
- 다만, etag를 생성하기 위해 해당 이미지 파일을 조회하는 작업에 따른 서버 측의 부담 자체는 여전히 존재합니다.
-
이미지 조회시, 서버는 응답하기 직전에 HTTP 요청의
If-None-Match
헤더 값과 응답하려는 이미지 파일에 해당되는 etag 값을 비교합니다.- 이를 위해 서버에서는 클라이언트로 보내려는 이미지 파일의 내용을 토대로 etag 값을 생성합니다.
- 이미지 파일의 내용이 변하지 않았다면 매번 동일한 etag 값이 생성됩니다.
- 해당 경로에 저장된 파일의 내용이 다른 파일로 변경된 경우, 응답될 때 다른 etag 값이 생성됩니다.
-
HTTP 요청의
If-None-Match
헤더 값이 없는 경우 해당 클라이언트에서는 해당 이미지를 최초로 보낸 것으로 간주하며,200 OK
로 응답합니다.- HTTP 응답에는 요청한 이미지 파일의 내용이 담기며,
ETag
헤더 값에 해당 이미지 파일에 대해 생성된 etag 값이 담깁니다. - 브라우저에서는 캐쉬 저장소에
ETag
헤더의 값을 해당 캐쉬에 대해 함께 저장합니다. - 이후 브라우저에서는 캐쉬 저장소에 등록된 해당 데이터를 서버에 다시 요청할 때
If-None-Match
헤더에 해당 etag 값을 담아 요청하게 된다.
- HTTP 응답에는 요청한 이미지 파일의 내용이 담기며,
-
HTTP 요청의
If-None-Match
헤더의 etag 값이 서버에서 응답하려는 이미지 파일의 etag 값과 동일한 경우304 Not Modified
를 응답합니다.- 이때 응답에는 요청한 이미지 파일이 담기지 않으므로, 네트워크 상을 오가는 데이터의 양은 훨씬 적고 응답은 더 빠릅니다.
- 브라우저에서는
304
응답 코드를 받게 되면 자동으로 캐쉬 저장소의 데이터를 계속 재사용하게 됩니다.
-
HTTP 요청의
If-None-Match
헤더의 etag 값이 서버에서 응답하려는 이미지 파일의 etag 값과 다른 경우200 OK
를 응답합니다.- 이 경우 해당 이미지 파일이 이전과는 달라졌으므로 HTTP 응답에는 요청한 이미지 파일의 내용과 그에 대응되는
ETag
헤더 값이 담깁니다. - 브라우저에서는 응답 내용을 토대로 캐쉬 저장소의 캐쉬를 갱신한다.
- 이 경우 해당 이미지 파일이 이전과는 달라졌으므로 HTTP 응답에는 요청한 이미지 파일의 내용과 그에 대응되는
-
기본적으로 이미지 조회시 응답에는 다음과 같은 헤더값이 추가됩니다.
Cache-Control: max-age=600, public
- 이는 캐쉬 저장소에 해당 URI에 대한 응답 정보가 존재하는 경우 10분 동안 해당 캐쉬 값을 그대로 사용하라는 의미입니다.
- 브라우저와 서버 사이의 컴포넌트들로부터 Shared Cache의 이점을 볼 수 있도록 기본적으로
public
옵션을 설정하였습니다.
-
max-age
값은application.yml
파일의cache.max-age
프로퍼티의 값에 해당하므로 쉽게 수정할 수 있습니다.
- 기본적으로 캐쉬 버스팅이란 캐쉬의 유효기간을 최대한 높게 설정하여 오랜기간 재활용하되, 버전이 변경되었을 때에 즉시 서버에 요청을 보내도록 하는 기법입니다.
- 이를 위해서는 자원의 URI를 버전에 따라 다르게 설정함으로써 캐쉬 저장소에 관리 중인 캐쉬를 사용하지 않도록 해야 합니다.
- 이를 구현할 수 있도록 해당 서버에서는 캐쉬의 유효기간을 수정하는 것만이 아니라 이미지를 업로드할 때
version
정보를 받아 이미지의 파일명에 추가하는 기능을 구현했습니다.
- 우선
Cache-Control
헤더의max-age
값을 31536000초로 설정함으로써 1년 동안 캐쉬를 재사용하도록 합니다. - 특정 이미지를 업로드할 때
version
정보를 명시하면 해당 파일명에는 해당 버전 정보가 추가됩니다.
- 예를 들어
user/profile1
라는 파일을 저장할 때asdfds
라는 해쉬값을 버전으로 명시하면, 파일은user/profile1_asdfds.webp
와 같이 저장됩니다.
- 특정 클라이언트에서
GET /images/user/profile1_asdfds.webp
로 요청을 보내는 경우, 해당 URI에 대한 응답은 1년 동안 재사용됩니다. - 이때 해당 파일을 새로운 버전으로 수정해야 하는 경우, 새로운 버전의 이미지를 업로드하고 사용자에게 이를 사용하도록 강제하도록 하면 됩니다.
- 예를 들어 기존과 같은
user/profile1
라는 파일을 저장하되bkfefd
처럼 다른 해쉬값을 버전으로 명시하면, 파일은user/profile1_bkfefd.webp
와 같이 저장됩니다. - 이후 기존 버전의 파일은 불필요해졌으므로
DELETE /api/images/user/profile1_asdfds
와 같은 요청으로 제거할 수 있습니다.
- 이제 클라이언트에서
GET /images/user/profile1_bkfefd.webp
로 요청을 보내도록 한다면 당연히 캐쉬 저장소에 있는asdfds
버전 데이터를 사용하지 않고 서버로 요청을 보내게 됩니다.
-
해당 서버는 업로드되는 이미지 파일을
.webp
확장자로 저장하여 제공합니다. -
이를 통해 응답 성능 개선만이 아니라, 서버의 부담을 줄이고 하드 디스크에 더 많은 파일을 저장할 수 있도록 해줍니다.
-
물론 업로드하려는 파일이 이미지 파일이 아닌 경우
400 Bad Request
로 응답합니다. -
cf)
webp
의 장점- 무손실 압축을 통해 이미지 품질은 유지하면서도 파일 크기 자체를 감소시키기 위한 확장자.
- I.E.를 제외한 모든 브라우저들에서 호환되는 형식. (참고)
- gif 등 모든 이미지 형식에 대응 가능.
- HTTP 응답을 압축함으로써 웹 사이트의 성능을 향상시키고자 하였습니다.
server.compression.enabled
프로퍼티를 통해 Spring Boot에서 제공하는 기본 설정인 gzip 압축 알고리즘을 적용하였습니다.- HTTP 응답에 다음 헤더들이 추가됩니다.
Content-Encoding: gzip
Transfer-Encoding: chunked
- 직접 해당 서버를 실행해보고 싶은 경우, 아래 두 가지 정보를 알아야 합니다.
- 데모 내용이 궁금한 경우 블로그를 참고하기 바랍니다.
- 주의. API 및 구현된 기능면에서 현재 서버와 다소 차이가 있습니다.
- 디폴트 경로:
~/static/images/{uploadPath}/{fileName}
static/images
는application.yml
파일의image.storage.root-directory
프로퍼티의 값에 해당함.- 해당 yml 파일의 프로퍼티 값을 수정하거나, 구동 시점에 값을 주입해줌으로써 저장 경로 변경 가능.
- 이미지를 업로드/제거하려는 경우, 해당 API를 호출할 때
Authorization
헤더 값으로 서버에서 설정한 비밀 키를 추가해줘야 합니다. - 비밀 키는
application.yml
파일의security.authorization-key
프로퍼티 값에 해당합니다.prod
프로파일로 실행하는 경우SECRET_KEY
환경변수 값을 활용하게 됩니다.