데이터베이스 최적화

데이터베이스의 성능에는 테이블, 쿼리, 설정 등 다양한 수준의 요소들이 영향을 미친다. 최적화 방법을 살펴보기 전에 아래 질문으로 어떤 점을 고려해야 하는지 간단히 알아보자.

인덱스 최적화

데이터베이스에서 인덱스는 테이블에 대한 검색 속도를 높여주는 자료 구조를 의미한다. 특정 컬럼에 인덱스를 생성하면 전체 테이블을 풀 스캔하는 대신 인덱스를 참조하여 원하는 결과를 빠르게 조회할 수 있다.

인덱스 설정하기 (성능 10배 향상)

Untitled

tarot_card 테이블에는 id와 card_pack_id가 키로 설정되어 있다. SHOW INDEX FROM... 명령으로 인덱스 정보를 조회하면, 앞서 언급한 키들이 자동으로 인덱스로 등록된 것을 확인할 수 있다.

SELECT `TarotCard`.`id` AS `TarotCard_id`, `TarotCard`.`card_no` AS `TarotCard_card_no`, `TarotCard`.`ext` AS `TarotCard_ext`, `TarotCard`.`created_at` AS `TarotCard_created_at`, `TarotCard`.`updated_at` AS `TarotCard_updated_at`, `TarotCard`.`deleted_at` AS `TarotCard_deleted_at`, `TarotCard`.`card_pack_id` AS `TarotCard_card_pack_id` 
FROM `tarot_card` `TarotCard` 
WHERE ( (`TarotCard`.`card_no` = ?) ) AND ( `TarotCard`.`deleted_at` IS NULL ) LIMIT 1

위 sql문은 typeorm의 로그에 기록된, 타로 카드 정보를 조회하는 SELECT문이다. 하지만 WHERE절에 사용되는 card_no에 인덱스가 적용되어 있지 않다. 이는 tarot_card 테이블이 대량의 데이터를 포함하고 있을 때 검색 성능에 부정적인 영향을 미칠 수 있다. 따라서 card_no 컬럼에 인덱스를 설정하여 검색 성능을 높여야 한다.

WHERE 절에서 사용하는 컬럼 인덱스로 등록된 컬럼
card_no deleted_at id card_pack_id

<aside> 🚨 인덱스의 성능을 확인하기 위해 테스트 DB에 bulk insert로 5000개의 레코드를 추가하여 진행했습니다.

</aside>

인덱스를 적용하기 전에 EXPLAIN을 통해 현재 쿼리의 실행 계획을 살펴보자.

+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                        |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
 -> Limit: 1 row(s)  (cost=554 rows=1) (actual time=7.12..7.12 rows=1 loops=1)
    -> Filter: ((tarot_card.card_no = 0) and (tarot_card.deleted_at is null))  (cost=554 rows=53) (actual time=7.12..7.12 rows=1 loops=1)
        -> Table scan on tarot_card  (cost=554 rows=5301) (actual time=0.104..4.59 rows=3116 loops=1)
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+