Как кэшировать разбитый на страницы запрос Django

Как кэшировать разбитый на страницы Django запрос, особенно в ListView?

Я заметил, что один запрос занимает много времени, поэтому я пытаюсь его кэшировать. Набор запросов огромен (более 100 тыс. Записей), поэтому я пытаюсь только кэшировать разбитые на него подразделы. Я не могу кэшировать весь вид или шаблон, потому что есть разделы, которые специфичны для пользователя / сессии и должны постоянно меняться.

В ListView есть несколько стандартных методов для извлечения get_queryset() , get_queryset() , который возвращает непамицированные данные и paginate_queryset() , который фильтрует его на текущей странице.

Сначала я попытался кэшировать запрос в get_queryset() , но быстро реализованный вызов cache.set(my_query_key, super(MyView, self).get_queryset()) вызывал сериализацию всего запроса.

Итак, я попробовал переопределить paginate_queryset() как:

 import time from functools import partial from django.core.cache import cache from django.views.generic import ListView class MyView(ListView): ... def paginate_queryset(self, queryset, page_size): cache_key = 'myview-queryset-%s-%s' % (self.page, page_size) print 'paginate_queryset.cache_key:',cache_key t0 = time.time() ret = cache.get(cache_key) if ret is None: print 're-caching' ret = super(MyView, self).paginate_queryset(queryset, page_size) cache.set(cache_key, ret, 60*60) td = time.time() - t0 print 'paginate_queryset.time.seconds:',td (paginator, page, object_list, other_pages) = ret print 'total objects:',len(object_list) return ret 

Тем не менее, это занимает почти минуту, чтобы работать, хотя только 10 объектов извлекаются, и каждый запрос показывает «повторное кэширование», подразумевая, что в кеш ничего не сохраняется.

Мои settings.CACHE CACHE выглядит так:

 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', } } 

и service memcached status показывает, что memcached запущен, а tail -f /var/log/memcached.log абсолютно ничего не показывает.

Что я делаю не так? Каким образом можно кэшировать разбитый на страницы запрос, чтобы весь запрос не был получен?

Изменить: я думаю, что это может быть ошибка в memcached или оболочке Python. Кажется, что Django поддерживает два разных memcached-сервера: один использует python-memcached и один использует pylibmc. Кажется, что python-memcached молча скрывает ошибку, paginate_queryset() значение paginate_queryset() . Когда я переключился на бэкэнд pylibmc, теперь я получаю явное сообщение об ошибке «ошибка 10 из memcached_set: SERVER ERROR», переходящее назад в django / core / cache / backends / memcached.py в строке, строка 78.

2 Solutions collect form web for “Как кэшировать разбитый на страницы запрос Django”

Вы можете расширить Paginator для поддержки кэширования с помощью предоставленной cache_key .

Сообщение в блоге об использовании и реализации такого CachedPaginator можно найти здесь . Исходный код размещен на djangosnippets.org (здесь есть ссылка на веб-актер, потому что оригинал не работает).

