Speee DEVELOPER BLOG

Speee開発陣による技術情報発信ブログです。 メディア開発・運用、スマートフォンアプリ開発、Webマーケティング、アドテクなどで培った技術ノウハウを発信していきます!

Webアプリ実行もPaaSにしてみない!?

こんにちは、ももクロ夏のバカ騒ぎ2014で終わってクリスマスの準備をしなければならない「ももクロクラスター構築屋」です。
今年の夏は初めてももクロのライブに行く同僚もいたりしてとても楽しめました。
もっと書きたいところですがエンジニアブログではなくなるのでこの辺で!

PaaSとIaaS

クラウド」という言葉が広く知れ渡ってから数年が経ち、既に身の回りにクラウドで提供されているサービスが溢れている時代になりましたが、改めておさらいしてみます。

IaaSはサーバ、ネットワークが仮想化されたものが提供され、その上に自由にシステムを構築することができるサービスです。Amazon EC2がIaaSにあたります。
PaaSは仮想化されたサーバ、ネットワークの上にミドルウェアが乗った状態で提供され、ミドルウェア上で動くアプリケーションを構築することができるサービスです。
Google App Engine(以下、GAE)やherokuといったサービスが有名です。
また、広義にはデータベースやストレージといった、専用のミドルウェアを乗せたインスタンスを提供するサービスもあります。

IaaSは、自由度の高さが最大の特徴になります。
例えば、物理サーバで動いているサービスをクラウドへ移設するといった場合でも、容易にIaaS上に同じ構成を再現することができます。
しかし自由度が高いが故に、仮に一般的な構成であったとしても、OSやミドルウェア(機能)については 自身でインストール・設定や管理をしなければならないとも言えます。

一方PaaSの場合、ミドルウェアまで提供されているため、開発者はいわゆるインフラの領域を意識することなく、アプリケーション構築のみに集中することができます。
特に、多くのPaaSサービスではオートスケールの仕組み(負荷状況に応じて自動でスケールする仕組みや、管理コンソールからワンタッチでスケールさせられる仕組み)が提供されており、トラフィックの増減が激しいサービスを効率よく構築するために、非常に強力な武器になります。
しかし逆に、必要なミドルウェア(機能)が提供されていない場合や、提供されているミドルウェアの枠を超える処理を行いたい場合はPaaSを選択できないという
デメリットがあります。

このように一長一短ある2つのサービスですが、最近では両方を提供する企業も増えてきました。

GAEでPaaSから提供を始めたGoogleは、Google Compute EngineというIaaSサービスも開始し、
GAEと合わせてGoogle Cloud PlatformというIaaS、PaaSの相互連携する形でサービスを展開しています。
Google Cloud Platform

一方Amazon EC2でIaaSから提供を始めたAmazonも、AWS Elastic BeanstalkというアプリケーションレイヤのPaaSサービスも2011年頃から開始し、
EC2や様々な用途に特化したPaaSと合わせてAWSというサービスを展開しています。
amazon web service
AWS Elastic Beanstalk

しかし、あくまで一長一短ある2者から選択する責任は開発者にあるため、両者をよく知り、適切な選択をしていきたいものです。
そこで今回は、アプリケーションレイヤのPaaSについて書きたいと思います。
アプリケーションレイヤのPaaS提供としてはGAEは先進的だったので個人的に初めて利用した思い出があり、GAEを題材にしたいと思います。
Javaを利用してGAE/J上で動くアプリケーションのサンプルコードをお見せしつつ、いくつかノウハウを紹介します。

GAE最大の敵!スピンアップを早くしよう

GAEはインスタンスの起動やスケールアウトを自動で行ってくれる機能を持っています。
このとき、インスタンスの起動からアプリケーションのロードを行い、リクエストを受けることができる状態にするまでの処理を、スピンアップと呼びます。
負荷状況に合わせて自動でスケールアウトしてくれるので非常に便利な機能ですが、一方で、スピンアップはユーザのリクエストを受けてから行われる、
つまり、ユーザはスピンアップしている間待たされるという問題があります。

特に、今回取り上げる、Javaを利用したGAE上のサービス構築は注意する必要があります。
Javaでは、クラスロードやフレームワーク/アプリケーションレベルの初期化処理を起動時にまとめて行い、
代わりにその後のスループットを上げるという思想が主流だからです。
GAEでは初期化処理がスピンアップに含まれ、ユーザの待ち時間になってしまいます。

では実際にGAE上でアプリケーションを動かして、具体的に見ていきましょう!

ネイキッドの場合(フレームワークなし)

処理自体はよく使うであろうAPIを適当に入れています。特に意味はありませんのでご了承ください。

  • ユーザ情報を取得
  • BigTableに保存
  • URLをフェッチ
  • データサイズをJsonで返却
public class TestServlet extends HttpServlet {
    private Logger logger = Logger.getLogger(TestServlet.class.getName());
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        logger.log(Level.INFO,"処理開始");

        //ユーザ情報取得
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();
        logger.log(Level.INFO,"#1");

        //Modelを生成
        UserModel userModel = new UserModel();
        if(user != null){
            userModel.setEmail(user.getEmail());
        }
        logger.log(Level.INFO,"#2");

        //メールアドレスをBigTableに保存
        DatastoreService dss = DatastoreServiceFactory.getDatastoreService();
        Key key = KeyFactory.createKey("user",1);
        Entity entity = new Entity(key);
        entity.setProperty("email", userModel.getEmail());
        dss.put(entity);
        logger.log(Level.INFO,"#3");

