개발이야기

항해99) 인덱스 관련 이야기

gaelim 2024. 5. 10. 17:39
반응형

항해 99 과정서 콘서트 좌석 관련 테이블 형태는 다음과 같다.

제약조건: 

좌석수: 1~50
콘서트수: 1~200
좌석상태: EMPTY, RESERVED

seat_number: 좌석 정보
show_id: 콘서트 아이디
occupied_status: EMPTY, RESERVED 

이다.

1번 콘서트에서 잔여좌석 확인때 다음과 같이 수행한다.

select * from seat_info where occupied_status = "EMPTY" and show_id = 1;

Explain view로 보면 Full Table Scan을 수행한다.

 

 

여기서 풀테이블 스캔을 피해가려면 Index를 걸 수 있다.

Index는 Cardinality가 큰 녀석을 걸면된다. 직감적으로 show_id에 걸면 될 것이다.(1~50)이므로.
Index를 걸 때도 복합 인덱스 선택방향이 있는데, 다음과 같이 선택방향이 있을 것이다.

> (show_id)
> (show_id, occupied_status)
> (show_id, seat_number, occupied_status)

다만, occupied_status는 EMPTY, RESERVED 두 가지 상태만 존재하므로 카디날리티가 2밖에 되지 않는다. 이런경우 다음과 같은 단점이 있다.

1. 카디널리티가 낮으므로, 인덱스 탐색 범위가 넓어지고 최악의 경우 FULL TABLE SCAN보다 성능이 낮을 수 있다.
2. 좌석의 상태는 자주 변경된다. 인덱스는 삽입과 제거의 성능을 저하하지만 조회의 속도를 빠르게 하는 것이다. 상태가 EMPTY, RESERVED의 변경이 잦다면 인덱스를 선택하는데 더 좋지 않은 선택사항이 될 수 있다.

 

그래서 나의 최종적인 결정은 show_id 에만 걸게 하였다.

스프링에서 인덱스를 걸기 위해 다음과 같이 수행하였다.

package reservation.Domain;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name="seat_info", indexes = {
    @Index(name = "IDX_show_id", columnList = "show_id")
})
public class Seat {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name="seat_number")
    private int seatNumber;

    @Column(name="occupied_status")
    private String occupiedStatus;

    @Column
    @Version
    private int version;

    @ManyToOne
    @JoinColumn(name="show_id")
    private Show concertShow;

    public reservation.DTO.Seat toSeatDTO(){
        return  reservation.DTO.Seat.builder()
                .id(this.id)
                .seatNumber(this.seatNumber)
                .occupiedStatus(this.occupiedStatus)
                .build();
    }
}

 

실행결과는 다음과같다.

query cost가 875.8 에서 17.5로 무려 50배, 98% 감소하게되었다.

 

다음은 내가 삽입 쿼리를 만드는 파이썬 테스트 코드이다.

from random import choice

for i in range(1, 200):
    print(f"insert into concert_show (id, price, concert_id) values({i}, 1000, 1);")

print("")
for i in range(1, 200):
    for j in range(1, 50):
        val = choice(["EMPTY", "RESERVED"])
        print(
        "insert into seat_info (id, seat_number, occupied_status, version, show_id)" +
        f"values ({i*50+j}, {j}, \"{val}\", 0, {i});"
        )

문서끝

반응형