이미지 압축 oome 다시 돌아보기

wellbeing-dough·2024년 11월 7일

상황

예전에 서버에서 oome가 터졌었다 예전에 한번 터졌던 적이 있어서

             -XX:+HeapDumpOnOutOfMemoryError \
             -XX:HeapDumpPath=/tmp \

jar파일 만들 때 이런 옵션으로 oome가 터지면 자동으로 /tmp에 힙 덤프 파일을 만들어 놨다 물론 덤프 파일 만들고 자동으로 서버 다시 동작하게도 해놨다 어려운건 아니고 elastic beanstalk에서 알아서...

힙 덤프 파일을 분석해봤는데

아 sksamuel.scrimages는 이미지 webp로 압축하려고 했던건데 여기서 oome가 터졌다는 것을 단번에 알 수 있었다

일단 급한대로 스왑 메모리 설정하고 힙 메모리 늘려놓고

            JAVA_OPTS="-Xms512m -Xmx1024m"

우리 서버가 t3.small 이라서 메모리가 1기가다 여기다가 스왑 메모리 1기가 넣어놓고 자바 프로세스 메모리 최대 1기가 넣어놨다

또한 이미지 webp압축은 일단 일시정지 했었다

이제부터 webp압축을 다시 돌려놓고 근본적인 문제를 해결 해 보자

문제

1. 힙 덤프 분석을 자세히 봐보자

Pixel이라고 되어있다. 여기서 예상해볼 수 있는 것은 이미지의 메모리 때문이 아니라 이미지의 해상도 ex)1024x1024일 것으로 예상할 수 있다

그렇다면 스펙이 같은 서버를 빠르게 구축해보고 한번 간단하게 테스트를 해보자


여기 옛날에 했던 프로젝트의 로고가 마침 4267x4267인 것을 볼 수 있다 하지만 용량은 113KB이다


업로드를 해 봤다 100메가에서 웃도는 메모리가 이미지 하나를 업로드 하자 850메가로 치솟고 2분 후 또 업롣드를 하자 850까지 치솟는 것을 볼 수 있다. Major GC도 두번 일어난다 이러니 메모리가 터지지... 그렇다면 내 컴퓨터의 바탕화면을 전체 캡쳐해보자


용량은 7.2로 상당히 무겁지만, 해상도가 2546x1440이다 이걸 업로드 해보면


350에서 450이 되는 것을 볼 수 있다.

로고를 올린 후 바탕화면을 올려서 살짝 불공정할 수 있지만 가설은 확실하게 검증되었다

확실하게 다시 알아보자

2. 원인 제대로 파악

해상도가 높을수록 메모리 사용량이 더 크다

이미지를 처리하기 위해 메모리로 로드하면, 각 픽셀의 색상 정보를 메모리에 할당해야 한다. 일반적으로 한 픽셀당 3~4바이트(RGB 혹은 RGBA)를 차지한다
예를 들어, 100x100 해상도의 이미지는 10,000개의 픽셀이므로 메모리에서 약 3040KB가 필요하지만, 4000x4000 해상도의 이미지는 1,600만 픽셀로, 메모리에서 약 4864MB가 필요하게 된다
즉, 해상도가 높을수록 픽셀 수가 증가하고 메모리 사용량이 비례하여 증가한다

