GraalVM - R과 Java 사이

 

안녕하세요. 정말 오랜만에 글을 작성합니다. 오늘은 오랜만에 작성한다는 의미를 담아 조금 특별한 개발글을 적어보고자 합니다.
개발을 하다보면, 한 가지 언어가 아닌 여러가지 언어를 사용할 때가 많이 있습니다. 특히 저의 경우는 데이터 처리 하면서 애플리케이션을 개발할 때 이러한 경우를 많이 느끼곤 했었는데요.

GraalVM이 무엇이고 이것이 혼용 언어의 개발과 어떠한 연관이 있는지에 대해 알아보도록 하겠습니다.

 

R과 Java 사이

R 언어는 보통 데이터 처리를 할 때 많이 사용하는 언어 중 하나입니다. 하지만 애플리케이션을 만들지 못하는 것은 아닙니다. R에서는 Shiny라는 공식 웹 애플리케이션을 제공하여 이를 이용해서도 R을 이용한 웹 애플리케이션 개발이 가능합니다.

그렇지만 이러한 웹 애플리케이션이 대용량 서비스에 적합하도록 개발하고 싶다면 Shiny로는 아마 역부족일 것입니다. 그러한 성능이 검증되지도 않았고, 단순히 내부에서 데이터 처리로 사용한다면 모르겠지만 서비스용으로는 적합하지 않습니다.

그렇다 보면 내가 처리한 데이터를 Java의 Spring boot 등과 연동하거나 반대로 Java에서 구현한 메소드나 함수를 R에서 호출하고 싶을 때가 있었을 것입니다. 이를 가능하게 해주는 R의 패키지 중 하나가 바로 rJava 입니다.

 

What is GraalVM ?

GraalVM Architecture

그렇다면 GraalVM은 무엇일까요? GraalVM은 JVM 중 하나로 많은 프로그래밍 언어를 번역할 수 있는 Polygot 기능이 지원되어 R에서 Java 함수를 호출하는 방식을 가능한 오버헤드 없이 사용할 수 있어 R에서 FastR 패키지 설치를 통해 사용할 수 있는 PolygotVM 입니다.

실제로 GraalVM은 R과 Java 뿐만 아니라 Scala, Kotlin, Clojure, LLVM(C, C++, Rust), Python, Ruby 등 다양한 언어를 지원하고 있고, 차후 아키텍처에서는 LLVM 언어를 제외한 다른 언어들도 Truffle Framework 위에서 돌릴 수 있도록 하는 것을 목표로 하고 있습니다.

 

Performance

그렇다면 생각 이상으로 좋게 뽑고 있다는 GraalVM의 Performance는 어느 정도 일까?

먼저 소개해드릴 점이 있다면, GraalVM은 JVM 기반의 Native Compile을 사용하여 JVM 언어를 VM 위에서 동작시키지 않고 실제 장비 위에서 동작할 수 있도록 기계어로 작성된 Native 실행 파일로 미리 컴파일 할 수 있어 생성된 프로그램은 JVM에서 동작하는 기존 프로그램에 비해 시작 시간이 빨라지고, 실행 중에 사용하는 메모리가 그만큼 적다는 특징을 가지고 있습니다.

GraalVM Performance plot (Oracle)

특히 Oracle에서 발표한 GraalVM의 성능 차트는 R 언어에서 가장 최고의 성능를 보여주고 있습니다. 하지만 R 자체가 굉장히 퍼포먼스가 안좋은 언어인 것을 감안할 때, R 자체의 성능을 끌어 올려서까지 좋은 성능을 바래서는 안될 것입니다.

 

Polygot

