SpringBoot 3, GraalVM Native Image 적용 실패담

profoundsea25·2023년 5월 18일
0

Spring

목록 보기
2/2

GraalVM Native Image에 대하여

SpringBoot 3는 GraalVM을 왜 지원하는가

  • 결론부터 말하면 성능 때문이다. 기존 JVM보다 프로그램의 실행 시간을 줄이고 메모리를 더 효율적으로 사용한다.
  • 이는 클라우드 환경에서, 컨테이너가 생성된 후 서버가 구동되기까지의 시간을 단축한다는 뜻이다.
    • 특히 빠르게 동작하는 Scale-Out이 필요한 상황에서 유용하다.

Native Image

  • Java 코드를 독립적으로 실행 가능한(standalone executable) 이미지로 만들어주는 기술을 말한다.
  • 기존의 Java는 실행하는 OS에 JRE를 설치하여 실행하지만, Native Image는 JVM 등을 자체적으로 포함하여 다른 프로그램을 필요로 하지 않는다.
  • 다만, 실행 환경에 맞는 Image를 만들어야 한다.

AOT 컴파일 vs JIT 컴파일

  • GraalVM을 찾아보면 AOT 컴파일을 핵심 특징으로 내세우고 있다. 컴파일의 방식인데, 기존 Java 방식인 JIT 컴파일과 간단히 비교하면 다음과 같다.
    • JIT (Just-In-Time)
      • 저수준(low level) 기계어 코드 컴파일을 런타임 때 한다.
      • 그때 그때 컴파일을 진행하기 때문에, 상황에 최적화된 코드를 컴파일 한다. 다만 AOT보다 느리다.
    • AOT (Ahead-Of-Time)
      • 런타임 전에 저수준(low level) 기계어 코드 컴파일을 미리 진행한다.
      • 미리 컴파일 해놓기 때문에 실행 속도가 빠르다. 다만 JIT보다 상황에 최적화된 컴파일을 할 수 없다.
  • AOT의 경우 JIT와 다르게 런타임에서 동적 요소들(ex Reflection, Dynamic Proxy 등)을 처리할 수 없다.
    • 따라서 전체 코드를 컴파일하기 전에, 이에 대한 정보들을 설정 파일(configuration file)로 전달하여 보완한다.
    • 즉, 컴파일 시에 byte code와 동적 요소들을 나타내는 meta data들을 함께 사용해 native image를 생성한다.

Spring Boot 3.0이 GraalVM Native Image를 지원한다는 의미

  • AOT complile 단계에서 Spring Boot와 관련된 meta data(configuration file)를 GraalVM에 제공하는 것이다.
  • AOT 컴파일의 특징이 Spring Boot 환경에서 제약으로 작용하기 때문이다.
    • 이는 "정의된 Bean들은 런타임에서 변경될 수 없다."을 의미한다.
    • 예를 들어, Spring Boot가 meta data를 생성해주지 않으면 다음과 같은 문제가 발생한다.
      • @Profile같은 어노테이션은 사용할 수 없다. 반드시 컴파일 당시에 관련 변수를 함께 넘겨주어야만 한다.
      • @ConditionalOnProperty와 같은 동적으로 빈을 생성하는 어노테이션도 마찬가지이다.

주의점

  • 아직 완전히 Java application을 지원하는 것이 아니다.
    • 특히, 동적 요소들에 대한 meta data를 완전히 전달하지 못할 경우, 런타임 실패가 발생할 수 있다.
  • Mockito 등의 라이브러리는 사용할 수 없다.

적용하기

환경

  • Kotlin 1.7.22 (Java 17), Spring Boot 3.0.2 (적용을 위해 3.0.6으로 올림), Gradle (Kotlin DSL)

Gradle

  • 플러그인 추가
    • Spring Boot가 지원하는 것 외의 meta data 생성을 위한 플러그인이다.
plugins {
  // ...
  id("org.graalvm.buildtools.native") version "0.9.21"
}
  • Docker 이미지 생성
    • 위에 명시한 플러그인을 통해 gradle에서 바로 네이티브 이미지를 만들 수 있다.
$ gradle bootBuildImage
  • 혹은, Docker가 필요없는 실행 파일을 만들 수도 있다. 다만, GraalVM이 설치되어 있어야 한다.
$ gradle nativeCompile

적용에 실패한 이유

  • 아직 SpringBoot가 관리하는 Dependency(라이브러리) 이외에 대한 Hint들은 직접 찾아서 configuration file에 명시해야 한다.
    • 예를 들어, mybatis를 사용하면서 SQL을 로그에 남기려면 log4jdbc를 사용해야 한다. AOT 컴파일 실행 후 서버를 띄워보면 이를 위해 log4j에 대한 참조를 필요로 한다. (왜 그런지는 모르겠다.)
    • 관련 힌트를 주면, 또 다른 힌트를 요구하고, 또 다른 힌트를 주면, 또 다른 힌트를 요구하는 것의 무한 반복이었다.

그렇다면 GraalVM만이라도 적용한다면?

  • 결론만 말하면 기존보다 서버 구동 속도가 조금 느려졌었다.
    • GraalVM을 적용하는 것만으로도 속도가 향상된다고 공식 페이지에 명시되어 있지만, 서버 구성에 따라 결과가 다른 듯하다.

결론 - SpringBoot 3.0.6 기준

  • Spring의 동적 요소를 활용하는, SpringBoot가 관리하는 것 외의 Dependency가 추가된 경우 AOT 컴파일을 위한 Hint를 모두 명시해야 한다.
    • 즉, Gradle에 명시된 Dependency에 대한 모든 힌트를 추가하는 자동화가 아직은 완성도가 높지 않다.
  • GraalVM을 적용하여도, 서버 구동 시간이 반드시 빨라지지 않으므로, 적용 후 측정이 필요하다.

참고자료

profile
Kotlin/Java 웹 애플리케이션 백엔드 개발자입니다.

1개의 댓글

comment-user-thumbnail
2024년 4월 16일

graalvm 을 적용했는데 서버구동이 느려진거면 이미지가 잘못빌드된거같은데요. 그런경우는 첨봤네요. 저는 잘 작동되었어서요

답글 달기