3. 이미지를 압축하기 전, resize를 진행 해야 한다

  1. Thumbnailator
    Thumbnailator는 Java용 오픈소스 썸네일 생성 라이브러리로, 이미지 리사이징을 쉽게 구현할 수 있다 간결한 API로 코드 양을 줄이며, 빠르게 고품질의 썸네일을 생성할 수 있고 성능 최적화가 잘 되어 있어 높은 품질과 성능이 좋지만 고급 이미지 조작(회전, 필터링 등)에 대해 제한적이다

  2. OpenCV (Java bindings)
    OpenCV는 컴퓨터 비전 작업을 위한 강력한 오픈소스 라이브러리로, Java 바인딩을 통해 Java에서도 사용할 수 있다 이미지 리사이징 기능은 물론 다양한 컴퓨터 비전 기능을 제공한다. 빠른 성능과 고품질 이미지 처리가 가능하고 GPU가속을 사용할 수 있어 대용량 이미지나 고성능이 필요한 작업에 좋지만, 외부 라이브러리 설치와 Java 프로젝트에 적용하려면 추가 설정이 필요하다. 단순 리사이징 용도로는 너무 과하고 무겁다

  3. Image.getScaledInstance
    특징: Image.getScaledInstance는 Java에서 기본적으로 제공하는 Image클래스의 메서드 이다 기본 Image 클래스를 사용하여 빠른 구현을 가능하게 하지만 크기 조절 시 이미지가 흐릿해질 수 있고, 성능이 떨어진다

  4. imgscalr
    imgscalr
    imgscalr는 경량의 Java 이미지 리사이징 라이브러리로, 성능과 품질을 모두 고려한 이미지 스케일링이 가능하다 내부적으로 Graphics2D를 사용하여 동작하지만 보다 품질이 좋고 빠른 리사이징을 제공하며, 설정이 간단하다 이미지 품질을 위한 다양한 모드(ULTRA_QUALITY, FAST 등)를 제공하여, 성능과 품질의 균형을 쉽게 맞출 수 있다 경량 라이브러리이므로 추가적인 의존성 문제를 최소화하면서 고품질 리사이징을 수행할 수 있습니다.

  5. Graphics2D
    Graphics2D는 Java AWT 패키지에서 제공하는 고급 그래픽 변환 및 렌더링이 가능하다
    장점으로는 이미지 품질을 세밀하게 조절 할 수 있고, 크기 조절 외에도 회전, 뒤집기, 기타 2D 그래픽 처리가 가능하다
    단점으로는 사용법이 비교적 복잡하고 더 많은 코드 작성이 필요하다 대규모 이미지 처리 시 성능에 영향을 준다

다 직접 리사이징 해서 눈으로 직접 봐보고 결정 해 보자
확실히 Graphics2D, imgscalr가 정말 빨랐다 하지만 화질이 너무 안좋아진다. Image.getScaledInstance도 엄청 빨랐고 화질이 처음엔 깨졌지만 그나마

getScaledInstance(imageSize.getWidth(), imageSize.getHeight(), Image.SCALE_SMOOTH);

SCALE_SMOOTH 로 스무스하게 했을때 좀 괜찮아졌다

Thumbnailator를 쓰기로 했다

해결

    private File resizeImage(File imageFile) throws IOException {
        BufferedImage resizedImage = Thumbnails.of(imageFile)
                .size(1440, 1440)
                .asBufferedImage();
        File tempFile = new File(imageFile.getParent(), "resized_" + System.currentTimeMillis() + ".png");
        ImageIO.write(resizedImage, "png", tempFile);
        return tempFile;
    }

    private File convertToWebP(File imageFile) throws IOException {
        ImmutableImage image = ImmutableImage.loader().fromFile(imageFile);
        String fileName = generateUniqueFileName("webp");
        File outputFile = new File(imageFile.getParent(), fileName);
        image.output(WebpWriter.DEFAULT, outputFile);
        return outputFile;
    }

사이즈를 여러개로 조합해본 결과 성능도 잡고 프론트 엔드 동료와 합의점도 찾은 1440x1440이 적절하다는 것을 알았고 1440x1440으로 했다

결과

이미지 결과는 하나만 봐보자 (2024 롤드컵 페이커의 슈퍼플레이)

압축 전

압축 후

솔찍히 진짜 별 차이 없는데 4.1MB를 147KB로 압축 시켰다 굳굳

메모리도 봐볼까?

물론 Major GC가 생기긴 한다 하지만 300~400 메모리 왔다갔다 한다. 기존에 사진 두장에 1기가 턱밑까지 뚫은거 생각하면 양반이다

회고

이미지 리사이징, 압축 과정이 서버에 상당히 큰 자원 소모를 하게 하는 것을 알았다.
가장 좋은 방법은
s3에 이미지 올리면 람다 함수 실행시켜서 거기서 리사이징 압축 하는 방법일 것 같다. 이왕 하는거 s3의 presigned url 써서 프론트가 직접 s3에 올리는 것도 좋아 보이지만, 일단 그럴려면 프론트 동료의 리소스도 필요하고 나중에 나랑 프론트 동료가 둘다 널널할 때 조심스럽게 얘기를 꺼내봐야 할 것 같다

그리고 앞으로 라이브러리 생각없이 막 갔다 쓰지말고 api 하나 개발할때마다 상용 서버에서도 테스트 해보면서 리소스 확인도 해야겠다 아직 dev 환경을 배포하기엔 돈이 너무 없다...

참고:
https://babble-dev.tistory.com/39
https://developers.google.com/speed/webp/faq?hl=ko#can_a_webp_image_grow_larger_than_its_source_image
https://medium.com/@angal2310/image-resize-webp-4dffafd68683

0개의 댓글