2014年4月28日月曜日

【追加】最強モックツール JMockit その12 カバレッジオプション

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、私の現場ではこのブログを参考資料としてJUnit開発を進行中!!

そこで現場の皆様からリクエストが上がりましたので、ちょっと昔に巻き戻って情報を補強させて頂きます。

カバレッジオプション

JMockitにはカバレッジ出力機能が備わっている事は第2回でご紹介致しました。

JMockitのカバレッジ出力は簡単でして、jarファイルをインポートすれば勝手に出て来ます。
なので前回の記事では簡単に済ませてしまいましたが、ちょっとカスタマイズしたいという事情が出て来ましたので、オプションにてご説明しましょう。

カバレッジについて記述されている公式ページは以下になります。

機能のご紹介としては、概ね以下のような内容です。

coverage-output

カバレッジの出力モードのオプションです。
普通に実行するとHTMLファイルのカバレッジ結果が出力されますが、モードを設定することによって「シリアル」「マージ」といった機能が解禁されます。

「シリアル」「マージ」というのは、JUnitテストの分割で使用するパラメータです。

普通、JUnitのカバレッジというのは1回で全部出力しなければなりませんが、「シリアル」で実行するとcoverage.serという途中ファイルが出力されます。
これがシリアルファイルです。

複数に出力したシリアルファイルを合体させてHTML出力する機能を「マージ」と言います。

基本、JUnitは1クリックで全部出力するのが良いですが、「シングルトン」とか使ったプログラムですと、どうしても1回ではカバーしきれない場合があります。
そういう時に「シリアル」「マージ」のオプションを使うのが良いでしょう。

coverage-outputDir

カバレッジ出力先を指定するオプションです。
普通に実行すると実行パス直下に「coverage-report」というフォルダが出力されますが、違う場所に出力したい場合に使用します。

coverage-srcDirs

カバレッジ出力対象とすろソースフォルダのパスを指定するオプションです。
デフォルトでは「src」の配下を対象とするので、プロジェクト作成時に「srcフォルダ」「testフォルダ」の2つに分割するのが基本構成であるのは、第2回でご紹介したとおり。

しかし何らかの事情でsrcフォルダ配下にソース、という構成が取れない場合は、このオプションを使用して対処します。

coverage-classes

カバレッジ対象とするJavaファイルを正規表現で絞り込む機能です。
通常ですと全Javaファイルを走査してカバレッジを出力しますが、このオプションによって「特定パッケージだけ」等の対応が可能になります。

coverage-excludes

こちらはcoverage-classesの反対で、正規表現で特定Javaソースを除外する機能です。

coverage-metrics

カバレッジの網羅パターンを指定します。
デフォルトで実行すると「C0(行網羅)」でカバレッジが出力されますが、
オプションを指定することで「C1(分岐網羅)」「C2(条件網羅)」のモードに切り替えることが出来ます。

coverage-check

カバレッジ率をチェックするオプションです。

普通に実行すると単にカバレッジが出力されるだけですが、オプションを指定することにより「指定数値よりカバレッジが低い場合」に警告を出してくれるようになります。

オプション実行

如何でしたでしょう?
最強モックツールに相応しい、痒いところにまで手が届く充実したオプションシリーズですね。

しかし、基本的には「オプションを使わなくても良いようにプロジェクトを運用する」のが大事になるかと思います。
オプションを使用するということは、それだけ複雑になるということですから。。。

しかし、どうしてもオプションを指定して実行しなければならなくなることもありますので、Eclipseでオプション指定実行をする手順もこの記事にて掲載します。

実行の構成から指定する


EclipseからVM起動オプションを設定する場合、基本は以下のように実行の構成から指定することになるでしょう。


  • 右クリック⇒実行の構成⇒引数

ここから、「-Dcoverage-metrics=all」のように、頭に「-D」をつけた引数を入力すれば、それでオプション付きで実行したことになります。


しかしですね、これをやると「ファイル毎に起動構成を作らなければならない」という欠点があります。

JUnitテストなんて、色々なテストケースを何度も何度も実行するものですから、毎回毎回オプションを気にしながら実行するのは面倒極まりないと思います。

そこで、私としては「デフォルトVM引数」を指定するのをオススメします。

デフォルトVM引数指定

Eclipseには「いつも同じVM引数をつけて実行する」というデフォルト指定モードがあります。
VM引数指定が必要な実行があるなら、こちらにすることで手間を省くことをオススメしたいです。

  • ウィンドウ⇒設定⇒Java⇒インストール済みのJRE

VM引数はVM単位で付与しますので、実行時に使用するJavaVMを選び、編集をクリックして下さい。
すると、「デフォルトのVM引数」という項目があるので、ここに入れて実行すればOKです。



