포스트

Database

데이터베이스 페이징 성능을 어떻게 봐야 하는가

페이징은 왜 느려지는가

목록 조회에서 페이지네이션은 당연한 기능처럼 보이지만, 데이터가 커질수록 단순 limit offset 방식이 병목이 되기 쉽다.

DB가 offset = 100000을 받았다고 해서 100000개를 공짜로 건너뛰지는 않는다. 앞 데이터를 실제로 읽은 뒤 버리기 때문에 뒤 페이지로 갈수록 느려질 수 있다. PostgreSQL 문서도 OFFSET으로 건너뛴 행은 서버 안에서 계산되므로 큰 OFFSET은 비효율적일 수 있다고 적는다.

Offset 기반 페이징

장점:

  • 구현이 단순하다.
  • 특정 페이지로 바로 이동하기 쉽다.

단점:

  • 뒤 페이지일수록 비용이 커진다.
  • 중간에 데이터가 삽입/삭제되면 결과가 흔들릴 수 있다.

Cursor 기반 페이징

이전 페이지의 마지막 키를 기준으로 다음 페이지를 가져오는 방식이다.

장점:

  • 대량 데이터에서 안정적이다.
  • 무한 스크롤과 잘 맞는다.

단점:

  • 임의 페이지 이동은 어렵다.
  • 정렬 기준이 명확해야 한다.

성능을 볼 때 체크할 것

  • 정렬 기준 컬럼에 인덱스가 있는가
  • count 쿼리가 비싸지 않은가
  • 조회 쿼리와 count 쿼리를 분리해야 하는가
  • tie-breaker(정렬 값이 같을 때 순서를 정하는 보조 키)가 있는가

예를 들어 created_at desc만 쓰면 같은 시간대 레코드에서 순서가 흔들릴 수 있어서 created_at desc, id desc처럼 보조 정렬 키를 두는 편이 안전하다. 같은 문서는 유일한 순서를 강제하는 ORDER BY 없이 LIMIT을 쓰면 예측할 수 없는 부분 집합을 받는다고 설명한다.

흔한 실수

  • 무조건 Page를 반환해서 무거운 count 쿼리를 매번 수행함 (Spring 쪽 설계)
  • fetch join과 페이징을 함께 써서 예상과 다른 결과가 나옴
  • offset 방식이 느린데도 쿼리 대신 애플리케이션 레이어만 만짐

정리

페이징 성능은 “API 모양”보다 “DB가 어떤 순서로 몇 건을 읽는가”의 문제다. 그래서 데이터가 커지면 offset과 cursor 중 하나를 고르는 일과 정렬 키와 인덱스를 맞추는 일을 함께 결정해야 한다.

참고

데이터베이스 내부와 트랜잭션
이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

변경이력

2번 수정

  1. docs(notes): cite sources and ease reading in pagination-performance
  2. docs(posts): rewrite database notes collection

댓글

아직 댓글이 없습니다