Что вам нужно, так это сочетание автономной обработки и кэширования.Под автономным я имею в виду, что логика вычислений происходит вне цикла запрос-ответ.Под кэшированием я подразумеваю, что результат ваших дорогих вычислений достаточно действителен для времени X, в течение которого вам не нужно пересчитывать его для отображения.Это очень распространенный шаблон.
Автономная обработка
Существует два широко используемых подхода к работе, которые должны происходить вне цикла запрос-ответ:
- Задания Cron (часто упрощаются с помощью настраиваемой команды управления)
- Celery
В относительном выражении, cron проще в настройке, а Celery - более мощным /гибкий.При этом Celery обладает фантастической документацией и полным набором тестов.Я использовал его в производстве почти для каждого проекта, и хотя он требует определенных требований, его настройка не слишком сложна.
Cron
Задания Cron - проверенный временем метод.Если все, что вам нужно, это запустить некоторую логику и сохранить какой-либо результат в базе данных, у задания cron нет нулевых зависимостей.Единственные неприятные моменты с заданиями cron - это запуск вашего кода в контексте вашего проекта django - то есть ваш код должен правильно загружать файл settings.py, чтобы знать о вашей базе данных и приложениях.Для непосвященных это может привести к некоторому ухудшению в предсказании правильных PYTHONPATH
и т. П.
Если вы идете по маршруту cron, хорошим подходом является написание собственной команды управления.Вам будет легко тестировать свою команду из терминала (и писать тесты), и вам не нужно будет делать какие-то особые помехи в верхней части команды управления для настройки правильной среды django.На производстве вы просто запускаете path/to/manage.py yourcommand
.Я не уверен, что этот подход работает без помощи virtualenv , который вы действительно должны использовать независимо от этого.
Еще один аспект, который следует рассмотреть с помощью cronjobs: если ваша логика принимает переменную величинувремени бежать, cron не знает этого вопроса.Милый способ убить ваш сервер - запускать двухчасовой cronjob вот так каждый час.Вы можете свернуть свой собственный механизм блокировки, чтобы предотвратить это, просто имейте это в виду - то, что начинается с короткого cronjob, может не сохраняться таким образом, когда ваши данные растут, или когда ваша RDBMS плохо себя ведет и т. Д. И т. Д.
Вв вашем случае кажется, что cron менее применим, потому что вам нужно будет рассчитывать графики для каждого пользователя очень часто, независимо от того, кто на самом деле использует систему.Вот где сельдерей может помочь.
Сельдерей
... это колени пчелы.Обычно люди напуганы требованием «по умолчанию» брокера AMQP.Настройка RabbitMQ не слишком обременительна, но требует немного выхода из комфортного мира Python.Для многих задач я просто использую redis в качестве хранилища задач для Celery.Доступны следующие настройки: :
CELERY_RESULT_BACKEND = "redis"
REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_DB = 0
REDIS_CONNECT_RETRY = True
Вуаля, нет необходимости в брокере AMQP.
Сельдерей дает множество преимуществ по сравнению с простыми заданиями cron.Как и cron, вы можете планировать периодических задач , но вы также можете запускать задачи в ответ на другие стимулы, не задерживая цикл запроса / ответа.
Если вы не хотите вычислятьграфик для каждого активного пользователя время от времени, вам нужно будет создать его по требованию.Я предполагаю, что запрашивать последние доступные средние значения дешево, вычислять новые средние значения дорого, и вы генерируете фактические диаграммы на стороне клиента, используя что-то вроде flot .Вот пример потока:
- Пользователь запрашивает страницу, которая содержит диаграмму средних значений.
- Проверьте кэш - есть ли сохраненный, не истекший набор запросов, содержащий средние значения для этого пользователя?
- Если да, используйте это.
- Если нет, запустите задачу сельдерея, чтобы пересчитать ее, запросить и кэшировать результат. Поскольку запрос существующих данных обходится дешево, запустите запрос, если хотите показать устаревшие данные пользователю.
- Если график устарел. при необходимости предоставьте некоторое указание на то, что график устарел, или сделайте некоторую изворотливость ajax, чтобы периодически пинговать django и спросите, готов ли обновленный график.
Вы можете комбинировать это с периодической задачей, чтобы каждый час пересчитывать диаграмму для пользователей, имеющих активный сеанс, чтобы предотвратить отображение действительно устаревших диаграмм. Это не единственный способ избавиться от кошки, но он предоставляет вам весь контроль, необходимый для обеспечения свежести при ограничении загрузки ЦП задачи вычисления. Лучше всего то, что периодическая задача и задача «по требованию» используют одну и ту же логику - вы определяете задачу один раз и вызываете ее из обоих мест для добавления DRYness.
Кэширование
Структура кеширования Django предоставляет вам все хуки, которые вам нужны, чтобы кэшировать все, что вы хотите, так долго, как вы хотите. Большинство производственных сайтов используют memcached в качестве бэкэнда кеша. В последнее время я начал использовать redis с бэкэндом django-redis-cache , но не уверен, что доверяю пока что для крупной производственной площадки.
Вот некоторый код, демонстрирующий использование низкоуровневого API кэширования для выполнения описанного выше рабочего процесса:
import pickle
from django.core.cache import cache
from django.shortcuts import render
from mytasks import calculate_stuff
from celery.task import task
@task
def calculate_stuff(user_id):
# ... do your work to update the averages ...
# now pull the latest series
averages = TransactionAverage.objects.filter(user=user_id, ...)
# cache the pickled result for ten minutes
cache.set("averages_%s" % user_id, pickle.dumps(averages), 60*10)
def myview(request, user_id):
ctx = {}
cached = cache.get("averages_%s" % user_id, None)
if cached:
averages = pickle.loads(cached) # use the cached queryset
else:
# fetch the latest available data for now, same as in the task
averages = TransactionAverage.objects.filter(user=user_id, ...)
# fire off the celery task to update the information in the background
calculate_stuff.delay(user_id) # doesn't happen in-process.
ctx['stale_chart'] = True # display a warning, if you like
ctx['averages'] = averages
# ... do your other work ...
render(request, 'my_template.html', ctx)
Редактировать: Стоит отметить, что выборка набора запросов загружает весь набор запросов в память. Если вы набираете много данных с помощью своего среднего набора запросов, это может быть неоптимальным. В любом случае было бы целесообразно проводить тестирование на реальных данных.