인덱스로 API 응답속도 개선하기

이상윤·2026년 2월 13일

DB

목록 보기
1/3

먼저, 채팅 메시지는 시간이 지날수록 계속 쌓이는 구조이다.
현재 채팅 메시지 테이블의 구조는 다음과 같고, 조회 시에도 JPA를 이용하여 Sequence Scan을 하고있다.
ChatMessageEntity

  id (PK)                                                             
  room_id (FK → chat_rooms)
  sender_id
  sender_name
  content
  sent_at
  message_type

하지만 만약 메시지의 개수가 매우 많아진다면 이런 방식의 조회는 분명 응답 시간에 영향을 줄 것이고, 이번에 테스트로 10만개의 메시지를 저장해 직접 개선 전과 후의 응답시간 차이를 비교해 보겠다.

먼저, 테스트용 채팅방 100개와 메시지 10만개를 db에 집어넣은 다음 Jmeter에서 부하 테스트를 실행해보자.

현재 api에서 사용하는 메시지 조회 방식은 이렇다.

jamjam_database=#  EXPLAIN ANALYZE                                                                                                                                                                                                                  
  SELECT * FROM chat_message_entity
  WHERE room_id = 9000
  ORDER BY sent_at DESC
  LIMIT 20;
                                                             QUERY PLAN                                                             
------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=2709.14..2709.19 rows=20 width=219) (actual time=66.150..66.180 rows=20 loops=1)
   ->  Sort  (cost=2709.14..2711.58 rows=975 width=219) (actual time=66.148..66.158 rows=20 loops=1)
         Sort Key: sent_at DESC
         Sort Method: top-N heapsort  Memory: 30kB
         ->  Seq Scan on chat_message_entity  (cost=0.00..2683.20 rows=975 width=219) (actual time=1.700..65.278 rows=1000 loops=1)
               Filter: (room_id = 9000)
               Rows Removed by Filter: 99177
 Planning Time: 0.333 ms
 Execution Time: 66.223 ms
(9 rows)

Plan을 살펴보면 Seq Sean으로 10만전에 달하는 전체 데이터를 읽은 후, 99177건의 쓸모 없는 데이터를 버리고 정렬하였다.

이런 비효율적인 쿼리를 한번 개선해보자. 현재 쿼리는 room_id가 9000인 메시지를 보낸 순서로 정렬하는 작업을 수행한다.

먼저 인덱스의 첫 번째 컬럼으로 room_id를 지정하면, DB 엔진은 9000번 방의 데이터가 시작되는 지점으로 즉시 점프한다. 이 덕분에 9만 9천 건의 불필요한 데이터를 읽지 않을 수 있다.

또한, 다음 두 번째 컬럼으로 sent_at을 사용해 역순으로 미리 정렬하여 저장한다. 따라서 DB는 데이터를 다 뽑은 뒤에 다시 정렬할 필요 없이, 인덱스 상단에서부터 딱 20개만 읽어서 바로 사용자에게 던져줄 수 있다.

jamjam_database=# CREATE INDEX idx_chat_message_room_sent_at 
ON chat_message_entity (room_id, sent_at DESC);
CREATE INDEX

인덱스를 지정한 후 다시 쿼리를 실행해보자.

jamjam_database=#  EXPLAIN ANALYZE                                                                                                                                                                                                                  
  SELECT * FROM chat_message_entity
  WHERE room_id = 9000                                                                                                                                       
  ORDER BY sent_at DESC
  LIMIT 20;
                                                                           QUERY PLAN                                                                            
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.42..60.82 rows=20 width=219) (actual time=0.033..0.075 rows=20 loops=1)
   ->  Index Scan using idx_chat_message_room_sent_at on chat_message_entity  (cost=0.42..2945.22 rows=975 width=219) (actual time=0.031..0.055 rows=20 loops=1)
         Index Cond: (room_id = 9000)
 Planning Time: 0.167 ms
 Execution Time: 0.098 ms
(5 rows)

가장 먼저 속도를 비교해보자. 기존 66.223 ms -> 0.098 ms로 약 675배나 빨라진 것을 확인할 수 있다. 또한, 이전에는 10만건을 다 뒤져서 찾았지만 이번에는 인덱스를 통해 room_id가 9000인 메시지를 즉시 찾아낸 것을 볼 수 있다. 마지막으로, 인덱스가 이미 정렬되어 있으므로 정렬 단계 또한 사라졌음을 알 수 있다.

이제 다시 Jmeter에서 부하 테스트를 진행해보자.


먼저 평균 응답 시간을 살펴보자. 이전에는 5.2초가 걸렸지만 인덱싱 후 0.07초로 무려 98.5%가량 시간이 줄어든 것을 볼 수 있다. 또한 표준편차값을 살펴보면 이도 1,104에서 31.07로 매우 줄어들어 모든 요청에서 걸린 시간이 차이가 크게 나지 않는 것을 볼 수 있다.

0개의 댓글