Django と Celery で非同期処理を実装します。
リアルタイムで重たい処理を実行したい場合、様々な問題が出てきます。パッと思いつくのは、Apache や Nginx など Web サーバーのタイムアウト問題でしょうか。あと、ユーザを待たせてしまうUXの問題などもあります。
この問題を解決する方法として、重たい処理は非同期としてバックグラウンドで実行し、HTTP のレスポンスは、すぐに返してしまう方法があります。
Contents [show]
完成イメージ
環境とバージョン
- CentOS 7.4.1708
- Python 3.6.4
- Django 2.0.3
- Celery 4.2.0
- django-celery-results 1.0.1
- mysqlclient 1.3.12
- epel-release 7.11
- Redis 3.2.10
- MySQL 5.7
インストール
Python, Django
Python と Django のインストールは、Python Web フレームワーク Django の環境を構築するの記事を参考にしてください。
Celery, django-celery-results, redis
1 | $ pip install celery django-celery-results redis |
Celery と Celery で使用する Python パッケージをインストールします。
MySQL のインストール
1 2 | $ sudo yum-config-manager --disable ius $ sudo yum install mysql mysql-devel mysql-server mysql-utilities |
Celery の結果を DB に保存するために MySQL をインストールします。
MySQL のインストールについては、こちらを参考にしてください。
Python を ius リポジトリでインストールした場合、MySQL リポジトリと衝突します。
なので、先に ius リポジトリを disable にしてください。
mysqlclient
1 | $ sudo pip install mysqlclient |
MySQL に接続するための Python パッケージをインストールします。
Redis
1 2 3 | $ sudo yum install epel-release $ sudo yum install redis $ sudo systemctl start redis |
Broker は RabbitMQ ではなく、Redis を使いたいと思います。
実装
パッケージのインストールが一通り終わりましたので実装して行きます。
tree
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $ tree . ├── app │ ├── __init__.py │ ├── apps.py │ ├── tasks.py │ ├── templates │ │ └── app │ │ └── index.html │ └── views.py ├── manage.py └── proj ├── __init__.py ├── celery.py ├── settings.py ├── urls.py └── wsgi.py 4 directories, 11 files |
まず最初に、ファイル構成です。このように実装していきます。
settings.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | ALLOWD_HOSTS = [ '*' ] INSTALLED_APPS = [ 'app.apps.AppConfig' , 'django_celery_results' , ... ] DATABASES = { 'default' : { 'ENGINE' : 'django.db.backends.mysql' , 'NAME' : 'proj' , 'USER' : 'root' , 'PASSWORD' : 'Eba7|B33veK+' , 'OPTIONS' : { 'charset' : 'utf8mb4' , } } } CELERY_RESULT_BACKEND = 'django-db' |
settings.py です。
Celery の実行結果を MySQL に保存するために、django-celery-results を使います。そのため、INSTALLED_APPS と CELERY_RESULT_BACKEND の設定をします。
celery.py
1 2 3 4 5 6 7 8 | import os from celery import Celery os.environ.setdefault( 'DJANGO_SETTINGS_MODULE' , 'proj.settings' ) app = Celery( 'proj' ) app.config_from_object( 'django.conf:settings' , namespace = 'CELERY' ) app.autodiscover_tasks() |
Celery の設定ファイルを実装します。この celery.py は settings.py と同じ階層に保存します。
__init__.py
1 2 3 | from .celery import app as celery_app __all__ = ( 'celery_app' ,) |
この __init__.py は celery.py と同じ階層の __init__.py です。
tasks.py
1 2 3 4 5 | from celery import shared_task @shared_task def add(x, y): return x + y |
tasks.py は、非同期処理のメインとなる関数を実装するファイルです。
サンプルとして、足し算結果を返す関数を実装しましたが、本来であれば、ここに重たい処理がくると思います。
urls.py
1 2 3 4 5 6 | from django.urls import path from app.views import index urlpatterns = [ path(' ', index, name=' index'), ] |
ブラウザから動作を確認するために、urls.py を用意します。
views.py
1 2 3 4 5 6 7 8 9 | from django.shortcuts import render from django_celery_results.models import TaskResult from app.tasks import add def index(request): add.delay( 1 , 1 ) object_list = TaskResult.objects. all ().order_by( 'pk' ) context = { 'object_list' : object_list} return render(request, 'app/index.html' , context) |
アクセスがあったら、バックグランドに足し算処理を投げ、ブラウザにすぐにレスポンスを返します。
ページに何か表示した方がいいと思ったので、Celery の結果を表示しています。
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | < html > < head > < title >Django と Celery で非同期処理を実装する</ title > </ head > < body > < h1 >Django と Celery で非同期処理を実装する</ h1 > < table > < thead > < tr > < th >#</ th > < th >Task Id</ th > < th >Task Name</ th > < th >Status</ th > < th >Result</ th > < th >Date Done</ th > </ tr > </ thead > < tbody > {% for object in object_list %} < tr > < td >{{ forloop.counter }}</ td > < td >{{ object.task_id }}</ td > < td >{{ object.task_name }}</ td > < td >{{ object.status }}</ td > < td >{{ object.result }}</ td > < td >{{ object.date_done }}</ td > </ tr > {% endfor %} </ tbody > </ table > </ body > </ html > |
DB マイグレーション
Create database
1 | $ mysql -u root -p -e 'create database proj default charset utf8mb4' |
非同期結果を DB に保存しますので、まずデータベースを用意します。
migrate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $ python3.6 manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, django_celery_results, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying django_celery_results.0001_initial... OK Applying sessions.0001_initial... OK |
データベースを用意したら、テーブルを作成します。
動作確認
Django の起動
1 2 3 4 5 6 7 8 | $ python3.6 manage.py runserver 0.0.0.0:8000 Performing system checks... System check identified no issues (0 silenced). July 04, 2018 - 04:29:40 Django version 2.0.3, using settings 'proj.settings' Starting development server at http: //0 .0.0.0:8000/ Quit the server with CONTROL-C. |
ブラウザでアクセスするために、Django のデバッグ用サーバー runserver を起動します。
Celery の起動
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | $ celery -A proj worker -l info -------------- celery@localhost.localdomain v4.2.0 (windowlicker) ---- **** ----- --- * *** * -- Linux-3.10.0-693.21.1.el7.x86_64-x86_64-with-centos-7.4.1708-Core 2018-07-04 04:35:30 -- * - **** --- - ** ---------- [config] - ** ---------- .> app: proj:0x7f42c6e217f0 - ** ---------- .> transport: redis: //localhost :6379 /0 - ** ---------- .> results: - *** --- * --- .> concurrency: 1 (prefork) -- ******* ---- .> task events: OFF ( enable -E to monitor tasks in this worker) --- ***** ----- -------------- [queues] .> celery exchange=celery(direct) key=celery [tasks] . app.tasks.add [2018-07-04 04:35:30,719: INFO /MainProcess ] Connected to redis: //localhost :6379 /0 [2018-07-04 04:35:30,743: INFO /MainProcess ] mingle: searching for neighbors [2018-07-04 04:35:31,794: INFO /MainProcess ] mingle: all alone [2018-07-04 04:35:31,813: WARNING /MainProcess ] /usr/lib/python3 .6 /site-packages/celery/fixups/django .py:200: UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments! warnings.warn( 'Using settings.DEBUG leads to a memory leak, never ' [2018-07-04 04:35:31,813: INFO /MainProcess ] celery@localhost.localdomain ready. |
非同期処理をするために、Celery を起動します。
ブラウザで http://192.168.33.10:8000 にアクセス
ブラウザでアクセスする毎に非同期処理 Celery がバックグラウンドで実行し、結果が表示されるのが確認できると思います。
まとめ
Django と Celery で非同期処理を実装しました。
今回は、足し算するだけの軽い処理なので、本当は非同期にする必要がないですね。
しかし、最初から重たい処理になることがわかっている場合や、運用後にデータ量が多くなり、処理が重たくなってきた場合は、Celery で非同期処理を検討してみるといいかもしれません。