こうすることで、最初にデフォルト指定しておけば、いつも同じ設定で実行出来るようになりますので、テストコード開発も捗るでしょう。

終わりに

久しぶりのJUnit記事になりました。

現在、私は現場でこの記事をベースにJMockitを使ったユニットテスト開発を行っていますが、やっぱりJMockitは強力ですね。
出来ないことが無いというくらいに、何でもJMockitで貫き通せています。

次回も現場で出て来た要望への対応、「プライベート変数書き換え」をお送りします。

現場で使っているライブラリ「Spring」のDI機能をモック化する為の特殊対応です。

2014年4月8日火曜日

【GAE】スピンアップ問題4~Ajax作戦~

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

現在はGAE界最大の敵である「スピンアップ」のシリーズです。

本日はスピンアップ編の最終章、Ajax作戦についてです。

重いものは重い

さて、前回のおさらいからですが、GAEのスピンアップは重いものです。
限界までチューニングした所で数秒、現実的には7~8秒は必要とすると見込んで良いでしょう。

つまり、


  • 最初に表示した時、画面が10秒近くホワイトアウトしている。


こういう現象に陥ってしまうわけです。
今回はこの現象に対する対抗策に関する内容です。

我慢作戦

まあ、「スピンアップ」というのはインスタンスが起動する時だけ重いものですから、
最初の一回だけ我慢すれば、後は速いものですよ。

私は過去に負荷テストを行っていますが、
その時の実績によると、20~25人がF5アタックしているような状態でも1インスタンスで捌けてしまいます。

つまり、普通の人が普通にアクセスするペースだと、その10~20倍の250~500人程度は、スピンアップが最初の1回以外は発生しない見込みになります。

何が言いたいかと言いますと、

  • ホワイトアウトなんて滅多に起きないんだから我慢しろ。

というスタンスも十分アリってことですよ。

2回目にアクセスした時は速くなっているわけですから、ネット回線の一時的な不調と切り分け出来ないレベルだと思います。

しかし、我慢作戦では元も子もありませんから、真面目に技術的対処方法をご紹介します。
それは、Ajaxを使用することです。

Ajax作戦のカラクリ

GAEのスピンアップ問題から逃れることは出来ません。
そこでAjaxが活躍するのです。

  1. 先にHTMLファイルや画像などの静的コンテンツを表示する。
  2. 表示直後、OnLoadでAjax起動。くるくるダイアログを表示しつつ、非同期通信で動的コンテンツを取得。
  3. Ajaxにより動的コンテンツを表示。

この三段構えで表示することで、体感的に速く表示されるようになります。

1.静的コンテンツ表示

実はですね、GAEというのは、「Javaファイルが置かれているサーバ」と「HTMLや画像など静的ファイルが置かれているサーバ」は別々なのです。

そして、スピンアップが発生するのはJavaが置かれているサーバのみ。
HTML、CSS、JS、画像ファイルなど、静的ファイルの表示だけであればスピンアップは発生しないのです。

よって、①は常に超高速で表示されます。

これがAjax作戦の最大の秘訣です。

2.非同期通信

Ajaxの非同期通信によって、Javaにアクセスして内容を動的に表示します。
ここでスピンアップが起きるので、くるくるダイアログが10秒近く表示されていることになります。

つまり、トータルで待たされている時間は全く変わらないわけです。

ただ、①の時点で枠組み部分は表示済みになっており、処理中を意味する「くるくるダイアログ」も表示されていますので、
ホワイトアウト状態と比較すれば、待たされている人のストレスがは劇的に軽減されます。

3.描画処理

バックエンドの非同期通信でデータを取得しましたら、画面に出力します。


これにて画面が無事に表示出来ました。
これがGAE界で主流になっているAjax戦術の概要です。


Ajaxスキル必須


はい、ここでお分かりになられたかと思います。

Ajax作戦では、初期表示の際には静的ファイルであるHTMLを使用して表示します。
よって、jspは使用出来ません。
システム全体がAjaxに完全対応しなければならないのです。

上記には「我慢作戦という選択肢もある」と書きましたが、実際の所、GAE界ではこのAjax作戦が支配的なシェアを持っています。
GAE開発では、Ajax作戦が最適解であると言い切ってしまって良いでしょう。

つまり、開発者には「Javaスキル」と「Ajaxスキル」の両方が求められるわけです。

  • GAE開発は、JavaとAjaxの両方のスキルを持っていることが必須!!

プルダウンの中身を差し替えるだけみたいな粗末なAjaxではありません。
画面全体がAjax状態。
技術者はフルAjaxを実現するスキルを持っていなければなりません。


私はこの、「技術者の要求するスキルハードルの高さ」がGAE最大のネックだと思っています。
JavaとAjax、両方に精通している技術者というのは中々いません。


