Herokuに乗ったDjangoに非同期処理をさせたいよ~~
Herokuは30秒レスポンスが返らないとタイムアウトさせてしまいます.
公式ドキュメントでも以下のように時間のかかる処理は非同期で行うことを推奨(というかほぼ強制)しています.
- メールの送信
- リモート API へのアクセス (Twitter への投稿、Flickr の照会など)
- Web スクレイピングまたは Web クローリング
- 画像または PDF のレンダリング
- 大量の計算 (フィボナッチ数列の計算など)
- データベースの大量使用 (時間のかかるクエリまたは多数のクエリ、N+1 クエリ)
Herokuのドキュメントに沿って作ってみる
Heroku自体がPythonの非同期処理についてまとめているのでそれに沿って作ってみます.
https://devcenter.heroku.com/ja/articles/python-rq
まずworkerを作成します.
import os
import redis
from rq import Worker, Queue, Connection
listen = ['high', 'default', 'low']
redis_url = os.environ.get('REDISTOGO_URL', 'redis://localhost:6379')
sys.path.append(os.getcwd())
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myapp.settings')
conn = redis.from_url(redis_url)
if __name__ == '__main__':
with Connection(conn):
worker = Worker(map(Queue, listen))
worker.work()
この時,Djangoの設定を読み込みます.
setdefaultで渡すだけだとそんなモジュールないが?って怒られてしまうのでpathも追加してください.
キューを作って投げ込み動かします.
投げ込むジョブはあらかじめUtils.pyにでも外だしで書いておきましょう.
from django.views import generic
from rq import Queue
from worker import conn
from myapp.utils import myfunc
class myView(generic.View):
context_object_name = 'myView'
def get(self, request, *args, **kwargs):
q = Queue(connection=conn, default_timeout=600)
r = q.enqueue(myfunc, request.param)
context = self.get_context_data()
return render(request, 'myapp/myView.html', context)
あとはQueue作って関数を投げ込むだけです.難しくないですね.
web: gunicorn myapp.wsgi --log-file -
worker: python worker.py
Heroku向けのProcfileにworkerを追加しておきましょう.
heroku scale worker=1
Herokuにデプロイしたらscaleでworkerを増やしましょう.
Django-rqを使う方法
こちらを参考に実装します.
Heroku特有の手続きはworkerをProcfileに登録することとsettings.pyをちょっと変えることだけです.
RQ_QUEUES = {
'default': {
'HOST': 'localhost',
'PORT': 6379,
'DB': 0,
'PASSWORD': '',
'DEFAULT_TIMEOUT': 7200,
'URL': os.getenv('REDISTOGO_URL', 'redis://localhost:6379/0'), #追加
},
}
web: gunicorn myapp.wsgi --log-file -
worker: python manage.py rqworker
こっちの方が固有ライブラリを使っている分すっきりしますね.(settingsに設定まとまるし)
終わりに
Herokuでは好きなだけ非同期処理のworkerが増やせますが,料金もworkerの分だけメインのdynoと同じ料金でかかるので個人趣味でやる分には結構負担が大きいです.
大した処理しないのに料金倍にしてworker作るのもなぁ~~ってなっているので良い方法あったら教えてください.