Django と Celery で非同期処理を実装します。
リアルタイムで重たい処理を実行したい場合、様々な問題が出てきます。パッと思いつくのは、Apache や Nginx など Web サーバーのタイムアウト問題でしょうか。あと、ユーザを待たせてしまうUXの問題などもあります。
この問題を解決する方法として、重たい処理は非同期としてバックグラウンドで実行し、HTTP のレスポンスは、すぐに返してしまう方法があります。
完成イメージ
環境とバージョン
- 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
$ pip install celery django-celery-results redis
Celery と Celery で使用する Python パッケージをインストールします。
MySQL のインストール
$ 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
$ sudo pip install mysqlclient
MySQL に接続するための Python パッケージをインストールします。
Redis
$ sudo yum install epel-release $ sudo yum install redis $ sudo systemctl start redis
Broker は RabbitMQ ではなく、Redis を使いたいと思います。
実装
パッケージのインストールが一通り終わりましたので実装して行きます。
tree
$ 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
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
import os from celery import Celery os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') app = Celery('proj') app.conf.broker_url = 'redis://localhost:6379/0' app.config_from_object('django.conf:settings', namespace='CELERY') app.autodiscover_tasks()
Celery の設定ファイルを実装します。この celery.py は settings.py と同じ階層に保存します。
__init__.py
from .celery import app as celery_app __all__ = ('celery_app',)
この __init__.py は celery.py と同じ階層の __init__.py です。
tasks.py
from celery import shared_task @shared_task def add(x, y): return x + y
tasks.py は、非同期処理のメインとなる関数を実装するファイルです。
サンプルとして、足し算結果を返す関数を実装しましたが、本来であれば、ここに重たい処理がくると思います。
urls.py
from django.urls import path from app.views import index urlpatterns = [ path('', index, name='index'), ]
ブラウザから動作を確認するために、urls.py を用意します。
views.py
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
<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
$ mysql -u root -p -e 'create database proj default charset utf8mb4'
非同期結果を DB に保存しますので、まずデータベースを用意します。
migrate
$ 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 の起動
$ 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 の起動
$ 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 で非同期処理を検討してみるといいかもしれません。
次の記事では、Celery の CentOS 7 用の起動スクリプトを作成し、systemd で管理できるようにしてみます。