실전 프로젝트를 진행하며 이미지 업로드 최적화에 대해 고민하게 되었다.
이미지를 최대5장까지 함께 등록할 수 있는 게시글 기능을 구현해야 했는데, 유저가 업로드할 이미지의 크기가 제각기 다르다는 점과 고해상도의 이미지를 업로드 했을 때 조회 속도가 느려지는 경우가 있다는 점에서 이미지 리사이징이 필요하다고 판단했다.
프론트에서 이미지 업로드시 용량을 줄이는 방법으로 리사이징을 했지만, 그와 더불어 백엔드에서도 일정한 크기와 비율로 이미지를 리사이징 하여 저장하기로 했다.
기술적 대안으로는 Marvin, S3 Lambda, imgScalr이 있었다.
S3 Lambda는 참고할 만한 reference가 적었고, Marvin의 경우 압축 비율이 우수하다는 reference를 확인했으나 처리 속도가 빠르고 화질이 깨지지 않는 imgScalr을 사용하는 것이 낫다고 최종 판단했다.
imgScalr 라이브러리를 사용하여 이미지를 리사이징 한 뒤 S3에 저장하도록 구현하였다.
private static final int TARGET_HEIGHT = 650;
// 이미지 업로드
public String uploadImage(MultipartFile file) throws IOException {
String fileFormat = Objects.requireNonNull(file.getContentType())
.substring(file.getContentType().lastIndexOf("/") + 1).toLowerCase();
String fileName = createFileName(file.getOriginalFilename());
BufferedImage croppedImage = resizeImage(file);
ObjectMetadata objectMetadata = new ObjectMetadata();
ByteArrayInputStream byteArrayInputStream = convertImage(croppedImage, file.getContentType(), fileFormat, objectMetadata);
amazonS3.putObject(new PutObjectRequest(bucket, fileName, byteArrayInputStream, objectMetadata).withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3.getUrl(bucket, fileName).toString();
}
// imgscalr 라이브러리로 resizing(TARGET_HEIGHT를 정하고 비율에 맞춰 리사이징)
private BufferedImage resizeImage(MultipartFile multipartFile) throws IOException {
BufferedImage sourceImage = ImageIO.read(multipartFile.getInputStream());
if (sourceImage.getHeight() <= TARGET_HEIGHT) {
return sourceImage;
}
double sourceImageRatio = (double) sourceImage.getWidth() / sourceImage.getHeight();
int newWidth = (int) (TARGET_HEIGHT * sourceImageRatio);
return Scalr.resize(sourceImage, newWidth, TARGET_HEIGHT);
}
private ByteArrayInputStream convertImage(BufferedImage croppedImage, String contentType, String fileFormat, ObjectMetadata objectMetadata) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(croppedImage, fileFormat, byteArrayOutputStream);
objectMetadata.setContentType(contentType);
objectMetadata.setContentLength(byteArrayOutputStream.size());
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}
이미지 리사이징을 적용하여 게시글 등록 속도는 전보다 조금 느려졌으나 저장한 이미지를 조회해오는 속도는 크게 개선되었다.
동일한 고해상도 이미지 5장을 등록하여 조회해본 결과, 리사이징 적용 전에는 이미지 조회에 333ms가 소요되었었는데 적용 후에는 79ms까지 개선된 것을 확인할 수 있었다.