2013年2月7日木曜日

17.Appstats(Java)でRPCのコストと処理時間を調べてみよう

このエントリーをはてなブックマークに追加




Ryo Yamasaki(@vierjp)です。

2/13に開催された appengine ja night#23 の前半のセッションで
@proppy氏によるDatastoreを中心としたAppEngineのパターンについての説明がされました。

その中でDatastoreへのアクセスやURLFetchに要した時間等、
RPCのコストや実行時間を計測するための仕組み「Appstats」について紹介されていました。

Appstatsはシステムのパフォーマンス向上や課金額を下げるために使える
プロファイリングツールです。

私は以前に少し使った事があったのですが、
認識しているより細かい情報ーを参照できる事がわかったので、改めて試してみました。

セッション中に「Java版のAppstatsはこんなに細かい情報が出なかった気がする」とtweetしましたが・・・
すみません、試したらちゃんと細かい情報まで見ることができました(汗

ごめんなさいも兼ねてドキュメント見ながら設定してブログ記事にしてみます。

参考にしたのは以下の公式ドキュメント。
Appstats for Java - Google App Engine — Google Developers


○概要

JavaSDKにはRPC(リモートプロシージャコール)のパフォーマンスプロファイリングに使用する
Appstatsが含まれています。
App EngineのRPCはアプリケーションとAppEngineのサービスAPI間のネットワーク呼び出しです。
例えば以下のAPI呼び出しのすべてがRPC呼び出しです。

・Datastore calls. (例:DatastoreService.get(), DatastoreService.put(), or DatastoreService.query() )
・Memcache calls. (例:MemcacheService.get(), or MemcacheService.getAll() )
・URL Fetch calls.
・Mail calls.

スケーラブルなアプリケーションを最適化したりデバッグしたりすることは困難な場合があり、
パフォーマンスの劣化や予想外のコストを引き起こす場合があります。

これらの問題はログやリクエスト統計のような通常の情報を使ってのデバッグは非常に困難です。
多くのアプリケーションは処理時間の大半をネットワーク呼び出しの完了を待つことに費やされます。

アプリケーションを高速に保つためには以下を知っておく必要があります。

・不要なRPC呼び出しをしていないか?
・同じデータを取得するために繰り返しRPC要求を行う代わりにキャッシュするべきではないか?
・複数のリクエストをシリアルにではなくパラレルに実行したらパフォーマンスは改善しないか?

AppstatsはRPC呼び出しをプロファイリングできるようにすることで、
これらの問いの答えを出し最も効率的な方法でRPC呼び出しをしている事を確認するのに役立ちます。

Appstatsは各RPC呼び出しの時間とコストをレポートするため、全てのRPC呼び出しをトレースすることができます。

※趣旨は合っていると思いますが、英語が得意な方は原文を参照した方がいいかも。


○設定方法


・web.xmlのFilter設定

    
    
        appstats
        com.google.appengine.tools.appstats.AppstatsFilter
        
        
            logMessage
            Appstats available: /appstats/details?time={ID}
        
        
       
          calculateRpcCosts
          true
      
    

    
    
        appstats
        /*
    
RPC呼び出しの情報を収集するため、全てのリクエストに対してFilterを設定します。
ドキュメントとは少し説明の順番が違いますが、
全ての情報を出力するための設定はこうなります。
(1)・・・Filterの定義
(2)・・・ログにAppStatsのログを出力するための指定
(3)・・・RPCのコストを計算するための指定
(4)・・・全てのリクエストにFilterを設定するための指定

注意として、Filterの定義順序によっては正しく統計情報を収集できないようです。
私は「Appstatsの<filter-mapping>」を他のFilterの「<filter-mapping>」よりも上に書いています。


・logging-propertiesの設定


(4)に関連して下記の一行を追加します。
com.google.appengine.tools.appstats.AppstatsFilter.level = INFO


・web.xmlのServlet設定

    
    
        appstats
        com.google.appengine.tools.appstats.AppstatsServlet
    

    
    
        appstats
        /appstats/*
    

    
    
        
            /appstats/*
        
        
            admin
        
    
Filterで収集した情報を参照するための「Appstats console」を表示するServletを定義します。
この場合、
http://[アプリID].appspot.com/appstats/
にアクセスすることでAppstatsの収集したレポートを参照することができます。
(5)・・・Servletの定義
(6)・・・ServletにアクセスするためのURLを指定

(7)・・・管理者権限(*)が無ければ参照できないようにするためのアクセス制御

※AppEngineの認証における「admin」はAdmin Consoleの「Permissions」に登録されたGoogleアカウントです。
Roleが「Owner」のアカウントだけでなく「Developer」や「Viewer」もアクセスできます。
(この辺の細かい制御もできるといいんですけどね)


・appengine-web.xmlの設定

    
        
    
「<appengine-web-app>」直下に記述します。

上述のURL「/appstats/」にアクセスすればレポートを参照できるので、
これは必ずしも必須ではありません。ですが設定しておくと便利です。
これはAppEngineのAdminConsoleに
「指定したURLへのリンク」を表示する機能です。
Appstatsに限らず、自前の管理画面へも同じ方法でリンクできます。


公式にはAppstatsのレポートを外部に公開するための「Public Access」の設定方法も書いてありますが、
通常は公開するものではないと思うので省略します。


○Appstatsを設定をしたJavaアプリケーション

作って配置してみました。Python版とは若干違います。
http://appstats.vier.jp/

・トップページはSlim3のテンプレートにある「EntityをPutしてクエリするController」
・トップページから「Appstats console」のURLにリンクしています。
・「Appstats console」のRequests Historyからトップページのリンクをクリック。
 (「2013-02-05 10:31:06.947 "GET /" 200 」のようなリンク)

セッションで見たのと同様のタイムラインとRPCの呼び出し回数等の情報を確認できます。

※誰でもアクセスできるように「Public Access」の設定をしてあります。


○Appstatsを設定したコード

基本的に上の説明通りですが、一応置いておきます。
eclipse-Pluginから作成したSlim3プロジェクトですので、
そのままeclipseにインポートして、eclipse-Pluginからデプロイすると良いでしょう。

https://docs.google.com/file/d/0B1_lZa0tcfbgMjM0X3NtRGhBd2M/edit?usp=sharing

※「Public Access」はできない設定にしてあります。


○おまけ

私が以前(1年以上前)にAppstatsを設定した際、
長時間実行するTaskでOutOfMemoryが頻発するようになった事があります。
Appstatsが原因だったと断言はできませんが、
AppstatsのFilterを外したら現象は収まりました。
そのため、私はそれ以来本番環境ではAppstatsを使用していません。

超適当な上に未テストですが、
以下のようにAppstatsFilterを継承したクラスを作ってそれを使うようにすれば、
割と簡単に「本番環境では使わない」とすることができるんじゃないでしょうか。
public class ExtendedAppstatsFilter extends AppstatsFilter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        String isProducationString = System.getProperty("isProduction");
        if ("true".equalsIgnoreCase(isProducationString)) {
            chain.doFilter(request, response);
        } else {
            super.doFilter(request, response, chain);
        }
    }
}
本来Filterのinitパラメーターを使った方が良いと思いますが、
サンプルコードの行数が長くなるのでそこも簡単に書いています。
この場合も
「デプロイ時にappengine-web.xmlやweb.xmlを環境に応じて自動で切り替える(書き換える)仕組み」
は欲しいところです。
mavenを使いましょう。

このエントリーをはてなブックマークに追加