이번 프로젝트 티켓팅 플랫폼 Ticket 에서 저는 공연관련 도메인을 맡았습니다.
보통 조회 관련한 기능들이 구현했는데요, 홈화면에 있는 인기공연 top10 이나 예매 예정과 같은 경우 빠른 속도의 응답이 중요합니다.
처음 local 에서 개발했을 때는 데이터가 6000건이었기 때문에 응답 속도 관련해서 문제가 나타나지 않았습니다.

위 사진은 간단하게 300 vuser 를 5분간 보낸 결과입니다.
TPS 는 2000건 이상에 1초 이내, 에러율 0 퍼센트로 아주 안정적인 환경으로 확인될 수 있었습니다.

문제 상황은 이때부터 였습니다. 공연 데이터를 불려서 3000만건 데이터를 쌓은 이후, macbook m3 pro 에서 postgres 를 배포하여 사용하였습니다.
데이터도 늘어났을 뿐만아니라 네트워크를 한 번 타고 오는 속도라 그런지 TPS 가 1 이하로 나오는 처참한 결과가 나왔습니다.
어떻게 해결할지에 대해 고민 후, 쿼리 튜닝이 필요한 것 같아 mybatis 의 mapper 를 확인해 보았습니다.
<select id="findRelatedPerformances" resultType="PerformanceDto">
SELECT
p.performance_id AS performanceId,
p.performance_title AS title,
p.performance_date AS date,
p.performance_img AS img
FROM performance p
JOIN status s ON p.status_id = s.status_id
WHERE p.genre_id = #{genreId}
AND p.performance_id != #{performanceId}
AND p.performance_deleted_at IS NULL
AND s.status_code != 201
ORDER BY p.performance_look_count DESC, p.performance_date ASC
LIMIT 5
</select>
⇒ 이미 fk 로 값을 상태 id 를 가져오기 때문에 join 으로 조회하지 않게 모든 mapper 수정하였습니다.
<select id="findRelatedPerformances" resultType="PerformanceDto">
SELECT
p.performance_id AS performanceId,
p.performance_title AS title,
p.performance_date AS date,
p.performance_img AS img
FROM performance p
WHERE p.genre_id = #{genreId}
AND p.performance_id != #{performanceId}
AND p.performance_deleted_at IS NULL
AND p.status_id IN (1,2)
ORDER BY p.performance_look_count DESC, p.performance_date ASC
LIMIT 5
</select>
⇒ 이후 4798ms 에서 최대 약 3222ms 정도로 줄어들었지만 아직도 느리다는 걸로 확인이 됩니다.
인덱스 적용 후 300 요청시에 약 319 TPS로 안정적이게 되었습니다.
-- 1) 장르별 목록/카운트/관련공연 (정렬: performance_date ASC)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_perf_genre_active_date
ON performance (genre_id, performance_date)
WHERE performance_deleted_at IS NULL AND status_id IN (1,2);
-- 2) 전체 인기 Top10 (정렬: look_count DESC, start_date ASC)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_perf_hot_global
ON performance (performance_look_count DESC, performance_start_date ASC)
WHERE performance_deleted_at IS NULL AND status_id IN (1,2);
조건문에서 쓰이는 정렬 및 상태값 등을 인덱스를 타게하여 조회 속도를 빠르게 증가시켰습니다.

(워밍업 요청을 안해서 초반 그래프가 위태로운 점 양해 부탁드립니다.)
nGrinder 를 보면, 1초 이내 300vuser 요청시 300 이상의 TPS 가 나오는 안정적인 응답 속도 결과로 3000만건의 대량 데이터 속에서도 빠른 응답 속도가 가능하도록 구축하였습니다.
왜 인덱스를 적용하면 응답 속도가 빨라지는 걸까요?
앞으로는 자주 호출되는, 빠른 응답을 사용해야하는 경우 인덱스를 사용해야 겠다는 것을 배웠습니다. 하지만 많은 사용은 저장공간을 차지하기 때문에 오히려 더 많은 성능 저하를 일으킬 수 있다는 것도 깨닫게 되었습니다.