一般に、Javaというのは業務用アプリで多く使われる傾向にあります。
業務用アプリではAjaxは出番が少ないのです。

逆にAjaxが多く使用されるのはエンドユーザ向けシステム、例えばSNS辺りでしょうが、ああいうのはサイクルの速いスクリプト言語で書かれている事が常です。

つまり、GAE開発に必要とする「Java」と「Ajax」を両方揃えている技術者というのはレアケースということになります。


一応、「Android + HTML5のハイブリッドアプリ」の開発経験者なら期待出来る所ですが、それも技術者全体から見れば少数派でしょうし……。


「Java × Ajax」、これは意外にレアなのです。


終わりに

GAE最大の問題である「スピンアップ」について検証を重ねるうちに、
徐々にGAEの癖というものが分かってきましたね。

GAEは、普通のJavaWebシステム開発とは明らかに違う点が、確かにあります。

この為、GAE開発を行っている会社というのは、GAE専門特化型になっているケースが多いように見受けられますね。
スペシャリスト型でなければキツいのでしょう。

そんな、気軽に使うにはちょっとハードルの高いGAEですが、
次回はそれの有効活用法について検討したいと思います。

2014年3月31日月曜日

【GAE】スピンアップ問題3~ライブラリ厳選~

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

現在はGAE界最大の敵である「スピンアップ」のシリーズです。

ライブラリ厳選

さて、前回の振り返りになりますが、GAEのスピンアップが重い理由。それは「クラスロードが重い」からです。

つまり、高速スピンアップを行う為には、クラスロードを減らさなければならないわけですね。
そして、クラスロードを減らすために一番重要なことは、「クラスロードの少ないライブラリを選ぶ」ことです。

例えば、JavaのWebシステム開発で定番フレームワークで「Struts」というものがありますが、
あれはGAEを想定したライブラリではありませんので、クラスロードを減らすという発想がありません。

よって、クラスロードには非常に時間が掛かり、20秒くらい要するケースもあるとか。



20秒。



画面をクリックしたら、まず20秒は真っ白にホワイトアウトしていて、それからメイン処理が走って、ようやく画面が表示されるという。。。

壊滅的遅さですね!!
これではせっかく訪れてくれた利用者も「サイトがダウンしているんだ」と思って帰ってしまうでしょう。

よって、GAE開発にはGAE専用にチューニングされたフレームワークを使う必要があります。

それが、私のオススメする「Slim3」です。

Slim3

Slim3とは、Seasarプロジェクトで有名なひがやすお氏が開発した、GAE専用フレームワークです。

と言っても、GAE専用特化フレームワークというのは、そんなに数はありません。
実質的に、GAE界はSlim3の独壇場になっていると言ってしまって良いと思います。

細かい話は横に置いて、とりあえずSlim3を選んでおけばハズレはありません。

では、さっそくSlim3をベースにスピンアップ時間を計っていきましょう。


計測

では、スピンアップ時間を計測していきます。

比較対象として、以下3種類を計測してみました。


  • ネイキッド:フレームワーク無し。GAE導入そのままの状態
  • Slim3:Slim3フレームワークのみを導入した状態
  • Slim3 + jsp:Slim3フレームワークでjsp出力する組み合わせ

その結果がこちらです。


種類ms
ネイキッド3826.2
Slim34658.6
Slim3 + jsp5587.6

ネイキッド:3826.2

「え~ッ!?」って感じですね。

ネイキッドとは、要するに何も無い空っぽの状態ということです。
GAE公式ライブラリをプロジェクトに入れて、真っ白な画面を表示するだけ。
これで3826.2msを要します。

空っぽでも4秒近くかかるわけです。
これ以上速くは絶対になりません。

これでもう、すでに一つの結論が出ましたね。



  • GAEのスピンアップはどうやっても遅いから、遅いことを前提でシステムを作れ。



これが一番重要です。
これを受け入れなければGAEは活用出来ません。

Slim3:4658.6


さて、我らの神ライブラリ「Slim3」ですが、「ネイキッド + 0.8秒」という数字になりました。
Slim3が使っている時間は0.8秒です。

Strutsの20秒とかと比較すると、如何に高速化されているかが分かるでしょう。

やっぱりSlim3は鉄板のライブラリです。(^_^)

Slim3 + jsp:5587.6


最後に「Slim3 + jsp」です。
急にjspを持ち出してしまいましたが、これも重用です。


  • jspのスピンアップは遅い。


jspは実行時にコンパイルを行っているわけですが、そのコンパイルエンジンである「jasper」の動きがGAEでは遅いと聞きました。
計測してみたところ、「slim3 + 0.9秒」です。

なるほど。確かにjspだけで1秒近く時間を食っています。
しかし、jspを使わなければこの時間は回避出来るわけですから、GAEではjspは使わないという判断が必要になりますね。


