Пагинация, фильтрация, throttling
Как отдавать большие списки порциями, давать клиенту фильтровать и искать, и не дать заспамить API.
Пагинация режет большой список на страницы; фильтрация, поиск и сортировка позволяют клиенту сузить и упорядочить выдачу; throttling ограничивает число запросов за период. Вместе они превращают «голый» список в управляемый и защищённый эндпоинт.
Зачем это знать на практике
Эндпоинт, который отдаёт Article.objects.all() целиком, работает ровно до тех пор, пока записей сотня. На десятках тысяч он кладёт и базу, и клиента: один запрос тянет мегабайты JSON. А открытый API без ограничения частоты — приглашение для скрапера и перебора паролей. Эти три механизма — не «украшения», а обязательная гигиена любого публичного списка.
Пагинация: три вида
Глобально пагинацию включают в настройках, задав класс и размер страницы:
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS":
"rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
}
DRF предлагает три стиля:
| Класс | Параметры | Когда |
PageNumberPagination | ?page=3 | привычные страницы «1,2,3…» |
LimitOffsetPagination | ?limit=20&offset=40 | гибкое «дай N, начиная с M» |
CursorPagination | ?cursor=... | большие/живые ленты, стабильность |
Ответ с пагинацией оборачивает данные в конверт со счётчиком и ссылками:
{
"count": 137,
"next": "http://api/articles/?page=3",
"previous": "http://api/articles/?page=1",
"results": [ ... 20 объектов ... ]
}
PageNumberPagination прост, но на «живых» данных едет: если между запросами страниц вставили запись, объекты сдвигаются и одна и та же запись может попасть на две страницы или потеряться. CursorPagination лишён этого: он листает не по номеру, а по указателю на поле сортировки (обычно по времени), поэтому стабилен на постоянно растущих лентах, хотя и не даёт прыгнуть на произвольную страницу.
Фильтрация через django-filter
Чтобы клиент выбирал записи по полям (?author=7&status=published), подключают пакет django-filter:
REST_FRAMEWORK = {
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend",
],
}
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
filterset_fields = ["author", "status"]
Теперь работают точные фильтры по этим полям. Для более тонких правил (диапазоны, «содержит») описывают FilterSet:
import django_filters
class ArticleFilter(django_filters.FilterSet):
min_views = django_filters.NumberFilter(
field_name="views", lookup_expr="gte")
title = django_filters.CharFilter(lookup_expr="icontains")
class Meta:
model = Article
fields = ["author", "status"]
# теперь возможен запрос ?min_views=100&title=drf
Поиск и сортировка
Помимо точных фильтров DRF даёт два готовых backend-а. SearchFilter — полнотекстовый поиск по нескольким полям через один параметр ?search=. OrderingFilter — сортировка через ?ordering= (минус — по убыванию).
from rest_framework import filters
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ["title", "body"]
ordering_fields = ["views", "created_at"]
# ?search=django — ищет в title и body
# ?ordering=-views — сначала самые просматриваемые
Эти три механизма свободно комбинируются: один запрос может одновременно фильтровать, искать и сортировать — каждый backend по очереди сужает queryset.
Throttling: ограничение частоты
Throttling задаёт потолок «сколько запросов за период». Глобально настраивают классы и нормы:
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "20/hour",
"user": "1000/day",
},
}
AnonRateThrottle ограничивает анонимов по IP, UserRateThrottle — залогиненных по их id. Когда лимит исчерпан, DRF отвечает 429 Too Many Requests и заголовком Retry-After. Для отдельных тяжёлых действий применяют ScopedRateThrottle — своя норма на «область»:
class PasswordResetView(APIView):
throttle_scope = "password_reset"
# в настройках: DEFAULT_THROTTLE_RATES = {"password_reset": "5/hour"}
Так дорогой и чувствительный эндпоинт (сброс пароля, отправка письма) получает жёсткий отдельный лимит, не затрагивая остальной API.
Как это работает под капотом
Все три механизма встроены в один и тот же конвейер вьюхи, но в разных точках. Фильтрация, поиск и сортировка применяются к queryset до выборки из БД: каждый backend из filter_backends получает текущий queryset и возвращает суженный, добавляя в SQL условия WHERE и ORDER BY. Затем пагинатор берёт уже отфильтрованный queryset и доклеивает LIMIT/OFFSET — поэтому из базы всегда тянется ровно одна страница, а не всё подряд. Throttling же срабатывает раньше всех, ещё в initial(), до обращения к данным: класс throttling по ключу (IP или id пользователя) хранит в кеше отметки времени недавних запросов и, если их больше нормы, прерывает обработку исключением Throttled с кодом 429. Из-за этого порядка throttling защищает в том числе и от запросов, которые иначе устроили бы тяжёлую выборку.
Частые ошибки
- Отдавать список без пагинации. На больших таблицах один запрос грузит базу и забивает канал; страницы обязательны.
- Брать PageNumberPagination для растущей ленты. Вставки сдвигают объекты между страницами; для лент берите
CursorPagination. - Сортировать по любому полю по запросу клиента. Перечисляйте
ordering_fieldsявно, иначе клиент отсортирует по неиндексированному полю и положит базу. - Забыть про throttling на публичных и логин-эндпоинтах. Без лимита открыты скрапинг и перебор; задайте хотя бы
AnonRateThrottle. - Полагаться на throttling как на единственную защиту. Он сглаживает нагрузку и злоупотребления, но не заменяет аутентификацию и права.
Итоги
- Пагинация режет список на страницы:
PageNumberPagination,LimitOffsetPagination,CursorPagination(стабильна на живых лентах). django-filterдаёт фильтры по полям; тонкие правила (диапазоны,icontains) — черезFilterSet.SearchFilter(?search=) ищет по нескольким полям,OrderingFilter(?ordering=) сортирует; backend-ы комбинируются.- Throttling ограничивает частоту:
AnonRateThrottle/UserRateThrottle/ScopedRateThrottle, ответ429при превышении. - Фильтры и пагинация сужают
querysetдо SQL-выборки; throttling срабатывает раньше всего и защищает от тяжёлых запросов.