웹 개발을 진행하면서 사용자에게 대량의 데이터를 효과적으로 제공하는 방법 중 하나는 페이징네이션(페이징 분할)을 구현하는 것입니다. Django는 데이터를 페이지별로 나누어 제공할 수 있도록 'Paginator'클래스를 제공하지만, 때로는 이보다 더 복잡한 요구사항을 충족시키기 위해 커스텀 페이징네이션을 구현해야할 필요가 있습니다.
글에서는 DRF의 pagination을 활용한 방법과 이점, 다른 방안에 대해 알아보고자 합니다.
from collections import OrderedDict
from django.core.paginator import InvalidPage
from rest_framework.exceptions import NotFound
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
class CustomPagination(PageNumberPagination):
page_size = 9
page_size_query_param = 'size'
def paginate_queryset(self, queryset, request, view=None):
"""
요청된 쿼리셋을 페이징네이션 합니다. 이 함수는 요청된 페이지의 데이터를 반환하거나,
페이징네이션이 뷰에 설정되지 않았다면 None을 반환합니다.
"""
page_size = self.get_page_size(request)
if not page_size:
return None
paginator = self.django_paginator_class(queryset, page_size)
page_number = self.get_page_number(request, paginator)
try:
self.page = paginator.page(page_number)
except InvalidPage as exc:
return list()
if paginator.num_pages > 1 and self.template is not None:
self.display_page_controls = True
self.request = request
return list(self.page)
def get_previous_link(self):
"""
이전 페이지의 링크를 생성합니다.
"""
if not self.page.has_previous():
return None
page_number = self.page.previous_page_number()
return self.request.build_absolute_uri('?page={}&pageSize={}'.format(page_number, self.page_size))
def page_data_format(self, next, previous, page, size, max_page):
"""
페이지 정보를 포매팅하여 반환합니다.
"""
page_data = dict(
next=next,
previous=previous,
page=page,
size=size,
max_page=max_page,
)
return page_data
def get_paginated_response(self, data):
"""
페이징네이션된 응답을 생성합니다. 이 응답에는 페이지 데이터와 실제 데이터가 포함됩니다.
"""
if hasattr(self, 'page') and self.page is not None:
page_data = self.page_data_format(
next=self.get_next_link(),
previous=self.get_previous_link(),
page=self.request.GET.get('page', 1),
size=self.page.paginator.per_page,
max_page=self.page.paginator.num_pages,
)
else:
page_data = self.page_data_format(
next=None,
previous=None,
page=1,
size=9,
max_page=0,
)
return Response(OrderedDict((
('page_data', page_data),
('data', data),
)))
def get_paginated_response_schema(self, schema):
"""
페이징네이션된 응답의 스키마를 정의합니다. API 문서에서 사용됩니다.
"""
return {
'type': 'object',
'properties': {
'page_data': {
'type': 'object',
'properties': {
'next': {
'type': 'string',
'example': 'http://localhost/api/products/?page=3&pageSize=9'
},
'previous': {
'type': 'string',
'example': "http://localhost/api/products/?page=1&pageSize=9"
},
'page': {
'type': 'integer',
'example': 1
},
'size': {
'type': 'integer',
'example': 9,
},
'max_page': {
'type': 'integer',
'example': 10,
}
},
},
'data': schema
}
}
get_next_link라는 함수로 다음 페이지로의 링크를 반환하고 다음페이지가 없으면 None을 반환할 수 있습니다., 이미 PageNuberPagination에 구현이 되어 있어 get_paginated_response 메서드에서 호출됩니다.
전체 데이터 중 지정된 수의 데이터만 전달함으로써 필요한 데이터의 수만 전달하므로 네트워크의 오버헤드를 줄일 수 있다.
커서 기반 페이징네이션은 데이터셋을 순차적으로 탐색할 때 유용하며, 특히 실시간으로 변경되는 데이터셋에서 뛰어난 성능을 제공합니다. 이 방식은 데이터의 '커서'를 기반으로 다음 데이터 묶음을 로드함으로써, 일관된 사용자 경험과 빠른 응답 속도를 보장합니다. 커서는 데이터의 특정 지점을 가리키는 포인터 역할을 하며, 이는 페이지 번호를 사용하는 전통적인 페이징네이션 방식과는 대조됩니다.
무한 스크롤은 사용자가 웹 페이지나 앱의 끝에 도달했을 때 자동으로 다음 세트의 데이터를 로드하는 기법입니다. 주로 소셜 미디어 피드나 뉴스 사이트에서 볼 수 있으며, 사용자가 페이지나 버튼을 클릭하지 않아도 자연스럽게 콘텐츠를 계속 탐색할 수 있게 해줍니다. 그러나, 이 방식은 특정 데이터를 찾기 어렵게 만들거나, 사용자가 스크롤 위치를 잃어버리게 할 수 있어, 적절한 사용 사례에서 신중하게 적용해야 합니다.
데이터 스트리밍은 클라이언트에게 데이터를 연속적으로 전송하는 방법으로, 대용량 데이터셋을 처리할 때 매우 유용합니다. 이 방식은 데이터를 작은 조각들로 나누어 순차적으로 전송하며, 사용자는 전체 데이터셋이 로드될 때까지 기다리지 않고 즉시 데이터를 볼 수 있습니다. 실시간 데이터 처리와 분석, 비디오 스트리밍 서비스 등에서 널리 사용됩니다.
데이터 압축은 서버와 클라이언트 간에 전송되는 데이터의 크기를 줄이는 과정입니다. 압축을 통해 데이터 전송 시간을 단축하고, 네트워크 대역폭을 절약할 수 있습니다. 특히, 대규모 파일이나 미디어 자원을 전송할 때 압축이 효과적입니다. 하지만, 압축과 압축 해제 과정에 추가적인 CPU 자원이 필요할 수 있음을 고려해야 합니다
데이터 적응형 로딩은 사용자의 네트워크 속도나 기기의 성능에 맞추어 로딩하는 데이터의 양을 조절하는 방식입니다. 이를 통해, 사용자는 자신의 환경에 최적화된 데이터 로딩 속도를 경험할 수 있습니다. 예를 들어, 느린 네트워크 환경에서는 우선적으로 중요한 콘텐츠만을 로드하고, 추가 정보는 선택적으로 로드할 수 있습니다.
프로그레시브 렌더링은 웹 페이지나 앱이 사용자에게 보여지는 방식을 최적화하는 기술입니다. 페이지의 중요한 부분을 우선적으로 로드하고, 이후 나머지 부분을 순차적으로 로드합니다. 이는 사용자가 초기 로딩 시간을 거의 기다리지 않고 바로 중요한 콘텐츠를 볼 수 있게 해주며, 전반적인 사용자 경험을 향상시킵니다.