(아래의 예시 코드들은 실제 프로젝트의 내용을 간소화했습니다)
프로젝트를 진행하다 보면 간혹
DB에 초기데이터를 먼저 올린 다음 백엔드 애플리케이션을 실행해야 할 때가 있다.
최근 진행한 프로젝트 내에서 근무시간표 관련 데이터를 다룰 일이 있었는데
팀원이 작성한 다음과 같은 설계를 발견했다.
'요일' 과 관련한 데이터를 미리 DB에 넣어놓고, 해당 데이터를 참조하게끔 하는 형식이었다.
그 서비스는 다음과 같은 sql 스크립트의 실행을 필요로 했다.
# 스크립트 예시. 'Calendar' 테이블에 요일 정보를 넣는다.
insert into 'Calendar' values (..., '월'),(..., '화'),(..., '수'),(..., '목'),(..., '금');
'Calendar' 테이블과 연결된 자바 클래스에는
요일을 의미하는 필드가 단순 String 형식으로 지정되어 있었는데,
1. '요일'은 절대 변하지 않는다.
2. 단순 String 형식 입력에서 발생 가능한 휴먼에러를 줄여야 한다.
위의 2가지 이유로 필드의 String타입을 Enum 타입으로 바꿨다.
String -> DayOfWeek
예시는 다음과 같다.
@RequiredArgsConstructor
public enum DayOfWeek {
MON("월"), TUE("화"), WED("수"), THU("목"), FRI("금");
@JsonValue
public final String value;
}
}
이렇게 Enum을 사용해 단순 String 타입으로 관리함에서 일어나는 실수를 줄일 수 있었다.
방금 말한 'Calendar' 테이블 말고도
필수 초기데이터 테이블은 하나 더 존재했다.
'Room' 이라는 테이블인데, 이 테이블의 한 행은
건물의 각 호실을 나타낸다.
그래서 또다시 아래와 같은 스크립트를 실행해야 했다.
# 이런 식으로 약 300행
insert into 'Room' values ('D0001'), ('D0002'), ('D0003')...;
'방 번호' 와 '날짜'
두 가지 데이터의 공통점은 무엇일까?
'개발자가 직접 값을 건드리지 않는 이상, 바뀔 일이 거의 없다'
-> 이는 각각의 값이 클라이언트의 요청으로는 절대로 update, insert, delete되지 않는다는 의미이다.
-> 딱 '참조용 초기 데이터' 역할만 하기 때문에 초기 데이터의 크기가 연결된 애플리케이션의 런타임에 변하지 않는다는
뜻.
그래서 이런 생각을 했다.
'테이블의 값들을 전부 ENUM 객체로 옮겨서 관리한다면 어떨까?
어차피 절대로 변하지 않을 데이터라면 소스코드에 내용을 모두 적어놓고, DB가 아닌 메모리에서 읽어오는 것이
훨씬 효율적이지 않을까?'
이미 잘 돌아가고있는 구조를 확실한 근거 없이 뒤엎기는 좀 무서우므로
우리 AI님에게 먼저 조언을 구해 보았다.
질문

답변

답변을 듣고 나니. 내가 리스크가 큰 변화를 꾀하는 건가? 하는 생각이 들었다.
또한, 메모리에 저장된 데이터가 '애플리케이션 재시작 시마다 초기화' 된다는 말이
잘 이해가 가지 않았다.
Enum 객체는 한번 사용한 후 참조가 끊어지면 Garbage Collecting 되지 않는 건가?
의문이 생겨서 생성자에 임시로 콘솔창 출력 코드를 작성하고,
'DayOfWeek.Mon' 객체를 불러온 뒤 콘솔창을 확인했다.
# 생성자
DayOfWeek(String value) {
System.err.println("DayOfWeek value: " + value);
this.value = value;
}
내 예상대로라면
'DayOfWeek value: 월'
과 같은 데이터가 출력되었어야 할 것이다.
DayOfWeek value: 월
DayOfWeek value: 화
DayOfWeek value: 수
DayOfWeek value: 목
DayOfWeek value: 금
하지만 결과는 달랐다.
나는 코드상에서 'MON(월요일)' 객체만을 불러왔는데도
MON-PRI 까지, ENUM 클래스에 선언된 모든 객체를 대상으로 생성자를 실행하는 것이었다.
이게 뭔가 싶어서 헐레벌떡 다시 AI님께 조언을 구했다.

