티스토리 뷰

jackson 을 이용할땐 나 같은 경우 항상 ObjectMapper 클래스를 이용했다. 그러다가 Serialization/Deserialization 에 각각 특화된 ObjectWriter/ObjectReader 클래스가 있다는걸 알게됐는데 ObjectMapper 를 사용하는 것과 어떤 차이가 있는지 알아보기로 했다.

 

먼저 ObjectMapper 의 가장 치명적인 문제는 스레드에 안전하지 않다는 것이다. 멀티 스레드 환경에서 공유 변수로 사용하게 될 경우 원치 않는 결과가 나올 수 있다. ObjectMapper 의 javadoc 에는 이렇게 적혀있다.

 

Mapper instances are fully thread-safe provided that ALL configuration of the instance occurs before ANY read or write calls. If configuration of a mapper instance is modified after first usage, changes may or may not take effect, and configuration calls themselves may fail.

 

사용 전에 모든 설정이 완료된게 아니라면 문제가 생길 수 있음을 얘기하고있다. ObjectMapper 를 멀티 스레드 환경에서 사용시 문제가 생기는 케이스를 알아보자.

 

// 테스트 대상 클래스
public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

// 테스트 코드
ObjectMapper objectMapper = new ObjectMapper();                           
Person person = new Person("LichKing");                                   
                                                                          
ExecutorService es = Executors.newCachedThreadPool();                     
                     
// 첫번째 스레드에서는 INDENT_OUTPUT 기능을 활성화 시키면서 json 출력
es.execute(() -> {                                                        
    for(int i = 0; i < 10; i++) {                                                                                      
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);      
        String json = objectMapper.writeValueAsString(person);        
        System.out.println("new line!!\n" + json);                    
    }                                                                     
});                                                                       

// 두번째 스레드에서는 INDET_OUTPUT 기능을 비활성화 시키면서 json 출력
es.execute(() -> {                                                        
    for(int i = 0; i < 10; i++) {                                                                                              
        objectMapper.disable(SerializationFeature.INDENT_OUTPUT);     
        String json = objectMapper.writeValueAsString(person);        
        System.out.println("one line!!\n" + json);                    
    }                                                                     
});                                                                       
                                                                          
es.shutdown();

 

두 개의 스레드에서 ObjectMapper 를 공유하고, 내부 상태를 변경하면서 사용하고있다. 두 스레드에서 기대 출력값은 이렇다.

 

// INDENT_OUTPUT 활성화
new line!!
{
  "name" : "LichKing"
}

// INDENT_OUTPUT 비활성화
one line!!
{"name":"LichKing"}

 

실제로 위 코드를 실행해보면 이렇게 출력되는 경우가 있었다. 멀티스레드 환경이니 환경별로 결과가 다를 수 있음을 유의하자.

 

// 기대와 다르게 출력
new line!!
{"name":"LichKing"}
one line!!
{"name":"LichKing"}

 

이는 ObjectMapper 가 내부 상태를 변경하는 가변 객체이기 때문에 발생하는 문제다. ObjectWriter/ObjectReader 는 이런 문제를 해결하고자 했고, 불변 객체이기 때문에 멀티 스레드에 안전하다.

ObjectMapper objectMapper = new ObjectMapper();                                    
// 스레드간에 공유해서 사용할 객체
ObjectWriter objectWriter = objectMapper.writerFor(Person.class);                  
Person person = new Person("LichKing");                                            
                                                                                   
ExecutorService es = Executors.newCachedThreadPool();                              
                                                                                   
es.execute(() -> {                                                                 
    for(int i = 0; i < 10; i++) {                                                                                                                       
        String json = objectWriter.with(SerializationFeature.INDENT_OUTPUT)    
                .writeValueAsString(person);                                   
        System.out.println("new line!!\n" + json);                             
    }                                                                              
});                                                                                
                                                                                   
es.execute(() -> {                                                                 
    for(int i = 0; i < 10; i++) {                                                                                                                
        String json = objectWriter.without(SerializationFeature.INDENT_OUTPUT) 
                .writeValueAsString(person);                                   
        System.out.println("one line!!\n" + json);                                                                                          
    }                                                                              
});                                                                                
                                                                                   
es.shutdown();

 

ObjectWriter 는 불변 변수이기 때문에 with/without 메서드를 통해 상태를 변경하면 내부 상태 변경이 아니라 상태가 변경된 복사본을 반환한다. 그렇기 때문에 멀티 스레드에 안전할 수 있다. 또한 ObjectWriter 를 만들때 writerFor 라는 팩토리 메서드를 호출하면서 미리 타입을 전달하고 있기 때문에(writerFor(Person.class)) 성능에도 약간 이점이 있다고 한다. 하지만 큰 향상은 아닌 것 같으니 굳이 성능 때문에 꼭 써야한다까진 아닌 듯 싶다.

 

# 정리

ObjectMapper 가 멀티 스레드에 안전하지 않다고 얘기했지만, 거의 대부분의 상황에서 ObjectMapper 를 멀티 스레드 환경에서 이용하고 있을 것이다. 그럼 깜짝 놀라며 모든 코드를 다 바꿔야하나 싶은 생각이 들지만 ObjectMapper 의 설정을 동적으로 바꾸는게 아니라 초기에 한번 설정해놓고 serialization/deserialization 만 멀티스레드에서 하는거라면 사실 ObjectMapper 를 이용해도 문제가 없다. 실제로 나도 개발자 생활 해오면서 무수히 많은 ObjectMapper 를 만들었지만 멀티 스레드 환경에서 동적으로 설정을 변경하면서 하나의 인스턴스만 이용한 적은 한번도 없었다. 그런 케이스가 꽤나 특수한 케이스임을 생각해본다면 지금껏 사용해왔던 습관대로 사용해도 큰 문제는 없을 것으로 생각한다. 다만 멀티 스레드에서 설정을 변경해가면서 써야하는 상황이 온다면 ObjectWriter/ObjectReader 사용을 고려해보자. 그리고 특정하게 정해진 타입만 핸들링하는 경우에도 사용을 고려해보면 좋을 것 같다.

 

참고자료

- https://github.com/FasterXML/jackson-databind/blob/2.16/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java

- https://cowtowncoder.medium.com/jackson-api-beyond-objectmapper-22b6d3bbd1ef

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함