100만개의 상품이 있는 애플리케이션에서 사용자가 상품의 목록을 요청한다고 가정해보자. 매번 100만개의 데이터를 전부 가져오게되면 매우 느려지며 사용자는 불편함을 느끼게 될 것이다. 하지만 데이터를 조금씩 (20~100개) 나눠서 가져오고 사용자가 원하는 경우 다음 데이터를 가져오게 되면 훨씬 빠르고 사용자도 애플리케이션에 대해 만족할 것이다. 이러한 이유로 Pagination을 사용한다.
offset이란 SQL에서 조회를 시작할 기준점을 의미하고 limit는 조회할 결과의 개수를 의미한다.
SELECT * FROM { 테이블 이름 } LIMIT 20 OFFSET 60;
위와 같은 예시는 60번째 행부터 20개의 데이터를 읽겠다는 것을 의미한다. (이때 행은 0부터 시작) offset은 조회를 한 결과에서 limit로 지정한 개수만큼만 반환하고 나머지는 버리는 식으로 동작한다.
위 쿼리로 예를 들자면 60번째 row부터 20개를 조회하기 위해서 80개의 데이터(row의 0부터 79번째 까지)를 모두 읽은 뒤, 앞의 필요없는 60개의 데이터는 버려야 한다.
위 쿼리처럼 적은양의 데이터를 조회할 때는 성능적인 문제가 발생하지 않지만, 전체 데이터의 개수가 많아질수록 버려지는 데이터의 양이 많아져 문제가 된다.
SELECT ... OFFSET X LIMIT Y
구조로 간단히 구현할 수 있어 코드가 직관적이다. 다양한 검색 조건을 추가해도 구조 자체는 동일해, 쿼리 작성이 비교적 간편하다.OFFSET
과 LIMIT
만 수정하면 되므로, 복잡한 필터나 검색 조건이 적용되는 경우에도 적용이 쉽다. 성능 저하
위에서 말했듯이 offset 방식은 여러 개의 데이터를 한꺼번에 가져와 필요한 만큼만 출력하고 나머지는 버려지게 된다. 예를 들어 1,000,000개의 데이터를 가지고있고, 100,000번째의 row에서 20개만 필요하다고 가정해보자. 내가 필요한 만큼은 20개인데 데이터는 100,020개를 가져오고 100,000개의 불필요한 데이터가 버려지게된다. 그 숫자는 커지면 커질수록 속도는 저하된다.
비효율적인 인덱스 활용
offset 방식은 인덱스 활용도가 낮고, 큰 페이지 번호로 이동할수록 인덱스 스캔 비용이 증가해 쿼리 성능에 영향을 미친다.
데이터 일관성 문제
실시간으로 데이터가 변동되는 경우, offset 방식은 페이지 간 이동 시 일관성을 보장하기 어려울 수 있다. 특히 데이터가 추가, 삭제가 빈번한 상황에서는 중복 조회나 데이터 누락이 발생할 수 있다.
나는 1번부터 5개를 줬는.. 어? 4번 5번이 없네? 그럼 7번까지 줬구나 !
no-offset(=cursor) 방식은 이 전 페이지의 마지막 데이터의 id 값을 기억하고, 다음 페이지를 요청할 때 이 id 값 이후의 데이터를 가져오는 방식이다.
이를 통해 매번 offset 값을 계산하지 않아도 되기 때문에 데이터베이스에서 더욱 효율적으로 데이터를 가져올 수 있다.
이 방식을 사용할 경우 기준점 이전의 데이터도 모두 조회하던 offset 방식과는 달리 기준점(=cursor)부터 limit의 개수만 조회하기 때문에 데이터의 개수가 많아져도 성능 문제가 발생하지 않는다.
보통 무한 스크롤이라 칭하기도 하고, sns에서 많이 사용되고 있다.
SELECT * FROM { 테이블 }
WHERE { 조건문 }
AND id < lastId
ORDER BY id DESC
LIMIT { 컨텐츠 개수 }
복합적인 검색 조건이 필요하거나 다양한 필터링을 제공해야 하는 경우라면 offset 방식이 유연하게 대응할 수 있다. 반면, 특정 키나 필드를 중심으로 일관된 순서를 유지하면서 빠르게 pagination을 제공해야 한다면 no-offset 방식이 적합하다.
즉, 각각의 장 단점을 살펴보고 상황에 맞게 적용하는게 best!