結論

さて、上記で結論が出ましたね。



  • どう頑張った所でスピンアップで4~6秒は待たされる。



これは痛い!!
普通に開発したら場合、「画面を開いたら4~6秒もホワイトアウトしている」という結果になるということですよ。


「ならダメじゃないか!!」と思われた方もいらっしゃるかもしれませんが、いえいえ、諦めるのはまだ速いです。


上記の遅さは「Javaのスピンアップ」の時間の話です。スピンアップさえ無ければ一瞬で結果が返ってきます。

遅いのは「Javaのスピンアップ」です。

つまり、



  • 最初にHTMLファイルだけパッと一瞬で表示して、ローディングのクルクル画像を表示しつつ、裏でAjaxでデータを取って表示する。



この裏技を使えば良いのです!!

実際には裏で4~6秒も待たされているわけですが、ホワイトアウトではなくローディング画像が出ているので、利用者のストレスはぐーんと低下します。

非常にセコい作戦ですが、これがGAE界の切り札、「Ajax戦術」なのです。


次回

次回は「Ajax戦術」についてご紹介致します。




2014年2月24日月曜日

【GAE】スピンアップ問題2~クラスロード~

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

現在はGAE界最大の敵である「スピンアップ」のシリーズです。

何で重いのか?

過去の記事でもご紹介した通り、スピンアップとはインスタンスの起動のことです。
これが非常に重い為に「インスタンスの度にユーザが待たされる」という状況が発生します。
これがスピンアップ問題です。

しかし、何でスピンアップがこんなに重いのでしょう?

これは、Javaプログラマーなら簡単に察しがつくと思います。

Javaって、起動が遅いですよね。

あれですよ、あれ。
C言語とかで作ったバッチだと一瞬でレスポンスが返って来るのに、Javaだと何秒も待ったりします。
でも、一回起動してしまえば、以後の処理は高速ですよね?

そう、Javaというのは、「初回起動時に重たい処理を全部済ませて、以降の処理は高速に行う」という思想の言語なのです。

つまり、インスタンスの起動処理であるスピンアップが重い原因は、Javaの初回起動の重さと直結したものなのです。

具体的には「クラスロードが重い」、これが原因です。

クラスロードとは


クラスロードとは、Javaのクラスを呼び出すこと。。。

まあ、当たり前ですが、普通の開発ではそんなこと気にしませんよ。
普通の開発では、その処理に必要なクラスを都度、普通に呼び出してコーディングすれば良いですからね。

その為、

「クラスロード? 意味は分かるけど、だから何だよ?」

みたいな感じに、イマイチ臨場感が無い単語だと思います。
しかし、スピンアップ問題を検討する上では、クラスロードの真の意味を理解して頂かなければなりません。

さっそく、クラスロード時間計測用のテストソースを作ってみました。
これで「クラスロード時間」とやらをチェックしてみましょう。

以下、ソース。

public class SpinUpTimeController extends AbstractWebapiController {

    /** logger */
    private Logger logger = Logger.getLogger(SpinUpTimeController.class.getName());

    /* (非 Javadoc)
     * @see jp.co.net.genesis.controller.webapi.AbstractWebapiController#doResponse()
     */
    @Override
    public String doResponse() throws Exception {
        
        logger.fine("1");

        //対象ユーザを取得
        UserService userService = UserServiceFactory.getUserService();
        userService.getCurrentUser();
        
        logger.fine("2");

        //DAOを生成
        UserSettingDao userSettingDao = new UserSettingDao();
        
        logger.fine("3");

        //キーを作成
        Key key = userSettingDao.createKey("endo@genesis-net.co.jp");
        
        logger.fine("4");

        //キーから既存レコードを取得する。
        UserSetting model = userSettingDao.getOrNull(key);
        if(model == null){
            model = new UserSetting();
            model.setKey(key);
        }
        
        logger.fine("5");
        
        //全角文字要素をURIエンコードする。
        model.encode();
        
        logger.fine("6");

        Map map = new HashMap();
        map.put("responseCode", ResponseCode.SUCCESS.responseCode);
        map.put("userSetting", model);
        
        logger.fine("7");

        Gson gson = new Gson();
        String result = gson.toJson(map);
        
        logger.fine("8");

        return result;

    }
    
    /* (非 Javadoc)
     * @see jp.co.net.genesis.controller.webapi.AbstractWebapiController#checkUser()
     */
    protected boolean checkUser() throws Exception{

        logger.fine("a1");
        
        if(!PermitAccount.isAfterAttestation()){
        }
        
        logger.fine("a2");

        if(!PermitAccount.isPermitted()){
        }
        
        logger.fine("a3");

        return true;

    };
}

