대용량 트래픽 환경에서 Offset Pagination을 Keyset Pagination으로 변경한 경험이 있나요? 왜 변경했고 성능이 좋아진 이유는 무엇인가요?
가장 일반적으로 사용하는 페이지네이션 방식이다.
SELECT *
FROM board
ORDER BY id
LIMIT 20 OFFSET 100;
OFFSET 100은 앞의 100개의 데이터를 건너뛰고 101번째 데이터부터 조회하라는 의미이다.
하지만 실제 DB는 101번째 위치를 바로 찾는 것이 아니라 앞의 데이터를 하나씩 읽는다.
1 읽기
2 읽기
3 읽기
...
100 읽기
↓
100개 버림
↓
101번째부터 반환
즉 페이지가 뒤로 갈수록 읽는 데이터가 계속 증가한다.
예를 들어
LIMIT 20 OFFSET 500000;
이라면
1~500000 읽기
↓
전부 버림
↓
500001부터 반환
을 수행한다.
Offset 대신 마지막으로 조회한 데이터의 Key(Cursor)를 이용하는 방식이다.
예를 들어 마지막 게시글의 id가 100이라면
SELECT *
FROM board
WHERE id > 100
ORDER BY id
LIMIT 20;
처럼 조회한다.
DB는 Primary Key 인덱스를 이용하여
id = 100
↓
바로 이동(Seek)
↓
101부터 조회
를 수행한다.
앞의 데이터를 읽고 버리지 않기 때문에 조회 비용이 거의 일정하다.
1
↓
2
↓
3
↓
...
↓
500000
처음부터 하나씩 읽는다.
id = 500000
↓
바로 이동
↓
500001부터 읽기
원하는 위치부터 읽는다.
예를 들어
WHERE id = 100
을 작성하면
DB 옵티마이저가
id 컬럼에 인덱스가 있으므로 Index Seek가 더 빠르겠네.
라고 판단하여 실행 계획을 선택한다.
즉 개발자가 직접 지정하는 것이 아니라 DB가 자동으로 최적화를 수행한다.
첫 번째 요청
GET /boards
응답
{
"list":[...],
"nextCursor":20
}
다음 요청
GET /boards?cursor=20
Spring에서는 일반적으로
@RequestParam Long cursor
로 받는다.
| Offset | Keyset |
|---|---|
| OFFSET 사용 | WHERE 조건 사용 |
| 앞의 데이터를 읽고 버림 | 마지막 Key부터 조회 |
| Scan | Seek |
| 뒤 페이지일수록 느림 | 성능이 거의 일정 |
Offset은 페이지가 뒤로 갈수록 앞의 데이터를 계속 읽고 버려야 하므로 조회 비용이 증가합니다. Keyset은 마지막으로 조회한 키를 WHERE 조건으로 전달하여 인덱스에서 해당 위치부터 바로 탐색(Seek)하기 때문에 대용량에서도 일정한 성능을 유지합니다.