개발을 하던 중 자바 직렬화로 인한 문제가 발생했고 이에대해 공부한 내용을 정리하기위해 작성하였다. 우아한 형제들의 기술블로그를 참고하였고 지난 개념편에 이어 실제로 프로그램에 적용했을때 어떤 문제들이 생기는지 알아보았다.


자바 직렬화 실제 프로젝트에서 사용하기

JSON, CSV 와 같은 형태의 포맷을 이용하면 직렬화 또는 역직렬화 시에 특정 라이브러리를 추가해야 쉽게 개발이 가능하고, 구조가 복잡하면 직접 매핑시켜줘야 하는 작업도 포함해야 한다. 그것에 비해 자바 직렬화는 비교적 복잡한 객체도 큰 작업 없이 java.io.Serializable 인터페이스를 구현해 주는 것으로 기본 자바 라이브러리만을 사용하여 직렬화와 역직렬화를 할 수 있다. 하지만 몇 가지 신경써주어야 하는 문제들이 있다.

역직렬화시 클래스 구조 변경 문제

class Message implements Serializable {
    private String user_name;
    private String content;

    public Message(String user_name, String content) {
        this.user_name = user_name;
        this.content = content;
    }
}

지난 포스팅에서 예시로 들었던 클래스인데 위와 같이 객체를 구성한 경우 (직렬화할 대상이 되는) 객체에 변화가 생길 시에 문제가 생기게 된다. 지난 포스팅에서 언급했던 역직렬화의 조건에서 자바 직렬화의 대상이 되는 객체는 동일한 SerialVersionUid를 가져야한다는 부분에 위배되어 생기는 문제이다.

serialVersionUID는 따로 선언하지 않을 시 클래스에 변동사항이 생길 때마다 매번 새로 생성 되는데 이때 serialVersionUID의 값은 클래스의 기본 해쉬값으로 사용된다. 해쉬값은 클래스 구조 정보를 이용해서 생성되므로 구조가 변경 될 시 serialVersionUID도 바뀌게 되어 문제가 생기는 것이다.

일반적으로 개발시에 새로운 멤버변수가 추가되더라도 기존에 존재했던 데이터들에는 null값이라도 추가되어 새로운 객체형태와 호환되기를 바란다. 따라서 조금이라도 역직렬화 대상 클래스의 구조가 바뀔 경우 에러가 발생해야 할 정도로 민감한 시스템이 아닌 경우엔 클래스내부에서 직접 serialVersionUID값을 관리해 주어야 변경 시에 혼란을 줄일 수 있다.

그러나 serialVersionUID값이 동일하더라도 멤버변수의 타입이 바뀌는 경우에는 역직렬화시 오류가 발생한다. 또한 변수의 이름을 바꾼 경우에는 기존의 값들이 모두 누락되기때문에 변화에 굉장히 민감하고 이 부분은 개선할 수 있는 사항이 아니기때문에 상황에 맞춰서 써야만 한다.

자바 직렬화시 신경 써야 할 부분들

  • 자바 직렬화 버전(serialVersionUID) 값은 직접 관리해야 한다.
  • 멤버변수를 삭제하거나 멤버변수의 이름을 변경한 경우 에러는 발생하지 않지만 데이터가 누락된다.
  • 역직렬화 대상 클래스의 멤버 변수 타입 변경은 지양해야한다.
  • 외부(DB, 캐시 서버, NoSQL 서버 등)에 장기간 저장될 정보는 자바 직렬화 사용을 지양해야 한다. 역직렬화 대상의 클래스가 언제 변경이 일어날지 모르는 환경에서 긴 시간동안 외부에 존재했던 데이터는 쓰레기가 될 가능성이 높고 언제 예외가 발생할지 모르는 지뢰 시스템이 될 수도 있다.
  • 프레임워크 또는 라이브러리에서 제공하는 클래스의 객체와 같이 개발자가 직접 컨트롤하기 힘든 객체는 직렬화를 지양해야 한다. (Spring Security의 SecurityContextImpl클래스와 같이 객체가 직접 serialVersionUID를 가지고 있는 경우도 있으나 버전업을 하면서 변경될 수 있기 때문에 혹시 모를 오류를 방지하기 위해서라도 사용하지 않는 것이 좋다.)

용량 문제

자바 직렬화시에 기본적으로 타입에 대한 정보 등 클래스의 메타 정보도 가지고 있기 때문에 다른 데이터 포맷에 비래서 용량이 큰 문제가 있다.
특히 클래스의 구조가 거대해지게 되면 용량 차이가 커지게 된다. 클래스 안에 클래스, 리스트 등이 있는 형태의 객체를 직렬화 하게 되면 내부에 참조하고 있는 모든 클래스에 대한 메타정보를 가지고 있기 때문에 용량이 비대해지게 된다. 그래서 JSON같은 최소의 메타정보만 가지있는 데이터포맷에 비해 같은 데이터에서 최소 2배 최대 10배 이상의 크기를 가질 수 있다.

용량문제는 직렬화된 데이터를 메모리 서버에 저장하는 형태를 가진 시스템에서 두드러지는데 메모리 서버 특성상 메모리 용량이 크지 않기 때문에 핵심만 요약해서 기록하는 형태가 효율적이다.
적은 데이터만 입력하는 시스템 구조라면 큰 문제는 발생하지 않지만 트래픽에 따라 데이터 기록이 급증하는 시스템은 유의해야 한다. 새롭게 시작한 서비스의 경우 초반에는 생산성을 위해 자바 직렬화를 그대로 이용한다고 해도 지속적으로 트래픽이 증가할 때에는 JSON형태 또는 다른 형태의 직렬화로 바꾸는 것을 고려해보아야 한다.

결론

  • 자바 직렬화를 사용할 때에는 자주 변경되는 클래스의 객체는 사용하지 않는 것이 좋다. 변경에 취약하기 때문에 생각지도 못한 예외사항들이 발생할 가능성이 높다.
  • 역직렬화는 예외가 생길 가능성이 굉장히 높기 때문에 역직렬화 되지 않을 때와 같은 예외처리는 기본적으로 해두는 것이 좋다.
  • 외부 저장소로 저장되는 데이터는 짧은 만료시간의 데이터를 제외하고 자바 직렬화 사용을 지양해야 한다.
  • 긴 만료시간을 가지는 의미있는 데이터는 JSON 등 다른 포맷을 사용하여 저장한다.