ソースの意味としては、適当に作った処理の隙間隙間にログを出力して時間を表示するだけです。
「元々は何をする為のソースだったの?」とかは今回は関係お気になさらず。
時間だけがポイントです。

では、コイツに初回リクエストを投げて、スピンアップさせてみたいと思います。
結果はこちら。


合計で7862msも要してしまいました。
たった1リクエストに8秒。
これは遅い……。

でも、2回目のリクエストなら29msですので、約300倍の差が出ています。
如何にスピンアップが重いかがお分かりになるかと思います。

遅い原因は上のログを見れば分かります。
しかし、このブログの読者の方が上のログに目を凝らすのも面倒で読む気がしないかと思いますので、
私にてポイントをピックアップしてみました。

着目点は、こちら。


2→3の処理で0.2秒を要しています。

その0.2秒の処理で何をやっているかと言うと、こちら。

logger.fine("2");

//DAOを生成
UserSettingDao userSettingDao = new UserSettingDao();
        
logger.fine("3");


ただインスタンスをnewして作っているだけですね。
コンストラクタで何かやっているわけではありません。

ただ、純粋にnewしてインスタンスを作るだけで、0.2秒。

1クラスロードに0.2秒を要するのです!!

よって、どこぞよりダウンロードしてきたライブラリをインポートして、
そのクラスの機能を呼び出して、その中でクラスをロードして、とやっていけば、

0.2 + 0.2 + 0.2 + 0.2 + 0.2 + 0.2 + 0.2 + 0.2 + 0.2……。

スピンアップに7秒も8秒も使ってしまうのは、こういう理屈なのです。

終わりに


というわけで、スピンアップを高速化するには「クラスロードを減らす」、これがキーとなりそうですね。

次回は、具体的に「どうやってクラスロードを減らせば良いのか?」という点について検討してみたいと思います。

2014年2月18日火曜日

【GAE】スピンアップ問題1~始めに~

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

今回はGAE界の最大の敵、スピンアップについてご説明します。

スピンアップとは

まずは過去の記事で掲載したインスタンスの起動についておさらいしましょう。


GAEは、ユーザからのリクエストをトリガーにして「インスタンスの起動」という現象が発生します。

  • ゼロインスタンスの状態から、初めてアクセスが来た時
  • アクセス殺到により追加でインスタンスを立ち上げる時

このような時に発生する「インスタンスの起動処理」、これを『スピンアップ(spin up)』と言います。

激遅

さて、なぜこのスピンアップがGAE界最大の敵かと申しますと、それはもの凄く遅いからです。

開発していて、すぐに気がつきました。


  • 最初だけ妙に遅いな……。2回目からは速いんだけど……。


その時間、十秒近く。画面がホワイトアウトしているんですよ。

元からJavaのWebエンジニアである私には、心当たりがありました。
jspコンパイルです。

  • jspを使ったWebシステムで、デプロイの際にプリコンパイルを行っていないと、初回アクセス時にコンパイルが走って初回だけ重い。

これと酷似している現象であると直感的に察しがつきました。
しかし、調べてみたらスピンアップ問題はこれ以上に厄介な問題です。

jspコンパイル問題は「初回アクセスのみ」発生しますが、スピンアップは「インスタンス起動」の度に発生します。
つまり、何度でも発生するわけですよ。

ある程度のアクセス頻度で安定しているサービスならば、いつもインスタンスが立ち上がっているので問題にはなりませんが、「利用者の少ないサービス」「多過ぎるサービス」だとインスタンスの停止/起動を繰り返しますので、


「このサイト、最初はいつも遅いね」


という印象なサービスになってしまいます。

ここで、Webサイトの表示速度と人間の体感について、データを見てみましょう。

サイトを表示する際、どれくらい待たされると人はイライラし始めるのか、というアンケートがあります。

待ち時間比率
4秒超17%
3秒36%
2秒30%
1秒12%
1秒以下5%

何と、3秒待っただけで過半数の人は「遅い!!」と感じるようです。


  • 表示に3秒以上掛かるサイトは、閲覧される前にブラウザを閉じられる。


技術者にとっては厳しい世界ですね……。

ちなみに、上記3秒ルールは普通のサイトの話です。
アマゾンみたいな大規模通販サイトだと、0.1秒の違いでも如実に売上が変化するそうですよ。

システムの速度はスピードは売上に影響するんです!!

仕事しているとクソ遅いシステムの改修とかやらされてしまうことが多々ありますが、ああいうシステムは最低ですね。

真のWebエンジニアは、刹那の一瞬に命を賭けるオリンピック選手顔負けのスピード狂でなければならないのです。

終わりに

このように「スピンアップにより、最初の表示に10秒」とかでは話にならないわけです。
このスピンアップ時間を如何に短縮するか、これがGAEを活用する上での最初の大問題となるわけです。