GraalVM Supported Runtimes (http://www.graalvm.org/docs)

GraalVM은 JVM 중에서도 Polygot을 지원하는 VM이어서, 여러 런타임 환경을 제공하는 것도 가장 큰 장점이라고 할 수 있습니다. 이러한 VM의 장점을 채택하여 R에서는 JVM이 가지고 있는 JDBC 드라이버를 자유로이 사용해 JDBC를 지원하는 대부분의 RDBMS랑 연결하여 유연하게 데이터 처리를 할 수 있다는 점이 매우 큰 장점으로 꼽히고 있죠.

(물론 JDBC가 아니더라도 ODBC를 사용하는 방법도 있겠지만 설치나 설정이 까다로워서 사용하기 불편하다는 단점이 있죠.)

또한 R에서는 GraalVM을 사용함으로써 Python에서 제공하는 인기 라이브러리인 TensorFlow나 Keras를 R에서 직접 호출함으로써 데이터 분석자들이 R만을 사용해서도 쉽게 딥러닝 모델을 개발할 수도 있죠.

다만 아쉬운 점이 있다면, Linux, Mac OS X에서만 지원하고, Windows에 대한 지원 내용은 전혀 없다는 것이군요...

 

R and Java

그럼 이제 본론으로 넘어가볼까요? R 코드와 Java 코드를 두 가지 동시에 사용할 수 있다는 것을 알았습니다. 이제는 이를 코드로 구현하여 한 번 실행해보도록 하죠.

우리는 이를 구현하기 위해 rJava 패키지를 설치하고, Java 라이브러리의 사용을 위해 OpenJDK를 설치해야 합니다.

$ sudo apt install default-jdk

JDK 설치가 끝났다면 이제 rJava 패키지까지 설치하여 R과 Java 환경 구축을 마무리 지어봅시다.

install.packages('rJava')

 

How to use

자 이제 그럼 Java Class를 만들어보고 이를 R에서 호출해봅시다.

먼저 Java class를 만들고 이를 JVM에서 실행 가능한 JAR 파일로 만들어줍니다. 그러면 R에서 같은 경로로 되어 있는 jar 파일을 읽어서 해당 클래스를 불러올 수 있게 됩니다. Java 클래스를 호출하려면 위와 같이 rJava 패키지에서 제공하는 .jnew와 .jcall invoke method를 사용하시면 됩니다.

 

GraalVM + Spring

그렇다면 이렇게 성능 좋은 GraalVM을 순수 Java 코드용으로는 사용할 수 없을까? 물론 가능하다. 하지만 Spring 같은 경우에는 몇 가지 문제점이 있다. GraalVM은 Spring에서 필요한 Reflection 기능을 제공하지 않기 때문이다. 

 

What is Reflection ?

Reflection에 대해 잘 모르는 분들이 계실 수도 있기 때문에, 설명을 드리겠습니다. Reflection이란, 객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말합니다. 쉽게 말하자면 Runtime 시점에서 사용되는 객체의 타입을 결정하거나 알아낼 수 있는 능력이라고 보면 됩니다.

 

이 코드는 오류가 나는 코드입니다. 왜 그럴까요? 아마 일반적으로 Java 코딩을 해보신 분들도 문법이 틀렸다고 말할 수도 있겠지만 실제로 이 코드는 문법이 틀리진 않았습니다. 

Object 클래스는 모든 객체의 부모 클래스입니다. 따라서 이의 자식 클래스인 Book 클래스를 인스턴스로 담을 수는 있지만 사용 가능한 변수와 메소드들은 Book 클래스가 가지고 있기 때문에 Object 타입인 상태에서 이들의 리소스를 사용할 수 없어서 생기는 오류이죠.

그런데 실제로 Java 프로그래밍을 했을 때 흔한 일인가? 그렇지 않다. 보통의 Java 프로그래밍을 하게 된다면 이러한 케이스는 굉장히 보기 드물다. 하물며 JavaFX나 Swing을 가지고 GUI 프로그래밍을 할 때도 말이다.

이러한 케이스는 Spring Framework에서 볼 수 있습니다. Spring에서는 BeanFactory, 그리고 이를 상속한 ApplicationContext 두 가지 유형의 Spring Container를 제공하는데, BeanFactory는 스프링 설정 파일(applicationContext.xml)에 등록된 bean 객체를 생성하고 관리하는 가장 기본적인 컨테이너입니다. 여기에서 Reflection이 이용되는 순간은 Container가 구동될 때 객체를 생성하는 것이 아닌 클라이언트의 요청에 의해서만 객체가 생성되기 때문에 이 때 Reflection이 사용됩니다.

보통 이러한 형태로 객체와 객체 사이를 드나들다보면 하나의 객체에서 다른 객체의 변수나 메소드를 사용해야하는 경우가 생깁니다. 그럴려면 JVM에서는 객체 생성과 생성된 객체의 레퍼런스 정보가 필요한데, 이를 보통 Java 코드에서는 아래와 같이 new 생성자를 통해서 의존성을 주입시켜 줍니다.

이렇게 객체를 생성하는 것은 외부에서 새로운 Heap을 할당해 객체를 생성 부여하는 것으로 다른 객체를 통해서 객체를 할당해주는 것이 아닌 외부의 새로은 Heap을 할당받아 객체를 생성하는 것입니다.

실제로 Spring or Spring boot에서는 아래와 같은 방법으로 의존성을 주입시킬 수 있습니다.

이와 비슷한 어노테이션으로 @Resource 어노테이션을 사용하기도 합니다.

본론으로 넘어가서 이렇게 런타임 진행 과정에서 객체의 정보를 받아올 경우에는 반드시 Reflection을 사용해야 합니다. 왜냐하면 실행 이전에 바이트 코드로 번역되지 않아 레퍼런스의 정보가 없기 때문이죠.

Reflection이 어떻게 이루어지는지 알아보도록 하죠.

위 코드는 실제 Java 코드가 아닙니다. 예시를 설명해주기 위한 코드로 실제로 Reflection은 위와 비슷한 형태로 동작합니다. 예를 들어서 MethodsBean 클래스를 Reflection하여 의존성 주입을 하고자 한다면 Reflection API에서 제공하는 getClass(), getMethod() 함수를 이용해서 클래스의 레퍼런스들을 가져오게 됩니다. 이를 통해서 해당 메소드가 있을 겨우 호출하고, 없으면 오류 메시지를 띄워주는 것이죠. 

마치 R에서 Java 코드를 사용하는 것과 비슷한 케이스입니다. JVM에 로드된 클래스를 직접 부를 수 없기 때문에 이러한 비슷한 API를 GraalVM에서 제공해주고, rJava 패키지를 통해서 invoke 한 다음 객체를 생성하고 메소드를 호출할 수가 있는 것이죠.

하지만 공교롭게도 GraalVM에서는 이 API를 지원하지 않아 직접 Spring을 위에 올릴 수가 없다는 것입니다. ㅠㅠ

 

마치며..

5개월 만의 글인데, 뭔가 점점 훑어볼수록 더 써야하는데... 라는 마음가짐이 남는 글이었습니다. 이 글을 쓰게 된 계기는 제가 지난 1년 동안 의료 데이터 분야에서 일을 하면서 R로 데이터 분석을 주로 이루고, Java의 코드를 이용해서 그로 인한 부족한 부분을 뒷받침하는 경우가 굉장히 많았었기 때문이었습니다.

R 언어는 굉장히 느린 언어이지만 C++, Java, Javascript, Python 등 여러 프로그래밍 언어와 같이 혼용해서 쓸 수 있다는 점이 예전부터 마음에 걸렸었습니다. 어떻게 하면 여러 개의 인터프리터와 컴파일러를 두고 데이터 처리를 할 수 있는 것일까?

그 답은 제가 rJava 패키지를 사용해 Java 코드를 작성하고 혼용시킬 때 알 수 있었습니다. Spring boot를 이용해 Web API로 정의된 코드를 어떻게 유연하게 부를 수 있을까를 고민하였습니다. 물론 처음에는 REST API 라이브러리를 사용해서 호출하는 것으로 쉽게 생각했지만 이는 Web API가 동작하지 않으면 무용지물이었습니다. 따라서 그에 의존하는 Java 코드를 불러와 이를 리팩토링해서 다시 재활용해서 쓰는 것을 목표로 바꾸었죠.

하지만 문제가 있었습니다. R에서 사용하는 JVM은 우리가 보편적으로 알고 있는 Open Java Runtime이 아닌 다른 JVM을 사용하고 있었습니다. JSON 데이터 타입을 처리하는 중 오류를 발견하게 되어, 이 오류를 해결해보고자, GraalVM이라는 것을 알게 되었습니다.

그런데, GraalVM에 대한 한글 레퍼런스 문서나 그 어디에도 R과 GraalVM에 대해서는 자세히 다뤄져있는 글이 존재하지 않았습니다. Documentation을 보면서 이렇게 "Native 위에서 움직이는 JVM도 존재하는구나"라는 것을 알게 되었고 글을 쓰기 시작했습니다. 이 글을 쓰면서 R에서 작동하는 Java 코드의 성능이 어느 정도이고, 최적화된 JVM을 찾는다면 아주 좋은 글이 될 거라고 생각했습니다.

저 또한 처음 접해보는 VM이었고 이를 통해서 Java에 대해 좀 더 공부하는 계기가 되어서 좋았습니다. 앞으로도 더 쉽고 유익하고 좋은 글 많이 작성하려고 노력하겠습니다... (절대 말만 하지 않을게요.. 더 많이 글 작성하도록 하겠습니다 ㅠ)

 

TAGS.
comments powered by Disqus

Tistory Comments 0