본문 바로가기

cs(with 매일메일)

[260209월] RDB에서 페이징 쿼리의 필요성을 설명해 주세요.

페이징 쿼리는 전체 데이터를 부분적으로 나누어 데이터를 조회하거나 처리할 때 사용된다. 

 

데이터를 상대적으로 작은 단위로 나누어 처리하기 때문에 데이터베이스나 애플리케이션의 리소스 사용 효율이 증가,

로직 처리 시간을 단축 시킬 수 있다. 

 

MySQL에서 페이징 쿼리는 일반적으로 LIMIT, OFFSET구문을 사용하여 작성한다. 

select *
from subscribe
limit 500
offset 0;

 

LIMIT, OFFSET방식의 단점

- 뒤에 있는 데이터를 읽을 수록 점점 응답 시간이 길어질 수 있다. 

- 왜? DBMS는 지정된 OFFSET수만큼 모든 레코드를 읽은 이후에 데이터를 가져오기 때문이다. 

 

  • 리소스 낭비: OFFSET 값이 커질수록 읽어야 하는 데이터 행(Row)이 많아지므로 CPU와 I/O 사용량이 급격히 증가합니다.
  • 데이터 정합성 문제: 페이징 도중에 새로운 데이터가 삽입되거나 삭제되면, 사용자가 다음 페이지로 넘어갔을 때 중복된 데이터를 보거나 일부 데이터를 건너뛰게 되는 현상이 발생합니다.

 

 

 해결 방안: No Offset (Cursor-based Pagination)

가장 대표적인 해결책은 마지막으로 본 데이터의 식별자(ID 등)를 기준점으로 삼는 것이다. 이를 '커서 기반 페이징'이라고 부른다. 

방법: WHERE 조건문 활용

-- 기존 방식 (OFFSET)
SELECT * FROM orders ORDER BY id DESC LIMIT 10 OFFSET 10000;

-- 개선된 방식 (No Offset)
SELECT * FROM orders WHERE id < [마지막으로_확인한_ID] ORDER BY id DESC LIMIT 10;

왜 더 빠른가요?

  • 인덱스 활용: WHERE 절에 인덱스가 걸린 컬럼(주로 PK나 생성일시)을 사용하면, DBMS는 인덱스를 타고 해당 위치로 직행합니다.
  • 상수 시간 복잡도: 데이터가 1억 건이 있어도 내가 마지막으로 본 위치 다음부터 10개만 읽으면 되므로, 뒤쪽 페이지를 조회해도 응답 시간이 일정하게 유지됩니다.

또 다른 대안: 커버링 인덱스 (Covering Index)

만약 비즈니스 요구사항상 반드시 특정 페이지로 바로 이동하는 '번호판 UI'가 필요해서 OFFSET을 써야만 한다면, 커버링 인덱스를 활용해 성능을 최적화할 수 있습니다.

  • 원리: 실제 데이터 레코드 전체를 읽기 전에, 인덱스에 포함된 정보(예: ID)만으로 먼저 필터링을 끝내는 방식입니다.
  • 효과: 실제 데이터 블록에 접근하는 횟수를 획기적으로 줄여 OFFSET의 부하를 완화합니다.
SELECT * FROM orders JOIN (
    SELECT id FROM orders ORDER BY id DESC LIMIT 10 OFFSET 10000
) as temp ON temp.id = orders.id;

요약

구분 LIMIT OFFSET No Offset (Cursor)
속도 뒤로 갈수록 느려짐 항상 일정함
정합성 데이터 변화 시 누락/중복 발생 가능 비교적 안전함
구현 난이도 매우 쉬움 조금 복잡함 (UI 제약 발생)
적합한 곳 데이터가 적은 서비스, 관리자 페이지 무한 스크롤(SNS), 대규모 서비스