あなたが探しているのは、オフライン処理とキャッシュの組み合わせです。オフラインでは、計算ロジックが要求 - 応答サイクルの外で行われることを意味します。キャッシングすることで、高価な計算の結果がX時間に十分有効であることを意味します。その間、表示のために再計算する必要はありません。これは非常に一般的なパターンです。
オフライン処理
要求応答サイクルの外に発生する必要のある作業には、2つの広く使用されているアプローチがあります(多くの場合、カスタム管理コマンドを使用して容易になり)
- cronジョブ
は、
- Celery
相対的に、cronはセットアップが簡単であり、Celeryはより強力で柔軟性があります。つまり、セロリは素晴らしいドキュメンテーションと包括的なテストスイートを楽しんでいます。私はほぼすべてのプロジェクトでプロダクションでそれを使用しましたが、いくつかの要件が含まれていますが、実際にセットアップするにはあまり意味がありません。
cron
cronジョブは、時間のかかる方法です。もしあなたが必要とするのは、いくつかのロジックを実行し、結果をデータベースに格納することだけです.cronジョブは、依存性がゼロです。cronジョブで悩んでいるのは、あなたのコードをdjangoプロジェクトのコンテキストで実行することです。つまり、データベースとアプリケーションについて知るためには、コードが正しくsettings.pyをロードする必要があります。初心者のために、これは適切なPYTHONPATH
などを割り振ることでいくつかの悪化につながる可能性があります。
cronルートを使用する場合は、カスタム管理コマンドを書くことをお勧めします。ターミナルからコマンドをテストするのは簡単です(そしてテストを書く)。適切なdjango環境を設定するには、管理コマンドの先頭に特別な嫌がらせをする必要はありません。プロダクションでは、単にpath/to/manage.py yourcommand
を実行します。このアプローチがvirtualenvの助けなしで機能するかどうかはわかりません。本当に関係なく使用する必要があります。
cronjobsと考えるべきもう1つの側面:実行するロジックの時間が変動する場合、cronはこの問題を無視しています。あなたのサーバーを殺すためのかわいい方法は、毎時間このような2時間のcronジョブを実行することです。これを防ぐために独自のロック機構を動かすことができます。これは、データが大きくなったときや、RDBMSが誤動作したときなど、短いクロージャーがそのままにならないようにすることから始まります。
実際に誰がシステムを使用しているかに関係なく、頻繁にすべてのユーザーのグラフを計算する必要があるため、cronはあまり適用されないように思えます。これはセロリが助けることができる場所です。
セロリ
...はミツバチです。通常、AMQPブローカーの「デフォルト」要件によって人々は恐怖を感じます。 RabbitMQを設定するのは大変面倒なことではありませんが、Pythonの快適な世界の外に足を踏み入れる必要があります。多くのタスクで、私はセロリのタスクストアとしてredisを使用します。設定はstraightforward次のとおりです。
CELERY_RESULT_BACKEND = "redis"
REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_DB = 0
REDIS_CONNECT_RETRY = True
出来上がり、AMQPブローカーの必要はありません。
セロリーは、単純なcronジョブに比べて豊富な利点を提供します。 cronと同様にperiodic tasksのスケジュールを設定できますが、要求/応答サイクルを守らずに他の刺激に応答してタスクを起動することもできます。
すべてのアクティブユーザーのチャートを頻繁に計算したくない場合は、オンデマンドで生成する必要があります。私は、最新のが利用可能であることを問い合わせていると仮定しています。平均値は安く、新しい平均値を計算するのは高価です。flotなどを使ってクライアント側の実際のグラフを生成しています。フローの例を次に示します。
- ユーザーは、平均チャートを含むページをリクエストします。
- キャッシュをチェックする - このユーザーの平均値を格納している、保存されていないクワイエットがありますか?
- はいの場合は、それを使用してください。
- セロリのタスクを起動して再計算し、再クエリーして結果をキャッシュします。 既存のデータを照会するのは安価なので、その間に失効したデータをユーザーに表示したい場合は、照会を実行してください。
グラフが古くなっている場合。必要に応じて、グラフが古くなっていることを示す情報を提供するか、djangoにpingを実行するためのajax fancinessを頻繁に行い、更新されたグラフが用意されているかどうかを尋ねます。
あなたが表示されているから本当に古いチャートを防ぐために、チャートにアクティブなセッションを持つユーザーごとに時間を再計算するために定期的なタスクと組み合わせることができます。これは猫の皮膚を傷つける唯一の方法ではありませんが、計算タスクのCPU負荷を抑えながら新鮮さを確保するために必要なすべてのコントロールを提供します。何よりも、定期タスクとオンデマンドタスクは同じロジックを共有しています。タスクを一度定義してDRY性を高めるために両方の場所から呼び出すことができます。
キャッシュ
Django cache frameworkはあなたがいる限り、あなたが望むようのために好きなキャッシュするために必要なすべてのフックを提供します。ほとんどのプロダクションサイトはキャッシュバックエンドとしてmemcachedに依存していますが、最近はdjango-redis-cacheバックエンドでredisを使用し始めましたが、まだ主要なプロダクションサイトで信頼できるとは思えません。
はここより上にレイアウトワークフローを実現するためにlow-level caching 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)
編集:クエリセットを酸洗すると、メモリに全体のクエリセットをロードすることは注目に値する。あなたが平均クエリーセットで多くのデータを取り上げているなら、これは最適ではない可能性があります。実際のデータを使ったテストはどんな場合でも賢明です。
非常に徹底的に役立つ、Idanに感謝します。 – thomallen