개발 낙서장

[TIL] 내일배움캠프 61일차 - 참조키 인덱싱 본문

Java/Sparta

[TIL] 내일배움캠프 61일차 - 참조키 인덱싱

권승준 2024. 3. 25. 21:16

 

 

오늘의 학습 키워드📚

인덱스

DB에서 인덱스는 매우 중요하다. 인덱스란 한국어로 색인이며 특정 데이터가 어디에 있는지 표시해 주는 항목을 말한다.
DB에서는 특정 컬럼 혹은 칼럼들을 인덱스로 설정하면 해당 조건을 통해 데이터를 정렬해 조회할 때 성능을 향상해 준다.
하지만 반드시 인덱스가 존재한다고 해서 성능이 향상되는 것은 아닌데 MySQL에서 일반적으로 인덱스는 B-Tree 인덱스 방식이 사용되기 때문이다.

B-Tree 인덱스

B-Tree 자료구조란 2개의 자식을 갖는 이진트리를 확장해 N개의 자식 노드를 갖는 트리 자료구조이다.
하나의 노드에 여러 개의 값을 가질 수 있으며 2개 이상의 자식 노드를 가질 수 있는 트리 구조이다.

가장 최상위 노드인 루트 노드, 중간 노드인 브랜치 노드, 가장 최하위 노드인 리프 노드로 구성돼 있다.

B-Tree 자료구조로 인덱스를 저장하면 좋은 점은 Up-Down을 통해 아주 빠르게 원하는 데이터를 찾을 수 있다.

출처 : B - Tree Datastructure(이미지 클릭)

각 숫자를 데이터의 번호(MySQL에서 PK)라고 가정해 보자.

SELECT * FROM TABLE T
WHERE T.ID = ?1;

위와 같은 쿼리를 날린다고 가정해 보자. 인덱스가 적용돼있지 않은 DB에서 만약 ID가 15인 값을 Select 한다면
1.. 3.. 7.. 8.. 15!
5번의 탐색 끝에 찾아낼 것이다. 하지만 ID가 85인 값을 Select 한다면?
1.. 3.. 7.. 8.. 15.. 21.. 23 ....................... 77.. 85!
세어봤는데 총 26번의 탐색 끝에 찾아낸다.
이는 데이터의 양이 많아질수록 탐색 횟수는 기하급수적으로 증가하기에 성능적으로 아주 불리하다.

하지만 위의 사진처럼 B-Tree 인덱스가 적용된 DB라면 어떤 값을 탐색하던 매우 빠르게 탐색할 수 있다.
7을 탐색한다면? 30보다 작다 -> 8보다 작다 -> 7!
단 3번의 탐색 끝에 찾아낸다.
49를 탐색한다면? 30보다 크다 -> 40보다 크다 -> 49!
역시 3번의 탐색 끝에 찾아낸다.

이처럼 인덱스를 DB에 적용하면 조회만큼은 성능을 보장할 수 있다.
그럼 인덱스는 무조건 많으면 좋겠네? 안타깝게도 그렇지는 않다고 한다.

인덱스를 설정할 때는 값의 고유성, 선택도, 분포도 등을 고려해 선택해야 한다.
중복되는 값이 많을 경우 인덱스를 적용한다 해도 성능상 이점이 크지 않을 수 있고 조회 빈도수가 적거나 잘 분포돼 있는 DB에서는 인덱스를 적용하는 것이 오히려 악영향을 끼칠 수도 있다.

그 이유가 인덱스가 적용된 DB는 결국 특정 기준으로 정렬된 트리 구조를 가져야 하기에 INSERT, UPDATE, DELETE 작업을 실행할 때 정렬 작업도 같이 이뤄진다고 한다.

따라서 인덱싱을 적용할 때는 DB의 구조, 서비스 등을 잘 고려하여 선택해야 한다.

참조키 인덱싱

서론이 길어졌는데 인덱스에 대해 간단히 공부한 이유가 있다.
개인 프로젝트 관련해서 쿼리 최적화에 대해 다뤄보고 싶어서 살펴보던 중 이상한 걸 발견했다.