今回の「スピンアップ問題シリーズ」では、このスピンアップについて調査し、掘り下げていこうと思います。
次回は、まずは現状調査。


  • なぜ、スピンアップはこんなに時間が掛かるのか。
  • 具体的に何秒くらい要しているのか。


この辺りから調査を始めていきます。

2014年2月4日火曜日

【GAE】インスタンス時間3

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

さて、前回の記事で、GAEの料金の中核は「フロントエンド・インスタンス時間」であることをご紹介しました。
Google本家の情報によると、一日200万プレビューくらいまでは無料で行けるそうです。

はてさて、本当にそんなレスポンスが出るのか、今回はちょっとDOSアタックを仕掛けて検証してみましょう。

負荷要件

さて、Googleの言う200万プレビューというのは、「効率が良いロジックである場合」の話です。
一回のリクエストが重ければ200万プレビュー無料は達成出来るわけ無いです。

この為、今回の負荷テストでは「標準的な効率的ロジック」に対し、多数のリクエストを投げる要件で検証してみようと思います。

具体的には、「テーブルの主キー検索1件」です。
一回のリクエストでテーブルの主キーを検索し、1レコードを取ってくるという処理は、
DBを使ったものの中でも最も標準的なものですからね。

この要件に対し、大量アクセスをかけて検証してみましょう。

1リクエスト速度

この負荷要件の場合、「1回のリクエストは何秒なのか?」が重用になります。

GAEだと、1リクエストの時間は標準でログに出力されておりまして、簡単に正確な時間が分かります。
それがこれ。



  • ms=26

何と、1リクエストで26msです!!

これは速いですね。
ちなみに、GAEの場合、どれだけデータ量が増えても主キー検索の速度は変わりませんので、安定してこの速度が出せます。

では、この要件で負荷テストを実施します。

アタックバッチ

DOSアタックには、私が自前で作ったマルチスレッドのリクエスト送信バッチを使います。
指定本数だけリクエスト送信スレッドを立てて、無限ループでリクエストを送りまくるという単純なバッチです。

1スレッドの時は、同時に1ユーザがアクセス中。
10スレッドの時は、同時に10ユーザがアクセス中。

と見なすわけです。

お見せする程の価値のあるソースではありませんが、一応以下に張っておきます。

public class DosAtackBatch extends AbstractBatch {

   /** logger */
   private Logger logger = Logger.getLogger(DosAtackBatch.class);

   /** 送信先URL */
   private String URL = "http://genesis-gae-service.appspot.com/webapi/develop/SpinUpTime";

   private int threadCount = 1;

   private int attackTime = 30000;

   private static AtomicInteger count = new AtomicInteger();

   /**
    * @param args
    * @throws Exception
    */
   public static void main(String[] args) throws Exception {

      DosAtackBatch batch = new DosAtackBatch();
      batch.excute(args);

   }

   /*
    * (非 Javadoc)
    *
    * @see jp.co.tacy.batch.AbstractBatch#doExcute(java.lang.String[])
    */
   @Override
   public void doExcute(String[] args) throws Exception {

      final Date dateBefore = new Date();

      for(int i=0;i<threadCount;i++){

      Thread thread = new Thread() {
            /*
             * (非 Javadoc)
             *
             * @see java.lang.Thread#run()
             */
            @Override
            public void run() {

               while (true) {
                  RequestSender sender = new RequestSender();
                  try {
                     sender.sendPost(URL, null);
                     Date dateAfter = new Date();

                     long responseTime = dateAfter.getTime() - dateBefore.getTime();

                     logger.info("アタック回数:" + DosAtackBatch.count.incrementAndGet());
                     logger.info("レスポンス時間:" + responseTime);

                     if(responseTime > attackTime){
                        break;
                     }

                  } catch (IOException e) {
                     logger.fatal("エラーが発生しました。", e);
                  }
               }
            }
         };

         thread.start();
      }

   }

}

計測:1スレッド

では、このバッチを使ってアタックをかけてみます。

  • スレッド数:1
  • 実行時間:30秒
  • リクエスト総数:108回

結果はこの通り。



1インスタンスしか上がっていません。
まあ、1スレッド=1ユーザという想定ですから、1ユーザしかアクセスしていないのであれば、1インスタンスで足りるのは当然ですね。

計測:10スレッド

次は、一気に10ユーザ行ってみましょう。

  • スレッド数:10
  • 実行時間:30秒
  • リクエスト総数:1024回

どうやら私の作ったバッチは10スレッドを立ち上げても、1スレッド辺りのパワーは落ちないようです。
それなりに良いPCを使わせて頂いております。(^_^)

さて、肝心の結果ですが、何とこれでもインスタンス数は1コでした。
  • 10ユーザがF5アタックしているような状態でもビクともしない!!

