ecsimsw

S3 업로드 속도 개선, 썸네일을 생성하는 AWS 람다 제작 본문

S3 업로드 속도 개선, 썸네일을 생성하는 AWS 람다 제작

JinHwan Kim 2024. 5. 31. 06:31

파일업로드 속도 문제

현재 'FE -> BE -> S3' 으로 사진을 업로드하고 있는데, 큰 패킷 전달이 두번이다 보니 업로드 속도가 너무 느리다. S3 업로드가 아니라 애초에 사이즈가 큰 요청이 오가는 시간 자체가 느린 것을 부하 테스트로 확인했다. 1MB 파일, 100명의 동시 요청 테스트에서 단순히 서버에서 MultipartFile 로 첨부 파일을 응답 받는 것만으로 응답 평균 시간은 200ms 가 걸렸다. 

 

클라이언트에서 직접 S3 업로드

파일 전달에 필요한 비용을 낮추고 서버의 요청 처리 속도를 개선하기 위해 클라이언트에서 직접 S3에 사진을 업로드한다. 프론트엔드에서 백엔드 서버로 이미지 파일이 전송되는 비용을 아낄 수 있다. 허용된 path에, 허용된 용량만큼만 업로드 할 수 있도록 S3 Pre-signed url 사용한다. 처리 시나리오는 아래와 같다.

 

1. FE -> BE, 파일 사이즈와 함께 업로드 요청, 업로드 가능 여부 확인
2. BE -> FE, pre-signed url 반환
3. FE -> S3, 파일 업로드
4. FE -> BE, 파일 업로드 처리 완료 알림, 스토리지 사용량 업데이트

 

Pending, Commit / Rollback

S3 업로드는 성공했으나 BE에서 리소스 정보 저장에 실패하는 경우가 발생할 수 있다. Picup 에서는 이 문제를 DB에 상태를 저장하고 스케줄링으로 더미 데이터를 삭제하는 식으로 처리한다.

 

Pre-signed url 발급 요청이 오면 WAS는 이를 업로드 준비 생각하고 DB에 파일 상태를 비저장 상태(Pending)로 기록한다. 클라이언트에서 정상 업로드를 완료하면 WAS에 이를 알리고 DB 에 기록해둔 파일 상태를 저장 상태 (Commit)로 업데이트한다.

스토리지 업로드에 실패했거나, WAS에 커밋 요청을 실패하는 경우 파일 상태는 비저장 상태(Pending)으로 남긴다.

 

이후 스케줄러를 통해 저장 실패된 자원을 더미 파일로 판단하여 주기적으로 제거한다. 이때 업로드가 진행되는 시간을 고려하여 Pending 이면서 유효 시간을 지난 자원을 저장 실패로 판단하고 제거해야 한다.

 

처리결과

상위 80%의 사진 파일 크기를 기준으로 성능 향상을 확인한다. Mysql 8.0부터 도입된 순위/등급 함수 'ntitle'를 사용하면 쉽게 N%에 해당하는 데이터를 추출할 수 있다. 아래는 썸네일이 아닌 원본 파일로 저장된 사진 메타 데이터에서, size를 기준으로 10등급으로 나누고 그 중 9등급의 첫번째 로우를 읽는 쿼리이다.

 

select * from \
     (select size, ntile(10) over(order by size) as grade from file_resource where storage_type='STORAGE') \
     t where grade = 9 LIMIT 1;

 

 

약 30GB의 가족 사진 데이터를 기준으로 쿼리의 결과는 8.22MB 였고, 이와 유사한 7.97MB의 사진 파일을 기준으로 업로드 테스트를 진행한다.

 

 

S3 업로드를 10회 반복해서 속도를 확인한다. BE에서 S3로의 단순 업로드이고, 대략 900ms ~ 1초가 걸리는 것을 확인할 수 있다.

 

처리 전, 클라이언트에서 BE로 파일 업로드, BE에서 썸네일을 생성하고 다시 S3 업로드까지의 응답 시간은 대략 3.5초가 걸렸다. 처리 후, 클라이언트가 BE로 pre-signed url을 반환받는데 응답 시간은 200ms, S3 업로드를 마치고 BE에 결과를 알리는 Commit 요청의 응답 시간은 95ms 였다.

 

S3 업로드에 소요 시간
- 900ms ~ 1000ms

처리 전
- 파일 업로드 응답 시간 : 3.58 s

처리 후
- Pre-signed url 응답 시간 : 208 ms
- Commit 응답 시간 : 95 ms

 

