본문 바로가기
Programming/Study

JPA Indexing은 개념에 맞게 사용해 중복을 피하자

by JKROH 2024. 4. 15.
반응형

 JPA에서는 DB Indexing기능을 아주 간단하게 제공해준다. 혹시 DB Indexing에 대해 잘 모른다면 해당 영상을 참고하도록 하자. 한 줄로 요약하자면, 데이터가 수억개 있을 때, 특정 값을 검색하기 편하게 트리 형태로 인덱스 칼럼을 따로 저장해서 검색을 좀 더 빠르고 간결하게 하겠다는 것이다. 조금 깊이 알고 싶으면 해당 영상을 참고하면 좋다. 

 

 예를 들어, 1억 개의 데이터가 있는데 이 중에 id = 3524125인 값을 SELECT해주세요. 하면 1억 개를 전부 뒤져야한다. 그러나 인덱스에 해당 칼럼을 저장해놓으면 업다운 게임처럼 트리를 이용해서 훨씬 빠른 시간에 찾을 수 있다. 특히 관계형 DB를 사용해 데이터를 영속화하는 경우에는 하나의 테이블에 꽤 많은 데이터가 저장되기 때문에, 인덱스를 사용해 검색 성능 증가를 꾀하는 것이 중요하다. 결국 서버 레벨에서 오랜 시간을 요하는 것은 데이터를 검색하는 경우에 많이 있다.

 

 JPA를 사용하면 인덱스를 쉽게 걸 수 있는데, @Table애너테이션을 통해 테이블 정보를 정의할 때 인덱스도 함께 걸어주면 된다. 아래 예시와 같이 말이다.

@Table(name = "EMPLOYMENT", indexes = @Index(name = "IDX__BARBER__SHOP__ID", columnList = "WORK_PLACE_ID"))

 

 위 코드에는 적어놓지 않았지만, index 칼럼을 unique하게 설정할 것이냐 그렇지 않게 할 것이냐에 따라 설정도 가능하다.

 

 아무튼 위 테이블은 EMPLOYMENT 테이블 즉, 고용 정보가 담긴 테이블이다. 해당 테이블은 앞선 포스팅에서 다뤘던 테이블로, 회원 - 바버샵 간 다대다 고용 관계를 해소하기 위해 사용된다. 그런데 EMPLOYMENT를 검색하는 것은 바버샵에서 해당 샵의 바버들에 대한 정보를 확인할 때 뿐이니 바버샵의 ID를 index로 사용해 검색에 이점을 꾀한 것이다. 인덱스를 걸 칼럼을 선정하는 기준은 링크에서 확인할 수 있다. 해당 칼럼은 카디널리티는 높지만, 선택도가 낮고 활용도가 높다.

 

 해당 중간 테이블 코드는 아래와 같다.

@Entity
@Getter
@Table(name = "EMPLOYMENT", indexes = @Index(name = "IDX__BARBER__SHOP__ID", columnList = "WORK_PLACE_ID"))
public class EmploymentEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long employmentId;

    @Column(name = "WORKER_ID")
    private Long memberId;

    @Column(name = "WORK_PLACE_ID")
    private Long barberShopId;
}

 

 그런데 바버샵과 회원이 다대다 관계를 맺는 또 하나의 개념이 있다. 바로 예약이다. 한 명의 회원이 여러 바버샵에 예약할 수 있고, 바버샵은 여러 회원의 예약을 받을 수 있으니 다대다 관계이며, 이를 해결하기 위해 예약이라는 테이블이 필요하다. 그래서 아래와 같이 예약 엔티티를 만들었다.

@Entity
@Getter
@Table(name = "RESERVATION", indexes = @Index(name = "IDX__BARBER__SHOP__ID", columnList = "BARBER_SHOP_ID"))
public class ReservationEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long reservationId;

    @Column(name = "CUSTOMER_ID")
    private Long memberId;

    @Column(name = "BARBER_SHOP_ID")
    private Long barberShopId;
}

 

 이제 프로젝트를 실행시켜보면... 짜잔!

org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "
    create index IDX__BARBER__SHOP__ID 
       on reservation (barber_shop_id)" via JDBC [Index "IDX__BARBER__SHOP__ID" already exists;]
	at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:94) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
	at org.hibernate.tool.schema.internal.Helper.applySqlString(Helper.java:233) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
	at org.hibernate.tool.schema.internal.Helper.applySqlStrings(Helper.java:217) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
	at org.hibernate.tool.schema.internal.SchemaCreatorImpl.createTableConstraints(SchemaCreatorImpl.java:383) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
	at org.hibernate.tool.schema.internal.SchemaCreatorImpl.createSequencesTablesConstraints(SchemaCreatorImpl.java:351) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]
	at org.hibernate.tool.schema.internal.SchemaCreatorImpl.createFromMetadata(SchemaCreatorImpl.java:239) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]

...

 

 이런 에러 문구가 뜬다. 척 보아하니 index가 중복되는 에러가 발생했다. 왜 그럴까? 이유는 단순하다. Index도 결국 하나의 테이블로 저장되기 때문이다.

 

 즉, 두 테이블에 인덱스로 건 두 항목의 이름이 같다는 것은 같은 이름의 테이블 두 개를 만드는 것과 같다. 당연히 중복이 발생해 두 테이블 중 하나는 생성되지 않는다. 그럼 둘의 이름을 개념에 맞게 바꿔주어야 할 것이다.

 

 Employment에서 사용하는 바버샵ID는 근무지의 값이다. 따라서 workPlaceId라는 변수명을 사용하고 있으며, 인덱스에도 해당 이름을 적용하는 것이 더 좋을 것이다. 반면 Reservation에는 member - barberShop이 개념적으로 옳다. 따라서 이름을 그대로 사용하는 것이 더 좋다.

 

 짧게 Index의 개념, 사용 이유, 사용 방법 등을 알아보았다. 조회 성능 개선은 모든 서버 개발자의 숙명과도 같으니 Index에 대해서도 좀 더 깊이 알고 사용하면 좋겠다.

반응형

댓글