計測:20~25スレッド


「性能が凄いのは分かったから、いつになったら変化するんだよ?」

という感じでしょうから結論を述べますと、25スレッドで変化が出ました。

  • スレッド数:25
  • 実行時間:30秒
  • リクエスト総数:2104回

25スレッドで実行すると、インスタンスが2つ立ち上がります。



流石に25スレッドともなるとPCの性能が追いつかなくなってきて、1スレッド辺りのパワーが下がってきているようです。

なので、多少数値が曖昧ですが、概ねの性能が出て来ました。

  • GAEは、20~25人のユーザがF5アタックしているような状態まで、1インスタンスで対処することが出来る。

キリの良い20スレッドで再計測しましたら、以下の実績で1インスタンスでした。

  • スレッド数:20
  • 実行時間:30秒
  • リクエスト総数:1564回

というわけで、この20スレッドの数字が1インスタンスの上限値と位置づけて、算出してみようと思います。


  • 30秒で1564リクエスト
  • 1分なら1564×2=3128リクエスト
  • 1時間なら3128×60=187680リクエスト
  • 1日なら187680×24=4504320リクエスト
  • 1ヶ月なら4504320×30=135129600リクエスト

1日で450万、1ヶ月で1億3000万リクエストまで無料!!

という概算見積もりとなりました。


まとめ

さて、私の計測結果だと、Googleの主張する1日200万プレビューの2倍以上の実績を叩き出してしまいました。

まあ、私がテスト用に作った機能は超軽量ですから、現実的にはもうちょっと重くなるのが普通でしょう。
となると、1日200万プレビュー無料という謳い文句は、妥当な数字であると言うことが出来るかと思います。

結論としましては、

  • GAEは1日200万プレビューまで無料まで実現可能

をキャッチフレーズにしても問題は無いのではないかと。

ただし、この数字はザクッとした見積もりですので、現実にプロジェクトとして導入するなら、もうちょっと細かく計算したいです。

私の計測の場合、1リクエストの処理時間が26msで1日450万という結果でした。ここから比例計算で、

  • 1リクエストの平均が26msの場合:450万
  • 1リクエストの平均が58msの場合:200万
  • 1リクエストの平均が100msの場合:116万
  • 1リクエストの平均が1000msの場合:12万

みたいな感じに算出することが出来ます。
各自、自分の作ったアプリの1リクエストの時間を見て、そこから上の比例計算で割り出してみると良いかと思います。

終わりに

今回の解析で、GAEのコスト効率が概ね見えてきましたね。

ちなみに現在、私はこのブログと平行して社内用を想定したアプリを作っているのですが、一番重い処理でも1リクエストの時間は200msです。

ここから考えて、

  • GAEに最適化した要件であれば、1日200万アクセスまで無料
  • 多少のカスタマイズを入れた業務用アプリなら、平均して1日50万アクセスくらいまで無料

これくらいが現実的な見積もりなんじゃないかと思っています。

それ以上になると有料になりますが、その場合は1時間当たり0.08ドルですから、
業務用としてWebサイトを運用する場合、

  • 1日50万アクセスくらいまで無料。
  • 1日100万アクセスなら毎月5000~6000円くらい。
  • 1万円出してくれれば1日120~130万アクセスくらい行けそう。

という予算感覚です。

こりゃ安いですね。
ビジネスモデルとしての展望が見えてきそうな感じです。

この先もまだまだ気合い入れて勉強していきたいと思います。

2014年1月29日水曜日

【GAE】インスタンス時間2

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

今回も前回に続いて、「インスタンス時間」について理解を含めていきたいと思います。

おさらい

まずは今までのおさらいから。

GAEは「従量課金制」ですので、消費するほどにお金が多く発生します。

①アクセスが増える。
↓
②インスタンスが立ち上がる。
↓
③インスタンス時間を消費する。

この3段ロジックです。

例えば、アクセス者が1人だった場合、インスタンスは1コしか立ち上がりません。

最初にリクエストが来て、この時にインスタンスが立ち上がると当時に、一定時間かけて処理を行います。
処理が終了した後、その瞬間にインスタンスが即死するわけではなくて、しばらくは「アイドル状態」で待機します。
以下みたいな状態のイメージです。
赤色の部分が処理中、白色の部分がアイドル中のイメージです。


アクセス数が少ないと「インスタンスはアイドル状態で立ち上がっている」という状況になりますので、アイドル時間中はインスタンスを無駄遣いしていることになりますね。
出来ればこの無駄は省きたい所ですが「アイドル状態で待ち構えているから、リクエストが来た時に高速に反応できる」という側面もあるのです。多少の無駄遣いは仕方がありません。
インスタンスが死んでいる時にアクセスが来ると、「スピンアップ」という重いインスタンス起動処理が走ります。アイドル状態からの起動と、死んでいる状態からの起動ではスピードが全然違います。
(「スピンアップ問題」はGAEの数ある制限の中でも頭を悩ませる超重要問題なのですが、それについては今後に記事にしたいと思います。)

