2011-07-31 6 views
8

私はdjango Webアプリケーションユーザーに見える "analytics dashboard"画面を持っており、計算に時間がかかります。これらの画面の1つは、ユーザーのデータベース内のすべてのトランザクションを通過し、そのユーザーに対してメトリックを提供する画面の1つです。計算に時間がかかるWeb解析画面用のDjangoデザインパターン

Iこれはリアルタイム性であるためには大好きだが、計算時間は、アクティブなユーザーのために、20〜30秒でできる(ページングが許可されていない、それが取引上の平均値を与えている。)

頭に浮かぶソリューションこれをバックエンドでmanage.pyバッチコマンドで計算し、キャッシュされた値をユーザに表示するだけです。これらのタイプのモデル/ディスプレイを容易にするためのDjangoデザインパターンはありますか?

答えて

14

あなたが探しているのは、オフライン処理とキャッシュの組み合わせです。オフラインでは、計算ロジックが要求 - 応答サイクルの外で行われることを意味します。キャッシングすることで、高価な計算の結果が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などを使ってクライアント側の実際のグラフを生成しています。フローの例を次に示します。

    1. ユーザーは、平均チャートを含むページをリクエストします。
    2. キャッシュをチェックする - このユーザーの平均値を格納している、保存されていないクワイエットがありますか?
    3. はいの場合は、それを使用してください。
    4. セロリのタスクを起動して再計算し、再クエリーして結果をキャッシュします。 既存のデータを照会するのは安価なので、その間に失効したデータをユーザーに表示したい場合は、照会を実行してください。
  • グラフが古くなっている場合。必要に応じて、グラフが古くなっていることを示す情報を提供するか、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) 
    

    編集:クエリセットを酸洗すると、メモリに全体のクエリセットをロードすることは注目に値する。あなたが平均クエリーセットで多くのデータを取り上げているなら、これは最適ではない可能性があります。実際のデータを使ったテストはどんな場合でも賢明です。

    +0

    非常に徹底的に役立つ、Idanに感謝します。 – thomallen

    1

    Django’s cache frameworkをご覧ください。

    +0

    初期の20〜30秒間の読み込み時間を修正/回避する必要があります。だから私はバックエンドのキャッシュデザインパターンが必要です。 – MikeN

    3

    このようなシナリオで最も簡単で正確な解決策は、ユーザーがダッシュボードを見たときに計算された値だけを表示するだけで、すべてが事前に計算されることです。

    これを行うにはさまざまな方法がありますが、一般的な考え方は、計算が変更に依存するものがある場合はバックグラウンドで計算機能をトリガーすることです。私は通常celeryを使用し、バックグラウンドでこのような計算をトリガするための

    、そう仮定するユーザーがビューview_fooでアイテムfooを追加し、我々は、代わりにあなたがセロリを持つことができ、バックグラウンドで実行され、FOO数を更新しますセロリのタスクupdate_foo_countを呼び出します再計算が必要かどうかをチェックして、10分ごとにカウントを更新するタイマー。ユーザーがデータを更新するさまざまな場所で再計算フラグを設定できます。

    0

    データが追加されたときに、計算が遅いデータが非正規化されて保存される可能性がある場合は、表示されたときではなく、django-denormに興味があります。