ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 시퀀스 최적화
    개발/JPA 2023. 7. 10. 18:14

    GenerationType

    순차적으로 증가하는 id 값을 가지려고 할 때 GenerationType으로 TABLE, SEQUENCE, IDENTITY, AUTO가 있다.

    IDENTITY를 사용하면 테이블이 생성될 때 MySql은 auto_increment, H2Database는 identity라는 전략이 적용된다.

     

    데이터를 삽입할 때마다 id가 1씩 증가하며 따로 시퀀스를 만드는 건 아니다. JPA에서는 엔티티 매니저를 통해 영속성 관리를 하는데, 영속성 관리는 1차 캐시에서 관리되고 있는, DB와 동기화된 하는 데이터 집합이라고 볼 수 있다.

     

    문제

    영속성 관리를 위해 데이터는 id를 pk로 설정한다. JPA에서 commit 함수를 호출할 때의 기본 동작은 임시 저장소에 있던 query를 일괄적으로(batch 처리) DB로 보내는 것이다. id 정책과 같이 살펴보면 순서가 약간 맞지 않는다. commit 과정에서 1차 캐시에 있던 데이터들이 플러시되어 db로 query가 날아가야 하는데, 이는 이미 영속성을 가지는 데이터들이 id를 알고 있는 상태여야 한다는 의미다.

     

    identity 전략을 이용하면 데이터를 먼저 넣고 나서야 id를 알 수 있는데, 영속성 관리는 DB에 query를 적용하기 전에 일어난다. 그러므로 identity 전략을 이용하면 query가 commit 시점에 일괄처리되지 않고 데이터 삽입 때(persist) 바로 일어난다. 데이터 삽입 후 id를 조회하여 영속성 관리를 한다.

    System.out.println("====BEFORE====");
    manager.persist(member);
    System.out.println("member.id=" + member.getId());
    System.out.println("====AFTER====");
    
    tx.commit();

     

    해결

    이 현상을 문제로 정의한다면, 시퀀스를 통해 해결할 수 있다(GeneratinoType.SEQUENCE). 결론부터 이야기하면 시퀀스를 만들 때 allocationSize(기본값 50)를 통해, 필요한 시퀀스를 서버에 미리 올려두는 것이다.

     

    개발자가 수동으로 하는 것이 아니라 JPA가 친절하게 알아서 처리한다. 이 방법은 동시성 이슈가 없는 장점도 가진다. 여러 웹 서버에서 요청을 해도 필요한 만큼 시퀀스가 미리 확보가 되기 때문이다. 시퀀스 정책을 적용해 데이터 삽입 없이 테이블만 만들어보자.

    테이블 생성 로그
    h2database에서 확인한 시퀀스

    기본값이 1로 시작하고 50 단위 증가가 확인된다. 데이터를 하나 넣고 로그를 확인해보자.

    manager.persist(member1);

    첫 번째 데이터의 id는 1로 확인되며 call next value for MEMBER_SEQ 쿼리가 보인다.

    이를 콘솔에서 확인하면 다음과 같다.

    DB에는 시퀀스가 올라가 있지만 웹 서버에서는 1부터 차례대로 사용한다. 50만큼 미리 확보했다.

    데이터를 하나 더 넣어보자.

    id는 순차적으로 증가했는데, 시퀀스가 아직 51 근처에도 가지 않았는데 50만큼 한 번 더 올라가 100이 더 확보가 되었다. 최적화 때문인데, 현재 테스트 버전(hibernate-core:5.6.5.Final)에서는 이런 동작을 보이지만 5.3.10 버전에서는 50까지만 미리 확보한다. 어느 버전에서 현재 버전으로 바뀌었는지는 확인 안 했다.

     

    5.3.10 버전에서도 동일하게 call next value for MEMER_SEQ 두 번 호출은 동일하지만 시작 값이 -49이라서 첫 번째 호출 때 1이 되고, 그다음 호출 때 현재값이 51이 된다. 두 번 호출은 유지하고 서버에서 확보할 수 있는 시퀀스 값을 늘리는 것으로 최적화 성능을 올린 것으로 보인다.

     

    allocationSize를 높일수록 DB에 요청하는 횟수가 줄어들어 성능은 좋아지겠지만, 만약 서버를 재시작할 경우 확보한 시퀀스가 휘발되므로 시퀀스가 낭비된다. 요즘처럼 데이터가 넉넉한 시대라고 해도 낭비는 낭비다. 마지막으로 데이터를 하나만 더 넣어보자.

    이제 call next value for MEMBER_SEQ는 호출되지 않았다. 시퀀스가 102가 되어야 할 때 DB의 현재값은 151이 되고 서버는 50만큼 확보하게 된다.

    '개발 > JPA' 카테고리의 다른 글

    페치 조인의 한계과 극복(요약 ver.)  (0) 2023.07.25
    MVCC=TRUE  (0) 2023.07.18
    일대다 연관관계를 지양하자  (0) 2023.07.18
    영속성 전이와 고아 객체  (0) 2023.07.13
    양방향 관계 시 주의사항  (0) 2023.07.11

    댓글

Designed by Tistory.