결과적으로 처리 전에는 3.5sec가 걸렸던 8mb 파일 업로드는, Pre-signed url 도입과 썸네일 생성 처리를 제거한 후에 1.2sec로 개선되었다. 이는 S3 업로드 시간은 테스트했던 서버 기준을 가정했고, 클라이언트가 직접 S3에 업로드 하는 경우에는 서버 부하가 없을테니 S3업로드 시간에도 개선이 있을 것이라고 생각한다.

 

또 파일 사이즈가 커질 수록, 동영상 파일일 경우에, 기존에는 썸네일을 처리하는 시간이 더 크게 소요됐기에, 테스트에 사용한 8MB보다 더 큰 파일일 수록 개선 수치는 커질 것이다. BE 서버의 부하나 메모리 사용량 역시 크게 개선되었다.

 

썸네일을 자동 생성하는 AWS 람다 제작

화면에 파일을 직접 표시하면 페이지 로드 시간에 문제가 생긴다. 예를 들어 파일 목록 조회에 많은 파일들이 로드되어야 할 것이고, 모바일처럼 화면이 작은 기기에서 불필요한 크기의 파일이 로드될 것이다. 사용 목적, 기기에 따라 다른 사이즈의 파일을 만들기 위해 유저가 업로드한 이미지를 리사이징하고, 동영상은 프레임을 캡쳐하여 썸네일 이미지로 사용하고 있다.

 

기존에는 파일을 받은 BE 서버가 썸네일을 생성하고 S3에 동시 업로드했다. Pre-signed url으로 클라이언트에서 S3에 직접 업로드하니 썸네일을 생성할 다른 방법이 필요했다. 또 썸네일 생성에 시간이 필요했고, 메모리가 사용되어 처리할 때마다 사용 메모리가 급등했다가 GC로 바로 제거되는 등의 불안정적인 메모리 사용 모습도 있었다.

 

Thumbnail Lambda

S3 업로드 이벤트를 트리거하여 썸네일 파일을 자동으로 생성, 저장하는 AWS Lambda를 제작했다. 사진 리사이징에는 Thumbnailator를, 동영상 프레임 캡쳐에는 JCodec을 사용했다.

 

이미지 파일의 경우 지정 크기 이상 사진 파일을 리사이징한다. 자바 awt.Graphics2D 를 사용하면 추가적인 라이브러리 없이 JRE만으로 리사이징을 처리할 수 있지만 간혹 사진이 회전되는 문제가 있다. 이미지 파일의 exif 메타데이터를 사용하여 회전 값을 고칠 수 있지만, 해당 메타데이터를 읽기 위한 라이브러리가 추가로 필요하고 코드 추가도 많다. Thumbnailator는 exif 를 사용하여 리사이징하기에 회전 문제가 발생하지 않는다. 단, BufferedImage를 원본 파일의 타입으로 사용하면 그대로 회전 문제가 발생한다. 이슈

 

동영상 파일의 경우 파일의 첫 번째 프레임을 캡쳐한다. Jodec 를 사용한 프레임 캡쳐를 위해선 해당 파일을 파일 시스템에 저장해야 하는데 AWS 람다에선 512 MB 만을 기본으로 제공한다. Picup 에선 업로드 가능 파일 최대 크기를 300MB로 제한하기 때문에 당장은 람다의 임시 공간을 사용해도 문제가 되지 않았다. 만약 영구 저장이 필요하거나 더 큰 공간이 필요하다면 EFS를 우선으로 알아볼 생각이다. Lambda-storage  

 

썸네일이 제작되는 시간 간격 처리 고민

사용자의 파일 업로드와 썸네일 저장이 동시에 이뤄지지 않기 때문에, 그 둘 사이의 시간 간격 처리가 필요하다. 썸네일이 생성되기 전까지 이미지는 원본 사진을 사용하고, 동영상은 동영상임을 표시하는 임시 아이콘을 사용한다. 썸네일 람다가 파일 저장을 마치면 BE에 썸네일 저장 완료를 알리고, BE에선 DB에 이를 기록하여 그 이후부터 썸네일을 사용할 수 있도록 하였다.

 

천 개 이상의 파일을 업로드하며 테스트했는데, 당장엔 불편함을 느끼지 못할 정도로 람다 처리가 빠르고 안정적이라고 생각됐다. 만약 같은 방식으로 처리했는데 성능에 문제가 있다면 람다나 썸네일 제작에 걸리는 시간을 개선하기보다 이미지 파일 캐싱이 제대로 되는지를 먼저 확인하고 개선했으면 좋겠다. 예를 들어 CDN를 사용하고 있는지, 브라우저에 컨텐츠 캐싱이 제대로 되어 있는지를 확인하는 것이다. 

 

파일 업로드 흐름

Comments