정리하자면,
1. ENUM은 클래스, 각각의 상수들은 모두 인스턴스이다.
2. 이들은 애플리케이션 시작 시 초기화되고 그만큼 Heap Area를 차지한다.
3. 애플리케이션이 종료될 때까지 이들은 메모리에서 내려가지 않는다.
4. Lazy Initialization 이라는 메커니즘 때문에, 정확히 애플리케이션을 켜자마자 초기화를 진행하지는 않고
어떤 Enum 클래스의 상수를 '최초 호출' 하는 시점에 같은 Enum 클래스의 다른 상수들도 같이 초기화된다.
(Enum 초기화 시점과 Lazy Init의 경우에는 정확한 출처를 찾기 어려웠다.
https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4.1
AI님은 위의 링크를 근거로 제시하셨는데, ENUM에 대해서 다루는 부분은 문서상에 존재하지 않았다.
다만 여러 곳의 stackoverflow 답변도 같은 말을 하고 있어(일부는 조금 다른 말을 하기도 하지만)
일단 그렇게 알고 넘어가기로 하였다.
자바 표준상 정의된 바가 없고 벤더마다 다르게 구현되는 것일까?)
여기까지 왔으니, 처음 AI님이 제안해주신 주의사항을 다시 읽어보자.

주의사항
1. ENUM 객체는 애플리케이션이 켜져 있는 한 GC되지 않고 메모리를 계속 차지할 것이다.
2. 메모리에 저장되기 때문에 서버가 여러 대일 때 주의가 필요하다.
라고 하는데,
1번의 경우는 현재 수준에서 ENUM 객체의 메모리 차지량이
서버 성능을 저하할 정도는 아니겠지만
DB에 데이터 보관하듯이 ENUM을 사용하는 시도는 무리가 있다는 것을 알 수 있다.
2번의 경우는 '자바 프로세스 하나당 메모리를 잡아먹음' 의 문제보다는
'데이터 동기화 문제'가 핵심인 듯 한데
싱글턴인 ENUM은
객체 내부의 필드를 기본적으로 final로 사용한다면 '런타임에 데이터가 변경되어'
발생하는 불일치 문제는 막을 수 있다.
(다만 AI님의 답변대로 '데이터의 관리'를 Application이 맡게 된다.
그 예시로,
ENUM 필드를 변경하고 서버를 재시작할 때 서비스를 중단하지 않으려면
여러 인스턴스를 순차적으로 down시키고 다시 켜야 할 것 같은데
이 과정에서 잠깐동안의 데이터 불일치가 일어나는, 그런 문제가 생길 수 있다는 생각은 든다.)
이렇게 ENUM의 생명주기에 대해서 다시 알고 나니,
많은 데이터를 ENUM으로 관리하는 것은 무리가 있을 것 같아 보인다.
우리 컴퓨터는 나약하지 않기에
내 프로젝트에 적용시킬 결론은 이렇게 내기로 했다.
1. 요일 데이터
이 데이터를 메모리에서 관리한다면,
30개정도의 Enum 상수를 생성하게 된다.
(요일과 시간의 조합 때문에, 월~금 5개로 끝나진 않는다).
이 정도의 숫자는 DB가 아닌 메모리에서 관리해도 괜찮을 것으로 보여서
Enum을 도입하기로 했다.
일단 String -> Enum 사용으로 구조를 변경하긴 했는데
이 기능은 내가 담당한 기능이 아니기 때문에
관련된 모든 설계를 함부로 갈아엎을 순 없어서
해당 기능을 개발한 동료에게 저장방식을 바꾸는 것도 좋아보인다고 제안만 하는 선에서 그치기로 했다.
(그래서 현재는 Enum과 DB에 모두 정보를 저장한다)
2. 방 번호 데이터
이 데이터를 메모리에서 관리한다면,
300개가 넘는 Enum 상수를 생성하게 된다.
이는 역시 메모리에 부담이 될 만한 크기는 아닐 것으로 생각된다.
그런데 문제는 메모리가 아닐 수도 있다는 생각이 들었다.
수백개가 넘는 데이터를 Enum에서 관리한다는 것은
해당 enum 클래스를 사용하는 사람이
'300개의 Enum 상수' 와 마주쳐야 한다는 것을 의미하기도 한다.
내가 짠 Enum 클래스 내부의
몇백개나 되는 상수를 마주친 다른 개발자는
내 코드를 보고 혼란에 빠질 수도 있겠다는 생각이 들었다.
그래서 '방 번호' 데이터는 DB에만 저장하기로(기존 방식 유지) 했다.
위의 내용을 다시 요약하여,
본 포스팅 과정에서 프로젝트에 생긴 변화를
Before-After로 정리해 보겠다.
요일 데이터
Before
- App: String으로 저장
- DB: 테이블에 저장
After
- App: Enum으로 저장하는 식으로 리팩토링 진행
- DB: 지금은 테이블에'도' 저장하나, 더이상 DB에서 저장하지 않는 방향을 고려할 수 있음
방 번호 데이터
Before
- App: String으로 저장
- DB: 테이블에 저장
AFTER
- BEFORE와 같음