Bluemixでワーカー オフロード パターンを実装する

WEBアプリケーションでは応答性能が求められることが頻繁にあります。いろいろ調べると、１０秒までは待てるようですが、なかには３秒が限界という結果もあるようです。待てる待てないは、人によりまちまちですが、速いほうが好感が持てるでしょう。この記事では、重たい処理をワーカープロセスに任せて、レスポンスをとりあえず返すという**ワーカー オフロード パターン**を紹介し、Bluemix での実装方法を簡単にご紹介したいと思います。

## ワーカー オフロード　パターン

WEBアプリケーションは、http リクエストをアプリケーションに投げて、何らかの処理を実行後に http レスポンスを返却します。仮に、この処理に時間を要すると応答までの時間がかかることになります。

ワーカー オフロード パターンというのは、処理に時間がかかる部分を別のプロセスに任せ、とりあえずは応答を返すというものです。

![WorkerOffload1.png](https://qiita-image-store.s3.amazonaws.com/0/74673/2760b379-4f2c-2eac-207a-3fbfd2617a23.png)
(図は MQLightのドキュメントサイトから抜粋 -  https://developer.ibm.com/messaging/mq-light/docs/ )


このパターンを実現するのにメッセージングがよく使われます。簡単には以下の図に示すように、メッセージシステムを利用して、処理依頼を**ワーカープロセス**に出します（図ではワーカーアプリ）。

![図2.png](https://qiita-image-store.s3.amazonaws.com/0/74673/cc53358c-8aa4-f250-4610-f4747c2db6aa.png)



ワーカープロセスは（時間がかかる)処理を実行した後に、処理結果をメッセージシステムを通じて通知をします。通知されたアプリケーションは、結果を読み取ります。

処理結果については、たとえば、ユーザーが再度WEBを開いた場合には、この結果を知らせるようにすればよいのですね。統計処理や、数値計算などを任せて後で見るというのに利用できます。処理依頼から結果を見るまでのターンアラウンド時間は、従来の処理よりもかかるかもしれませんが、応答という意味ではユーザーのストレスが軽減されるでしょう。


##Bluemixでの実装

Bluemix にはメッセージングシステムとして MQLight が提供されています。

- (注意）MQLight ではメッセージ数が上限を超えると課金されます。気になる場合は、Experimental のメッセージサービスを利用してください。

ここでは説明を簡単にするため、フロントエンドには [Node-RED](http://nodered.org/) を利用しました。Bluemix 上の Node-REDスターターキット には mqlight ノードがあるので、すばやく利用できます。

###Node-REDアプリケーションの実装

Node-RED はスターターキットを用いて作成し、コードをダウンロードします。MQLight サービスを利用するので、これを作成します。

	例：　
	$ cf create-service mqlight standard mqlight

さらに、テンプレートの manifest.yml を少し書き換えます。なお、Cloudant DB は筆者のスペースにある、既存の "cloudantdb"　を利用しているので、環境変数 ”NODE_RED_STORAGE_NAME” を設定しています。

	applications:
	- name: nodered
	  host: amntnodered
	  memory: 512M
	  instances: 1
	  domain: au-syd.mybluemix.net
	  disk_quota: 1024M
	  path: nodered
	  command: node --max-old-space-size=384 node_modules/node-red/red.js --settings ./bluemix-settings.js -v
	  services:
	   - cloudantdb
	   - mqlight
	  env:
	   NODE_RED_STORAGE_NAME: cloudantdb

Bluemixにデプロイした後に、フローを定義します。

![図3.png](https://qiita-image-store.s3.amazonaws.com/0/74673/9981b2f1-fbd7-a860-165b-54d33a6cc98b.png)


フローはこちらから参照できます。

	[{"id":"9e066960.61f998","type":"inject","name":"","topic":"","payload":"5","payloadType":"string","repeat":"","crontab":"","once":false,"x":135,"y":108,"z":"dfd55d9e.202aa","wires":[["1ca973b8.e3568c"]]},{"id":"c8286d2f.37d79","type":"mqlight out","name":"Request","service":"mqlight","topic":"mqlight/example/request","x":472,"y":108,"z":"dfd55d9e.202aa","wires":[]},{"id":"1ca973b8.e3568c","type":"function","name":"number","func":"var mqMsg = {\n    number: msg.payload\n};\n\nmsg.payload = mqMsg;\n\nreturn msg;","outputs":1,"noerr":0,"x":294,"y":108,"z":"dfd55d9e.202aa","wires":[["c8286d2f.37d79","3686002c.c97a"]]},{"id":"ef742968.108bd8","type":"mqlight in","name":"Result","service":"mqlight","topic":"mqlight/example/result","share":"sample","x":157,"y":210,"z":"dfd55d9e.202aa","wires":[["a7c58829.583a78"]]},{"id":"a7c58829.583a78","type":"debug","name":"","active":true,"console":"false","complete":"false","x":432,"y":211,"z":"dfd55d9e.202aa","wires":[]},{"id":"3686002c.c97a","type":"debug","name":"","active":true,"console":"false","complete":"false","x":488,"y":53,"z":"dfd55d9e.202aa","wires":[]}]

なおアプリケーションはフィボナッチ数を計算処理依頼をするというものです。フィボナッチ数列の定義はいくつかあるようですが、今回は以下のように定義しました。

 F(0) = 1
 F(1) = 1
 F(n) = F(n-1) + F(n-2)

フローの上段は、数値を　MQLight に飛ばす処理です。下段のフローは、処理結果をメッセージで受け取り結果を出力する処理になっています。

###ワーカープロセスの実装

さて、ワーカープロセスですがこれは単純にメッセージキューからメッセージ（数値）を読み取りフィボナッチ数を計算して、結果をメッセージとして流すという処理になっています。この程度の計算なら、ワーカーオフロードパターンでなくてもよいのでは？と思われるかも知れませんが、サンプルですのでご容赦を。

サンプルソースコードは、GitHub の [Gist にアップロード](https://gist.github.com/ibmamnt/e349d7e162f68a03edf9) しておきました。なお、

	---- app.js -----
	app.js 本体
	---- package.json ----
	package.json の内容

となっていますのでご注意を。


以下はソースの要点です。細かい部分は割愛しました。

	// Setup messaging API info for Order system
	var PUBLISH_TOPIC = "mqlight/example/result";
	var SUBSCRIBE_TOPIC = "mqlight/example/request";
	var SHARE_ID = "example";
	var mqlight = require('mqlight');
	
	// Create Client, and wait for message to process.
	var mqlightClient = mqlight.createClient(mqlightService, function(err) {
		mqlightClient.on('message', processMessage);
		mqlightClient.subscribe(SUBSCRIBE_TOPIC, SHARE_ID);
	
	});


ここでは、 MQLight のクライアントを作成し、サブスクライブするトピックを指定します。メッセージが来たら、"processMessage" で処理します。

	function processMessage(data, delivery) {
	
		var request = data;
	 	var fibNum = fib(parseInt(request.number, 10));
	
		var replyData = {
			result: fibNum,
			status: 'SUCCESS'
		};
	    replyData = JSON.stringify(replyData);
	    console.log("Sending response: " + replyData);
	    mqlightClient.send(PUBLISH_TOPIC, replyData, {
	    		ttl: 60*60*1000 });
	}

processMessage() では、受け取ったメッセージ（ここでは数値）を解釈し、fib() 関数にて計算を実行した後に、PUBLISH_TOPIC にメッセージを送信します。

このアプリケーションは Bluemix に対して**ワーカープロセス**としてデプロイします。

ワーカープロセスは、ルートなしアプリケーション (--no-route)です。このアプリケーションをデプロイするための manifest.yml は以下のとおりです。

	applications:
	- name: worker
	  no-route: true
	  memory: 256M
	  disk_quota: 1024M
	  path: worker
	  command: node app.js
	  services:
	  - mqlight


ここで、**no-route : true**に着目してください。この指定で、ルートなし（つまり URLを持たない）アプリケーションがデプロイされます。"cf push”コマンドで、アプリケーションをデプロイすると以下のメッセージがでます。

	$ cf push worker
	.
	App worker is a worker, skipping route creation
	.


出ているメッセージをみると、App worker is a **worker**, と出力されていますね。実際に、Bluemix 上でも worker (ワーカー)と呼ばれることがわかります。正常にアプリケーションが稼動できたら、動作を確認します。

###動作確認

フィボナッチ数列は

 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946

などの数列です。最初を0(ゼロ)とするのが最新のようですが、ここでは1から開始しました。

Node-REDの Inject に２を入れると右側の Debug に 1 と出ます。Injectに20 を入れると 10946 が返ってきました。正常に動作しているようですね。

![図4.png](https://qiita-image-store.s3.amazonaws.com/0/74673/5edb8331-55af-4f94-a125-0cb5b52b3ed9.png)


なお、MQLight サービスにはダッシュボードがあるので、これらを眺めてもよいでしょう。



##まとめ

今回は、エンドユーザーに対する応答を速くするためのワーカーオフロードパターンと、ワーカーオフロードパターンをどのように Bluemix 上で実装するかをご紹介しました。さまざまな処理を細かく分散させる、マイクロサービスの考えに通じるかと思われます。

実装例は１つのプロセスで実施しましたが、重たい処理依頼がたくさんくる場合には、Bluemix のオートスケール機能を利用してもよいでしょう。