Pagenation은 실무에서 엄청나게 자주 구현해야 하는 기능이다.
중요도와 등장 빈도에 비해서 온라인의 예제들은 복잡한 상황에서의 Pagenation에 대해서는 설명해주지 않기에 내가 경험한 트러블 슈팅 경험을 공유하고 자주 등장하는 문제들의 대처 방향성에 대해 공유한다.
실은.. 나도 문제는 해결되지만 효율적인 답을 찾지는 못한 상태라 경험 공유 정도의 글이다.
목차
1. Count Query를 분리한 페이징 구조
2. Where을 분리한 페이징 구조
3. Where을 분리한 페이징 구조에서의 복잡한 OrderBy 절의 대처 방안
4. 요구사항의 합의
Count Query를 분리한 페이징 구조
우선, 페이징이라고 했을 때 가장 기초적인 구조는 하나의 쿼리를 이용하여 페이징 처리를 하는 것이다.
페이징 쿼리를 작성한다라고 했을 때 기본적으로 해당 구조를 떠올리고 구현한다.
대부분의 간단한 구조의 페이징의 경우에는 이 구조에서 요건이 해결된다.
문제 발생
하지만 복잡한 조회 조건 때문에 Left Outer Join이 8~10번씩 걸리게 되고 Select 문에 중복이 발생하기 시작한다면 어떻게 될까?
Where을 분리한 페이징 구조
이 경우에 Where문을 분리하여 조회의 타겟이 되는 Table의 ID만을 중복 제거하여 조회하는 쿼리를 먼저 처리하는 구조를 활용할 수 있다.
기존 구조가 "Data조회 -> Count조회" 였다면 변경되는 구조는 아래와 같다.
"ID조회 -> Data조회 -> Count조회"
이렇게 구조를 가져간다면 중복 발생을 막으며 복잡한 조회 조건 처리를 위한 left join을 걸 수 있다.
문제 발생
하지만 이 경우에도 Select의 대상이 되는 각 필드에 대한 복잡한 정렬 조건이 필요하다면, ID만 조회하는 시점에 Select문에서 Case 절을 통해 결정되는 Contants 값 등에 대한 정렬이 불가능하다는 문제가 발생한다.
따라서 ID만 페이징 해오는 시점에서는 상세한 정렬 요건을 넣을 수 없다.
Where을 분리한 페이징 구조에서의 복잡한 OrderBy 절의 대처 방안
OrderBy에 Select절의 모든 필드에 대한 복잡한 Order Specifier 설정이 필요하다면 단순히 ID만 조회해 올 때 정렬을 수행하기 어렵다.
따라서 이 경우에는 대안으로 Where절의 필터링 조건을 이용하여 페이징은 하지 않고, ID만 모두 조회해온 뒤 Data 조회 쿼리에서
Pagenation과 정렬을 동시 수행하는 방법이 있다.
"ID조회(Pagenation) -> Data조회 -> Count조회"의 기존 구조에서
"ID조회 -> Data조회(Pagenation & Sort) -> Count조회"의 구조로 변경되는 것이다.
다만 이렇게 처리한다면 최초에 Data를 조회하기 위한 ID를 가져오는 과정에서 모든 ID를 조회하게 되므로 성능상 많은 손해가 발생하게 된다.
따라서 가능하다면, 페이징에 대하여서는 요건을 최대한 간단하게 가져가는 것이 좋다. 특히 1-N관계의 테이블 필드에 대한 페이징에서의 데이터 조회, 정렬요건 등은 제거하거나 분리하는 것이 성능적, 유지보수적으로 모두 좋다.
요구사항의 합의
이 글은 초안을 작성하고 관련한 경험이 있을 때마다 수정하며 작성한 글이다.
최근에도 관련한 고민을 계속하다 배우게 된 것이 있다.
너무 기술적으로 복잡한 요구사항은, 기획 단계에서 기술적인 부분을 고려하여 간단하게 풀어야 한다는 것이다.
예를 들어, 음식점 목록을 조회하는 페이지가 있다고 생각해보자.
기획자는 이 쿼리에서 음식점의 "메뉴"라는 필드를 조회 컬럼의 하나로 뿌리고 싶어 한다.
2개 이상일 경우에는 마우스를 호버 하면 음식점의 전체 메뉴가 보이게 하고 싶어 하고 해당 필드를 정렬 시, 1개이면 메뉴명이 알파벳 순으로 우선정렬되고 2부터는 숫자로 오름차순 정렬되길 원한다. (질문 포인트 1. 이게 조회하는 고객에게 도움이 될까?)
개발자는 기획자가 페이징에 잡다한 무언가를 마구마구 끼워 넣으려는 것을 보며 머리가 어지러워진다.
개발자는 이 순간에 2가지 접근 방법이 있다.
첫 번째, 머리를 싸매서 어떻게든 기술적으로 구현해 낸다.
두 번째, 기획을 분석하고 비즈니스에 필요한 요건은 아니지만 문제를 과도하게 어렵게 만드는 요소라면, 요건을 단순하게 바꾸는 것을 제안한다.
이 글 자체가 첫 번째에 초점을 두어 쓰인 글이다.
다만, 나는 개발자는 우선적으로 두 번째로 가야 한다고 생각한다.
한정된 인적 자원으로 해결할 수 있는 문제는 정해져 있다.
따라서 문제의 난이도에 상관없이 이 문제가 복잡한 공수를 들여서라도 풀어야 하는 문제인가? 또한 그렇게 되었을 때 코드의 유지보수 비용까지 감당할 정도의 효과가 있는가?를 개발자는 고려해야 한다.
따라서 우선 이런 경우에 개발자는 기술적 걸림돌이 될 포인트를 기획과 논의하고 해결책을 제시해야 한다.
예를 들어, "각 가게별 메뉴의 개수는 보여줄 수 있지만, 마우스 호버시에 전체 메뉴가 보이고 정렬까지 되는 것은 기술적으로 기능에 비해 공수가 많이 들어갑니다. 실제로 유저가 메뉴를 보기 위해서는 보통 상세로 들어가서 보는 흐름으로 진행되므로, 이 경우에는 차라리 메뉴의 개수를 클릭하면 별도의 메뉴 조회 API를 호출하여 보여주는 것으로 하시는 건 어떨까요?"
위와 같이 "문제점 - 비즈니스적 필요성 - 대안 제시"의 흐름으로 이야기하는 것이다.
내가 페이징을 못 짜서 위와 같은 고민을 하는 것일 수도 있기는 하지만 ㅎ..
현재 시점으로 내가 내린 결론은 아래와 같다.
"문제가 어려운데 비해 그 정도의 효용이 없다면 문제를 쉽게 만들자."