티스토리 뷰

Java & Kotlin

[JVM] Jackson ObjectMapper의 성능을 높여줄 Blackbird 모듈

망나니개발자 2024. 4. 30. 10:00
반응형

 

 

 

1. Jackson ObjectMapper의 성능을 높여줄 Blackbird 모듈


[ Blackbird 모듈이란? ]

기존에 Jackson 라이브러리의 성능 향상을 위해 Jackson Afterburner 모듈이 사용되고 있었다. 하지만 Afterburner 모듈은 끔찍한 바이트코드 조작을 사용하고 있었고, 곧 지원이 중단되는 Unsafe.defineClass 를 사용하고 있었다. 즉, 최신 JVM(JVM 11+)와의 호환성이나 쓰기 접근 제한 등을 고려하여 노후화 징후를 보이고 있었던 것이다.

따라서 이를 대체하기 위한 새로운 모듈이 등장하게 되었다. Jackson 2.1.2 릴리스(2020년 12월)와 함께 등장한 Blackbird 모듈은 뛰어난 성능 뿐만 아니라 새로운 구조(ex LambdaMetafactory)를 사용하여 최신 JVM과 더 잘 작동하도록 설계되었다.

 

 

 

[ Blackbird 모듈의 성능 향상 ]

Jackson Blackbird가 성능 상의 이점을 갖게 된 이유는 LambdaMetafactory를 도입했기 때문이다. LambdaMetafactory는 함수 객체를 동적으로 인스턴스화하기 위한 자바 표준 API이다. LambdaMetafactory를 사용하면 필요한 어댑터를 간단한 자바 코드로 작성하고 metafactory를 사용하여 모든 액세스 경로에 대해 별도의 호출 사이트를 만들 수 있다. 이를 통해 각 접근자가 단일화된 호출 프로필을 가질 수 있고 성능을 극대화하기 위해 쉽게 인라인화 할 수 있다.

Blackbird 모듈이 LambdaMetafactory를 이용함에 따라 주의할 부분도 있다. 자바에서 람다의 구현은 각 람다 객체와 각 대상 클래스의 클래스 로더를 강하게 연관시킨다. 즉, Blackbird 모듈 객체가 클래스에 대해 동적인 접근자를 클래스에 추가한다는 것이다.

따라서 ObjectMapper 객체를 많이 만들면 각각의 객체가 접근자를 클래스에 추가하므로 클래스 공간이 부족하여 OOM이 발생할 수 있다. 따라서 Blackbird 모듈을 사용하는 경우에는 ObjectMapper를 최소화하여 사용하는 것이 바람직하다.

Blackbird가 구체적으로 최적화된 내용들을 살펴보면 다음과 같다.

  • 직렬화 케이스(Serialization, POJO to Json)
    • Getter 메서드 호출이 리플렉션 대신 생성된 람다를 사용하여 인라인 처리됨
    • 소수의 원시 타입(int, long, boolean, String)에 대한 직렬화기는 JsonSerializer에 위임되지 않고 람다에 특화된 생성기로 대체됨
  • 역직렬화 케이스(Deserialization, Json to POJO)
    • 기본 생성자에 대한 호출이 리플렉션이 아닌 람다로 처리됨
    • 인자가 있는 @JsonCreate 팩터리 메서드와 생성자에 대한 호출이 덜 효율적인 람다 기반 구현이 됨
    • Setter 메서드 호출이 리플렉션 대신 생성된 람다를 사용하여 인라인 처리됨
    • 소수의 원시 타입(int, long, boolean, String)에 대한 역직렬화기는 JsonDeserializer에 위임되지 않고 람다에 특화된 생성기로 대체됨

 

성능에 대한 벤치마크 자료는 다음과 같은 링크에서 살펴볼 수 있다.

 

 

 

[ Blackbird 모듈 사용법 ]

먼저 다음과 같이 해당 모듈에 대한 의존성을 추가한다.

implementation("com.fasterxml.jackson.module:jackson-module-blackbird")

 

 

이후에 ObjectMapper에 다음과 같이 모듈을 추가해주면 된다.

ObjectMapper mapper = JsonMapper.builder()
    .addModule(new BlackbirdModule())
    .build();

 

 

참고로 Blackbird 모듈은 Java8에서도 잘 동작하지만, 특히 이후의 JVM 버전을 위해 권장된다.

Jackson의 ObjectMapper는 직렬화 또는 역직렬화 시에 java.lang.reflect 의 패키지의 Constructor나 Method를 이용한 리플렉션 기술을 사용한다. 문제는 리플렉션이 다음과 같은 추가 리소스를 사용하게 된다는 점이다.

  • 타입 캐스팅(파라미터 타입과 반환 타입이 정해져 있으므로)
  • 권한 체크
  • JIT을 통한 최적화 불가능

 

 

Blackbird 모듈은 이러한 문제를 해결하기 위해 LambdaMetaFactory를 만들어 동적으로 실제 메서드를 호출하게 하여 위의 단점을 해결하였다. 물론 이러한 방식으로 인해 리플렉션을 거의 사용하지 않거나(예를 들어 생성자를 기반으로 객체를 생성하는 경우) 또는 자바의 컬렉션(Map, List 등)을 사용하는 경우에는 성능 상의 이점을 얻지 못할 수 있다.

또한 Blackbird 모듈을 사용하는 경우에는 ObjectMapper를 최소화하여 사용하는 것이 바람직하다.

따라서 개선 상황이 서비스 상에서 눈에 띌 정도로 개선되지 못할 수는 있지만, 적용하기 쉽고 Jackson에서 충분히 관리하고 있으며, 개선이 확실하기 때문에 적용하는 것이 좋을 것이다.

 

 

 

 

 

참고 자료

 

 

 

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함