Azure Functionsのパフォーマンスに関する実験

昨日、ほんの出来心でMojoアプリをAzure Functionsで動かせたらどうだろうなどと考えてしまい、2時間弱くらいでMojo::Server::AzureFunctionsというものを作りました。

Mojo::Server::AzureFunctionsを作って使うまで

もともとAzure FunctionsのHTTP TriggerとHTTP output bindingをハンドリングするためのアダプタとして作ろうとあらかじめ決めており、Functions自体はリクエストごとにプロセスを起動する動作モデルであるため、これはCGIと同様であるとみなすことができました。

従って、既存のMojo::Server::CGIを参考に、環境変数の解析と入出力の調整だけでどうにかMojoアプリをAzure Functionsで動作させることに成功しました。

動作確認したソースコードはこちらのレポジトリに上げてあります。

苦しかった点

Mojoでは $c->render(json => {...}); とすることでJSONによるレスポンスを行うことができます。

ところが、Mojo::ServerMojo::Messageではrenderメソッドの第1引数に渡された値を一切検出できません。

そのため、レスポンスのContent-TypeやContentそのものを検証して、ContentがJSON Stringであるかどうかを検証する必要があり、そうしなければAzure FunctionsのHTTP output bindingでは単なる文字列として判別されてしまい、<string>{"message": "hello"}</string> という感じの残念なレスポンスを返してしまうことになるのです。

これを回避するコードがココでして、Contentが{で始まっていれば多分JSONだろうとみなし、evalで囲った中でdecode_jsonをかけるという非常に野蛮な方法で対応したことがお分かり頂けると思います。

実際に動作させてみた

さて、Azure Functionsにデプロイして実際に動作させてみた結果をお見せします。

Azure Functionsの大変便利な機能の一つとして、関数ごとにレスポンスにかかった時間のモニターをすることができる、その名もずばり「モニター」という機能があります。今回はそこで記録された動作速度を見ていただきたいと思います。

一番右側の丸括弧で囲われた値がレスポンスまでにかかった時間です。平均およそ23秒ほどでしょうか。これは遅いですね!

なぜ遅いか?

実は遅い理由は非常に単純でして、Mojoliciousをuseするコストがあまりにも高いことが挙げられます。これはMojoliciousが悪いわけでもありませんし、もちろんAzure Functionsが悪いわけでもありません。

どういうことかというと、そもそもAzure Functionsには非常にシンプルなものがデプロイされることが期待されています。Webアプリケーションフレームワークのような複雑なコードは毎回ロードするだけでもコストがかかりますので、当然のようにレスポンスまで時間がかかるのです。

証拠

例えば関数内のrun.shの内容が以下のように非常に単純なものだったとします。

1
perl -MJSON::PP -le 'print encode_json({message => "Hello, World!"})' > $res

その場合は、以下のようなレスポンス速度を実現することができるのです。

概ね0.5〜2秒程度でレスポンスを返すことができています。必要十分な速度と言えますね。

まとめ

Azure FunctionsでMojoアプリを動作可能にするMojo::Server::AzureFunctionsを作って、レスポンス速度を確かめてみました。

その結果わかったのは、Azure Functionsには複雑なコードをデプロイすべきではなく、徹底的にシンプルなコードをデプロイすることでその真価を発揮することができる、ということです。その先にはほぼ無限のスケーラビリティとメンテナンスフリーにほど近いシステムの実現があると私は思います。