@wawa

Как ускорить пагинацию в Django?

В табличке ~3.5 млн записей, но сервер дохленький.
Использую стандартный Paginator из джанги.
Понимаю, что он сначала делает запрос COUNT(*) ..., а потом уже переданный QuerySet.
Сначала всё было ок, но когда QuerySet усложнился (добавилась аннотация) запрос COUNT(*)... от пагинатора стал дико тормозить (> 10sec). Сам же QuerySet выполняется мгновенно (по индексу). Т.е. проблема в запросе COUNT(*)... пагинатора, где присутствует даже ...GROUP BY... по первичному ключу - короче совсем никуда не годится.

В голове витает вариант:
Самому делать offset/limit, а информация о кол-ве не нужна. Т.е. конец пагинации заранее не известен. А чтобы узнать что это была последняя страница - делать limit на 1 больше чем размер страницы.

Тем не менее как новичок хотел бы узнать как обычно решают подобные проблемы? И есть ли готовые решения (может даже в самой джанге). Могу навелосипедить, но будет глупо если существует стандарт (де факто) для таких ситуаций.
  • Вопрос задан
  • 590 просмотров
Решения вопроса 1
@wawa Автор вопроса
Имела место ошибка.
Изначально в целях оптимизаций я решил ограничить максимальный offset, ибо юзеру не нужно глубоко листать и куда разумнее поиграть с фильтром поиска. По-моему нередкий приём (хабр например).
Так как джанго пагинатор не имеет возможность ограничить offset, сделано следующее:
qs = MyModel.objects.filter(...)
objects = qs[:MAX_PAGE * PER_PAGE]  #2
paginator = Paginator(objects, PER_PAGE)
page = paginator.page(...)

Да, такой подход всегда дёргает из БД больше записей чем нужно, но (MAX_PAGE * PER_PAGE) - не большой (~500) и оверхед незаметен. Хотя может позже и напишу свой пагинатор.
Как ожидалось в строке 2 происходит запрос к БД и возвращает список объектов, который ниже передаётся пагинатору. Далее пагинатору нужно знать сколько вообще есть объектов, и он сначала, рассчитывая на QuerySet, пробует вызвать .count() и в случае неудачи вызывает .__len__(). Я полагался на второй случай и ошибся.
Оказывается в строке 2 возвращается не список, а всё еще QuerySet. Нет, я в курсе, что он ленивый, но на слайсинге "ленивость" не применима. Верно?
Так или иначе, то что получено в строке, 2 имеет метод count(), и в пагинаторе он благополучно вызывается и инициирует запрос к БД. При этом запрос какой-то чудовищный (GROUP BY зачем-то юзается).
Именно это и тормозило работу. А решение таково:
qs = MyModel.objects.filter(...)
objects = list(qs[:MAX_PAGE * PER_PAGE])  #2 !!!
paginator = Paginator(objects, PER_PAGE)
page = paginator.page(...)

Неравнодушным как всегда спасибо!
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы