이번에는 이전 1편에 이어 실제로 문제를 해결하는 과정을 설명해보려고 한다.

설명에 앞서 이전의 테스트 결과를 정리하자면 다음과 같다.

이 결과를 바탕으로 알 수 있는 것은 novel_period_stats 테이블을 기존처럼 사용하게 되면 속도가 굉장히 느려질 수 있고, 데이터가 많아질수록 더더욱 병목이 생길 가능성이 높다는 것이다. 그래서 이를 해결할 새로운 방법을 찾아야 했다.

대략 아래와 같은 방법들을 생각해볼 수 있다.

  1. novel_period_stats 테이블에 total_view_count 컬럼을 만든다.
  2. novel 테이블에 period_view_count 컬럼을 만든다.
  3. total_view_count, period_view_count 컬럼을 가지는 인기순 정렬 전용 테이블인 novel_summary 테이블을 만든다.
  4. 서브 쿼리를 활용한다.

각 방법의 가능성을 간단히 따져보면

1번은 논리적으로 불가능하다. novel_period_stats 테이블은 일정 주기의 데이터를 저장하는 테이블인데, 그곳에 현재까지 누적된 총 조회수를 저장한다는 것은 같은 novel_id 를 가지는 데이터들끼리 총 조회수의 값을 공유하고 동기화 해야 한다는 말이다. 이는 구현도 복잡해지고 테이블의 역할과 맞지도 않기 떄문에 이 선택은 좋은 선택이라고 볼 수 없다.

2번은 가능하다. 특히 랭킹 같이 복잡한 연산이 필요한 것이 아니라면 그냥 novel 에 비정규화 컬럼을 두는게 더 깔끔할 수 있다. 또한 현재 소설의 정렬에 필요한 값들은 전부 novel 에 있으므로 일관성 측면에서도 이 선택은 좋은 선택이 될수도 있다.

3번 또한 가능하다. novel_summary 에 인기순 정렬에 필요한 값들을 모두 저장해두면 인덱스 활용도 제대로 할 수 있고, novel 과 JOIN 만 하면 되므로 꽤 괜찮은 방법일 수 있다.

4번은 가능할 수도 있지만 좋은 방법이라고는 보기 어렵다. 서브 쿼리를 이용해 성능을 향상시킬 수 있는 경우는 EXISTS 쿼리 같은 경우나 서브쿼리에서 먼저 값들을 필터링 할 수 있는 경우일텐데, 현재는 아무리 서브쿼리를 이용해 novel 의 데이터를 먼저 가져온다고 해도 결국 novel의 total_view_count 로 정렬해야 하므로 오히려 novel_period_stats 테이블을 사용한 쿼리보다 더 느릴 수 있다.

예를 들어 아래와 같은 쿼리가 나올 수 있다.

select
	n.*,
	nps.id as period_stats_id,
	nps.view_count
from
	novel_period_stats nps
join
(select * 
 from novel 
 where total_view_count < ? or total_view_count = ? and id < ?
 order by total_view_count
) as n on nps.novel_id = n.id
where
	nps.start_date = ? and nps.end_date = ?
	and nps.view_count < ?
	or nps.view_count = ? and n.total_view_count < ?
	or nps.view_count = ? and n.total_view_count = ? and nps.id < ?
order by
	nps.view_count,
	n.total_view_count,
	nps.id
limit 50;

대략 이런 쿼리를 사용할 수 있을텐데, 딱 봐도 제대로 된 쿼리가 아니다. 서브쿼리에서 novel 데이터들을 가져올 때 total_view_count 를 기준으로 필터링을 하고 있는데 이렇게 되면 view_count → total_view_count 순서로 필터링 되야 하는 상황에서 잘못된 데이터가 나올 가능성이 높다.

그렇다고 아무 조건도 없이 novel 을 가져오게 되면 엄청나게 많은 데이터를 가져오게 되므로 성능이 당연히 저하될 것이다. 또한 애초에 메인 쿼리에서 필터링, 정렬 모두 novel 의 컬럼을 사용하므로 성능적으로 큰 이득을 보기 힘들다.