とにかく、アクセスが同時に1人だった場合は、こんなイメージです。

次に、これが2人になると、以下のようになります。


ご覧のように、少し待って「1インスタンスで2人捌く」という結果になります。

「1人なら1インスタンス。2人なら2インスタンス。100人なら100インスタンスだ!!」

なんて効率の悪いことにはなりません。
少ない人数でしたら1インスタンスで捌いてくれます。

ただし、待ちが発生しますけどね。

同時にアクセスが来たら、片方にはもう一方の処理が終わるまで待って貰います。
その待ち時間を「Pending Latency」と言います。

  • 待ち時間が短いうちは1インスタンスで捌く。
  • 「待ち時間長過ぎ!! 遅えぇぇぇぇぇッ!!」となったら新規インスタンス起動

こういう関係なのですよ。
スーパーのレジに人が溜まってきたら、新しい人が別のレジに入って捌くのと似たようなものです。

アクセス殺到時のイメージが以下の図です。


最初のうちは1インスタンスで頑張ってくれていますが、アクセスが殺到したら、アクセス者を待たせないようにもう1コインスタンスを起動して、そっちにもアクセスを振り分けるという意味です。

まとめますと、

  • 1リクエストの処理速度
  • リクエスト数

GAEのインスタンス時間は、この2つの要素の直撃を受けます。
リクエスト数を減らすことは出来ませんので、「1リクエストの処理速度」を出来る限り高速化すること。
この辺りがGAEのコツです。

「動けば良い」などというゴミロジックは、GAEでは厳禁!!
インスタンス消費時間という形で、明白にお金という形で報復を受けます。
高速ロジックこそがGAEの正義です。

ロジックを組むプログラマーの腕前が会社の経営を左右するかもしれないのです。

塩梅をつける

と言っても、実は「設定」でこのインスタンス消費時間を下げることも可能なのです。

上記に「アイドル状態」「待ち時間」という二つのキーワードが登場していますが、これらを手動で塩梅をつけることでインスタンス消費時間を下げることが出来ます。

アイドルインスタンス調整

アイドルインスタンスとは「次のアクセスに備えて待機しているインスタンス」のことです。アイドル状態で多数のインスタンスを控えさせておけば、急にアクセスが来た時でもすぐに対処出来ます。
と言っても、アクセスが来ていない場合は単なる穀潰しです。
「多少時間がかかっても、アクセスが来てから新規インスタンスを立ち上げれば十分」というの論理がOKであれば、「最大アイドルインスタンス数を下げる」という設定を行うことで、穀潰しを抹殺することが出来ます。
但し、その分だけ「スピンアップ」というインスタンス起動処理の発生頻度が多くなりますのでご注意を。

待ち時間調整

上記には「アクセス者の待ち時間が長くなったらインスタンス起動」とありますが、「それって具体的にどれくらい待ち時間が長引いたらインスタンスが起動するの?」という話です。
0.1秒待っただけで新規インスタンスを起動するようでは、そりゃインスタンスは些細なことでポンポン立ち上がってコストを大量消費してしまいます。
「5秒や10秒くらい大人しく待ってろ」というの論理がOKであれば、「許容待ち時間を長くする」という設定を行うことで、過剰なインスタンス起動を回避することが出来ます。



これらの設定を行う個所が、アドミンコンソールの「Application Settings」です。



ここに「Max Idle Instances(最大アイドルインスタンス数)」と「Min Pending Latency(最少待ち時間)」がありますね。
デフォルトではこれは「Auto」になっていますが、「Auto」というのは「最高パフォーマンス設定」のことで、この状態が一番速い代わりに高価です。Autoは本当に高速で、0.1秒待っただけでも次が上がってしまうような超高速モードらしいです。

これを最低設定の「Max Idle Instances:1」「Min Pending Latency:15s」まで手動で下げてしまえば、パフォーマンスが低下する代わりにインスタンスの起動が抑制され、結果コストが安く出来るのです。

「待ち時間15秒とか遅過ぎだろ!! でも3秒くらいなら許せるかな」みたいに、そのサービスの保証水準を鑑みて、各自調整するのが良いでしょう。

終わりに

以上が、インスタンス時間に関連する基礎知識でした。

しかし、これはあくまで「理論上の話」です。
次回は、いっちょ、自前でDoS攻撃を仕掛けて負荷テストを決行してみたいと思います。

一体、どれくらいのアクセスを捌くことが出来るのでしょうね。。。