explain
select *
from comment_tb c
where c.todo_id = 1
order by c.created_at desc;

댓글 테이블에서 특정 Todo를 찾아 생성일자로 내림차순 정렬하는 Select 쿼리에 대한 실행계획이다.
todo_id는 Todo 테이블을 참조하는 참조키이고 따로 복잡한 조건도 없으니 실행 성능이 매우 좋아야 하지만 결과는 이상했다.

type이 ALL이고 rows를 500개 전체 탐색을 했다.
이해가 되지 않았다. comment도 pk로 인덱싱 돼있고 todo도 pk로 인덱싱 돼있어 comment에서 todo를 참조해서 조회한다면 성능이 좋아야 할 텐데 마치 where c.content = "content" 처럼 인덱싱되지 않은 데이터를 탐색하는 것처럼 성능이 좋지 않았다.

저장된 DB 구조를 살펴보니 참조키에 대한 인덱싱이 적용되지 않고 있었다.
이유를 알 수 없어 AI한테 질문하니 다음과 같은 답을 얻을 수 있었다.

JPA에서는 @ManyToOne, @OneToOne, @OneToMany, @ManyToMany와 같은 관계 매핑 어노테이션을 사용할 때, 기본적으로 참조키에 대한 인덱스를 자동으로 생성하지 않습니다. 이는 JPA 구현체인 Hibernate나 다른 JPA 프로바이더의 기본 동작입니다.

JPA에서는 @ManyToOne, @OneToOne, @OneToMany, @ManyToMany 와 같은 관계 매핑 어노테이션을 사용할 때, 기본적으로 참조키에 대한 인덱스를 자동으로 생성하지 않습니다.
이는 JPA 구현체인 Hibernate나 다른 JPA 프로바이더의 기본 동작입니다.
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "COMMENT_TB")
public class Comment extends Timestamped {

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

    @Column(nullable = false)
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "todo_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Todo todo;

    public Comment(CommentRequestDto requestDto, User user, Todo todo) {
        this.content = requestDto.getContent();
        this.user = user;
        this.todo = todo;
    }
}

정확하진 않지만 그렇다고 한다. 또 한 가지 이유는 내 개인적인 생각인데 현재 참조키에서 물리적인 제약을 없앤 상태인데 이로 인해 DB에서 참조키 인덱스를 걸지 않은 것일지도 모른다.

어쨌든 인덱스를 생성하고 실행 계획을 다시 보면 향상된 성능으로 조회가 가능하다.

CREATE INDEX idx_comment_todo_id_created_at ON comment_tb (todo_id);

JPA Entity에서도 어노테이션을 통해 Index를 생성할 수 있다.
@Table 어노테이션 안에는 indexes 라는 항목을 통해 인덱스를 설정해 줄 수 있다.

@Table(name = "COMMENT_TB", indexes = {@Index(name = "idx_comment_todo_id", columnList = "todo_id")})

public class Comment extends TimeStamped {

    @Id
    
    ...
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "todo_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Todo todo;
    
    ...
}

@Index로 인덱스의 이름, 인덱싱할 칼럼들의 이름을 지정해 설정할 수 있다.

실제로 생성돼 있던 인덱스를 지운 뒤 프로젝트를 실행하니 자동으로 JPA에서 인덱스를 생성해 주었다.

실행 계획을 실행해 봐도 type가 ref, rows가 13으로 조회 부분에서 우수한 성능을 보이고 있다.

인덱스는 물론 위에서 말했듯 여러 가지 요소들을 고려해 선택해야 하지만 참조키 같은 부분은 웬만하면 인덱스를 걸어주는 것이 좋다고 생각한다.


오늘의 회고💬

팀 프로젝트 발표까지 끝났다. 생각했던 것들을 많이 구현하지 못해 아쉬웠지만 그래도 진행하면서 배웠던 것도 많았고 앞으로 새로운 걸 할 때도 보다 쉽게 진행할 수 있을 것 같다.

 

내일의 계획📜

내일부터는 본격적인 파이널 프로젝트!!!가 시작된다.

Comments