Однако я опубликую немного модифицированный пример из исходной версии, который может не только кэшировать объекты на странице, но и общее количество. (иногда даже счет может быть дорогостоящей операцией).

 from django.core.cache import cache from django.utils.functional import cached_property from django.core.paginator import Paginator, Page, PageNotAnInteger class CachedPaginator(Paginator): """A paginator that caches the results on a page by page basis.""" def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True, cache_key=None, cache_timeout=300): super(CachedPaginator, self).__init__(object_list, per_page, orphans, allow_empty_first_page) self.cache_key = cache_key self.cache_timeout = cache_timeout @cached_property def count(self): """ The original django.core.paginator.count attribute in Django1.8 is not writable and cant be setted manually, but we would like to override it when loading data from cache. (instead of recalculating it). So we make it writable via @cached_property. """ return super(CachedPaginator, self).count def set_count(self, count): """ Override the paginator.count value (to prevent recalculation) and clear num_pages and page_range which values depend on it. """ self.count = count # if somehow we have stored .num_pages or .page_range (which are cached properties) # this can lead to wrong page calculations (because they depend on paginator.count value) # so we clear their values to force recalculations on next calls try: del self.num_pages except AttributeError: pass try: del self.page_range except AttributeError: pass @cached_property def num_pages(self): """This is not writable in Django1.8. We want to make it writable""" return super(CachedPaginator, self).num_pages @cached_property def page_range(self): """This is not writable in Django1.8. We want to make it writable""" return super(CachedPaginator, self).page_range def page(self, number): """ Returns a Page object for the given 1-based page number. This will attempt to pull the results out of the cache first, based on the requested page number. If not found in the cache, it will pull a fresh list and then cache that result + the total result count. """ if self.cache_key is None: return super(CachedPaginator, self).page(number) # In order to prevent counting the queryset # we only validate that the provided number is integer # The rest of the validation will happen when we fetch fresh data. # so if the number is invalid, no cache will be setted # number = self.validate_number(number) try: number = int(number) except (TypeError, ValueError): raise PageNotAnInteger('That page number is not an integer') page_cache_key = "%s:%s:%s" % (self.cache_key, self.per_page, number) page_data = cache.get(page_cache_key) if page_data is None: page = super(CachedPaginator, self).page(number) #cache not only the objects, but the total count too. page_data = (page.object_list, self.count) cache.set(page_cache_key, page_data, self.cache_timeout) else: cached_object_list, cached_total_count = page_data self.set_count(cached_total_count) page = Page(cached_object_list, number, self) return page 

Проблема оказалась комбинацией факторов. В основном, результат, возвращаемый paginate_queryset() содержит ссылку на неограниченный набор запросов, что означает, что он по существу не доступен. Когда я вызывал cache.set(mykey, (paginator, page, object_list, other_pages)) , он пытался сериализовать тысячи записей, а не только page_size которые я ожидал, заставляя кешированный элемент превышать лимиты memcached и терпеть неудачу ,

Другим фактором было ужасное сообщение об ошибке по умолчанию в memcached / python-memcached, которое молча скрывает все ошибки и превращает cache.set () в nop, если что-то пойдет не так, что делает его очень трудоемким для отслеживания проблемы.

Я исправил это, по существу переписывая paginate_queryset() чтобы полностью скомпоновать функциональные возможности paginator Django и вычислить набор запросов самостоятельно:

 object_list = queryset[page_size*(page-1):page_size*(page-1)+page_size] 

а затем кешировать этот object_list .

  • Каков наилучший метод сериализации для объектов в memcached?
  • Получить список ключей кэша в Django
  • Установка объекта в API кэша Django завершается сбоем из-за ошибки сортировки
  • Какой хороший аналог Flask / Python / WSGI для общедоступных хранилищ PHP Apache, таких как apc_store / apc_fetch?
  • Что делать, если я хочу сохранить значение None в memcache?
  • Использование нескольких серверов memcache в пуле
  • Какие шаги необходимы для реализации memcached в приложении Django?
  • Максимальный размер объекта, который можно сохранить в memcached с помощью memcache.py
  •  
    Interesting Posts for Van-Lav

    Как помечать наиболее эффективный способ столбца фрейма данных значениями другого фрейма данных в python / pandas?

    RAII в Python – автоматическое разрушение при выходе из области

    Запуск внешних программ Python с помощью Eclipse PyDev

    Ошибка «Невозможно загрузить профиль», произошедшую с Selenium WebDriver от Python3.5 и FF48

    Data Modeling Advice for Blog Tagging system в Google App Engine

    Python 3 – Zip – это итератор в кадре данных pandas

    Как скомпилировать статическую библиотеку с -fPIC из boost.python

    Google App Engine: нет модуля с именем google.api

    Python urllib2 не может открыть localhost на альтернативном порту (не 80)? Ошибка 10013

    удалить узел в двоичном дереве поиска python

    Как / где Python ищет модули?

    Python3 + MySql: Ошибка при загрузке модуля MySQLdb: нет модуля с именем 'MySQLdb'

    Как читать файлы .cap, отличные от Pyshark, которые быстрее, чем rdpcap () Scapy?

    Pandas: объединить строки и int столбцы

    На клавиатуре вставляем строку кода в (mac) vim, для pdb

    Python - лучший язык программирования в мире.