이번에는 이전 1편에 이어 실제로 문제를 해결하는 과정을 설명해보려고 한다.
설명에 앞서 이전의 테스트 결과를 정리하자면 다음과 같다.
이 결과를 바탕으로 알 수 있는 것은 novel_period_stats 테이블을 기존처럼 사용하게 되면 속도가 굉장히 느려질 수 있고, 데이터가 많아질수록 더더욱 병목이 생길 가능성이 높다는 것이다. 그래서 이를 해결할 새로운 방법을 찾아야 했다.
대략 아래와 같은 방법들을 생각해볼 수 있다.
각 방법의 가능성을 간단히 따져보면
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 의 컬럼을 사용하므로 성능적으로 큰 이득을 보기 힘들다.