반응형
SMALL
Django를 사용하다보면 기본적으로 사용하는 DRF Pagination을 사용해왔다. 그러나 대량의 데이터를 페이지네이션을 하게되면, 퍼포먼스 및 최적화 관점에서 좋지 않다.
Pagination의 종류
구현 방법
- 오프셋 기반 페이지네이션(Offset-based Pagination)
- 커서 기반 페이지네이션(Cursor-based Pagination)
문제 상황
기존의 오프셋 기반 페이지네이션과 커서 기반의 페이지네이션의 차이는 무엇이고 왜 대용량의 페이지를 처리할때는 오프셋 기반보다 커서 기반으로 처리하는것이 좋은걸까?
기본적으로 많이 사용하는 Pagination은 Offset 기반의 Pagination이다.
Offset을 MySQL에서라면 간단하게 LIMIT 쿼리에 콤마를 붙여 ‘건너 뛸’ row 숫자를 지정하면 된다.
Offset-based Pagination의 간략한 설명과 문제점
SELECT * FROM `product`
LIMIT 10 OFFSET 10;
- OFFSET: 뛰어넘는 Row 개수
- LIMIT: 가져오는 개수 제한
- 왜? Why? 데이터 개수는 변경될 수 있기 때문에 매번 데이터를 확인하는(Full-scan) 해당 offset 수 만큼 지나가야하기 때문이다.
- 문제는 OFFSET에서 발생하게 된다. 예를들어 앞부분의 데이터를 조회하는 경우는 문제가 되지 않지만, 10만부터 40개씩 등과 같은 방식으로 조회할 경우 퍼포먼스가 굉장히 느려진다.
- 아래 코드를 보게 되면 이해가 빠를것이다. 기본적으로 DB는
- **ORDER BY id ASC**에 따라 전체 데이터셋을 정렬하게 되고,
- OFFSET 100만개의 행을 먼저 스캔하게 된다.
- 그 다음 10개의 결과를 반환한다.
- 아래 코드를 보게 되면 이해가 빠를것이다. 기본적으로 DB는
SELECT * FROM `product`
ORDER BY id ASC
LIMIT 10 OFFSET 1000000;
Cursor-based Pagination
SELECT * FROM `product`
WHERE id > {이전 페이지의 마지막 id}
LIMIT 10;
- Offset을 사용하지 않아서 zero Offset 방식이라고도 한다.
- 위의 방식으로 처리하면 DB의 인덱스를 활용하여 해당위치에서 바로 검색이 가능하다.
- 아래의 형태로 검색한다고 생각하면 될 것 같다!
사용법
- Settings에 선언하여 전역으로 사용하는 방법
# Settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
'PAGE_SIZE': 10,
}
- rest_framework의 CursorPagination을 상속받아서 처리
class CommonCursorPagination(CursorPagination):
page_size_query_param = 'page_size' # 유저가 동적으로 page_size를 받도록 허용
page_size = 10 # 몇개를 받을 것인가?
ordering = '-created'
cursor_query_param = "cursor" # cursor_query_param 옵션을 따로 사용하지 않으면, 기본으로 cursor 옵션이 들어간다.
views.py
# 전역으로 사용하지 않고 따로 상속을 받은 후 View에서 주입하여 처리하는 방식
class MyViewSet(
viewsets.GenericViewSet,
mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.DestroyModelMixin,
mixins.UpdateModelMixin,
mixins.RetrieveModelMixin,
):
queryset = Product.objects.all()
serializer_class = ProductSerializer
pagination_class = CommonCursorPagination
결과
"next": "<http://localhost:8000/link/me/?cursor=cD0yMDI1LTAyLTA4KzAwJTNBMDIlM0ExNy43ODUxMzklMkIwMCUzQTAw>",
"previous": null,
"results": [
{
"id": "fc98b586-b829-4bd1-b5a7-e841cf73c1bf",
반응형
LIST
'프로그래밍 > Django' 카테고리의 다른 글
Django ORM 최적화: GenericForeignKey를 활용한 데이터 모델링 (0) | 2025.02.19 |
---|---|
View가 깔끔해지는 Django 필터링 전략! get_queryset() vs filter_queryset() (0) | 2025.02.18 |
[Django]filter_fields로 URL 파라미터 사용하기 (0) | 2022.05.13 |
[Django] 멀티 DB 라우터설정 및 연동하기 (0) | 2022.05.11 |
[Django][에러노트]raise ValueError, "No frame marked with %s." % fname (0) | 2022.05.07 |