        //URL Fetch
        URL url = new URL("http://technica.speee.jp/");
        HTTPRequest httpReq = new HTTPRequest(url, HTTPMethod.GET );
        URLFetchService ufs = URLFetchServiceFactory.getURLFetchService();
        HTTPResponse response = ufs.fetch( httpReq );
        logger.log(Level.INFO,"#4");

        //HTMLデータサイズをJSONで返却
        JSONObject result = new JSONObject();
        try {
            if(response.getResponseCode() == 200){
                result.put("status", "success");
                result.put("size", response.getContent().length);

            }else{
                result.put("status", "error");
                result.put("code", response.getResponseCode());
            }
        } catch (JSONException e) {
            logger.log(Level.WARNING, "JSON Error", e);
        }
        PrintWriter pw = resp.getWriter();
        pw.write(result.toString());
        pw.close();
    }
}

ログ

リクエストを受けてからレスポンスが完了した時間はログのとおり、スピンアップ時に5秒ほど、2度目のアクセスからは60ms弱でした。
フレームワークを用いていないので、これが、サービスとして必要な最低限の初期化処理になっています。
つまりどうがんばってもこれ以上は早くならない数値だと思ってください。

ではフレームワークを使うとどうなるか?
ここではリクエストのルーティングのみフレームワークに任せます。

Spring3.2の例

処理自体はネイキッドと変わりないですが、コントローラーまでのルーティング処理のみをSpringに任せてみました。
Spring Framework

ライブラリはこちら

  • spring-beans-3.2.8.RELEASE.jar
  • spring-context-3.2.8.RELEASE.jar
  • spring-core-3.2.8.RELEASE.jar
  • spring-expression-3.2.8.RELEASE.jar
  • spring-web-3.2.8.RELEASE.jar
  • spring-webmvc-3.2.8.RELEASE.jar
  • commons-logging-1.1.3.jar
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

//SpringのControllerアノテーションを付与
@Controller
public class TestServlet{
    private Logger logger = Logger.getLogger(TestServlet.class.getName());

    @RequestMapping(value = "/test")
    @ResponseBody
    public void doTestt(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        // =======================================
        // ネイキッドと同じ処理
        // =======================================
    }
}

ログ

ログを見ていただいて分かるとおり、初回のリクエストではSpringの初期処理がログにでています。全体で12秒弱ですが初期処理が遅く6秒ほどかかってました。
2度目からはクラスロード・初期処理がない為、ネイキッドと全体の処理時間が変わらないことも分かります。

うーん・・・とても困りますねw

そこでSlim3を使え!となります。
Seasar2のひがさんが作成したGAE用のフレームワークです。 Slim3

クラスロードの最適化からGAEの機能をラップしたAPIが用意されている為
GAEアプリケーションを作成するなら最適なフレームワークになります。

Slim3の例

Slim3も同様でルーティングのみをry・・・
ライブラリはこちら

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;

//Slim3のControllerを継承
public class TestController extends Controller {
    private Logger logger = Logger.getLogger(TestController.class.getName());

    @Override
    protected Navigation run() throws Exception {
        // =======================================
        // ネイキッドと同じ処理
        // =======================================
    }
}

ログ

どうでしょうか? フレームワークの初期処理が行なわれたのか?と思えるほど、ネイキッドとほぼ同じ処理時間でした。
これを考えると各初期処理で大量にクラスロードされると、数百msが積み重なり数秒~数十秒の違いになってきます。

ルーティングだけフレームワークに任せるだけでも便利ですが、バリデーションやデータストア周りまで含めて利用すれば有益です。

スピンアップはいつ起きるのか?

起動が遅くなるのは問題なのは分かりました。
しかし、リリースして初回リクエストだけなら問題ないのでは・・・と思ったら間違いです。どういったときにスピンアップするかを図にしましたので見てみましょう。

PaaSの説明であったとおり、「自動でスケールする」時にもスピンアップが発生します。
アクセスの増減が激しいサービスやアクセスが少ない場合も、スピンアップを意識したつくりをしなければなりません。

スピンアップの確認

管理ツールのログからスピンアップの確認ができます。 ログにインスタンスIDが表示される為、アクセスが少ないときにIDが毎回変わるのであれば、アクセスのたびにインスタンスが立ち上がり、 アイドル状態になっていることが考えられます。

赤枠で示した部分がアクセスしたインスタンスIDです。初回アクセスでスピンアップし2度目は同じインスタンスにアクセスしてるんですね。
先で説明した通り、時間を置きアイドル時間を作ってアクセスすると違うIDになり、再度スピンアップしたことがわかります。

やっぱり癖ありますね。

アプリケーションPaaSは来るか?

スピンアップなどの問題や利用できるAPI,プログラミング言語などが制約がある中で作らないといけないという問題はあるものの、
データベースやストレージなどをPaaSに置き換えることは既に主流になってきているように、インフラに対する管理工数を削減し、サービスの開発に時間を割くことができるというのは、たくさんのプレイヤーがより良いサービスをいかに早く社会に提供できるかを競い合っている現代において、とても貴重な競争力になると言えます。
同じようにアプリケーション実行環境自体もPaaSにしてしまうメリットも非常に大きいのではないでしょうか?