어제 문제를 겪었던 부분을 해결하여 메인 페이지에 리뷰 많은 상위 8 개 장소와 북마크 많이 받은 상위 8개 장소를 성공적으로 표시하는데 성공했다.
먼저 문제 상황을 상기해보자. 리뷰 많은 장소나 북마크 많이 된 장소나 상황은 같으니 리뷰 많은 장소만 가지고 설명을 해 보겠다.
먼저 현재 supabase 테이블에 리뷰 table 이 있고 이 테이블의 각 row는 해당 리뷰가 대상으로 하는 place_id 를 가진다. 이 place_id 는 places 테이블의 id를 외래키로 가져온 것이다.
문제는 리뷰가 많은 상위 8개 장소를 뽑으려면 리뷰 table 에 등장하는 각 place_id가 각각 몇번 등장하는지 세어야 하고, 그걸 내림차순으로 정렬한 뒤, 그중 상위 8개만 불러와야 하는데 그걸 supabase에서 기본 제공하는 코드로는 구현할 수 없다는 것이다.
리뷰 테이블에 있는 모든 값을 읽어 들인 뒤 받아온 데이터를 가공해 원하는 결과를 얻을순 있겠지만 리뷰 테이블에 존재하는 모든 값을 읽어오는 시점에서 이미 좋은 코드는 아니다. db에 등록되어 있는 장소만 12000개가 넘는데, 각 장소에 리뷰가 3개씩만 달렸다고 해도 36000개가 넘는 행을 reviews 테이블에서 읽어 들여야 하기 때문이다. 지금이야 개발 단계고 리뷰의 수도 얼마 되지 않으니 문제가 되진 않겠지만 리뷰의 수가 조금만 늘어나도 서버요청이 과도하게 많아질 것은 불 보듯 뻔하다.
그래서 사용해본 것이 RPC 이다.
원격 프로시저 호출은 별도의 원격 제어를 위한 코딩 없이 다른 주소 공간에서 함수나 프로시저를 실행할 수 있게하는 프로세스 간 통신 기술이다. 위키백과
라고 사전에는 나와있는데 지금 내 상황에서는 supabase 에 등록한 서버 함수를 내 next.js 프로젝트 내에서 호출하여 사용하는 것이라고 이해해도 된다.
supabase에서 기본 제공하는 코드 만으로는 원하는 바를 이룰 수 없지만 함수를 직접 작성하여 사용한다면 이야기는 달라진다.
먼저 supabase에 작성한 쿼리문을 보자
create or replace function getTopPlaceIds()
returns setof uuid as $$
begin
return query
select place_id
from reviews
group by place_id
order by count(*) desc
limit 8;
end;
$$ language plpgsql;
위 쿼리문을 supabase 쿼리 편집기에서 실행시키면 자동으로 함수가 생성되어 해당 프로젝트의 database>functions 에서 확인할 수 있게 된다.
쿼리문은 reviews 테이블에서 place_id열에 있는 데이터들을 가지고 각각의 place_id별로 묶어서 해당 value 의 등장 횟수를 세고 이를 내림차순으로 정렬 뒤 해당 행들에 기록되어 있는 place_id 상위 8개를 uuid 의 배열로 반환하는 함수를 만들어 달라는 의미이다.
이렇게 만들어진 함수는 아래와 같다
begin
return query
select place_id
from reviews
group by place_id
order by count(*) desc
limit 8;
end;
그리고 이렇게 만들어진 함수를 기존에 해왔던 것처럼 review.ts 에서 사용 하면 된다.
//리뷰가 많은 상위 8개 장소의 place_id 조회(remote procedure call)
export const getPlacesByReviewCount = async () => {
let { data, error } = await supabase.rpc('get_top_place_ids');
if (error) console.error(error);
else {
console.log(data);
return data;
}
};
위 함수는 조건에 부합하는 장소들의 place_id를 배열에 담아 반환한다. 그리고 이걸로 place_id로 place 테이블에서 데이터를 가져오면 처음에 목적했던 데이터를 받아올 수 있다.
상술한 로직이 작동하려면 쿼리의 실행 순서가 고정되어야 한다. 즉, place_id의 배열을 받아오는 코드가 무조건 먼저 실행되어야 하고, 여기서 받아온 place_id의 배열을 가지고 place 데이터를 조회하는 코드가 무조건 나중에 실행되어야 한다는 것이다.
이러한 쿼리의 실행 순서를 고정 시키기 위해 enabled
속성을 활용해 보았다.
const { data: topReviewedPlaces, isLoading } = useQuery({
queryKey: ['topReviewedPlaces'],
queryFn: getPlacesByReviewCount,
});
const { data: topReviewedPlacesList, isLoading: placesListLoading } =
useQuery({
queryKey: ['topReviewedPlacesList'],
queryFn: () => getPlaceInfoList(topReviewedPlaces),
enabled: !!topReviewedPlaces,
});
다음은 순서가 지켜져야 하는 두 쿼리의 실행 순서를 고정시킨 예시이다.
enabled: !!topReviewedPlaces
때문에 하단의 쿼리는 topReviewedPlaces라는 이름으로 place_id 의 배열이 준비되기 전까진 실행되지 않다가, topReviewedPlaces가 제대로 준비되면 그때 실행되며 topReviewedPlaces를 queryFn 에 넘겨줄 인자로 활용하고 있다.