Google App Engine をベースに Eclipse を使用して作成するマッシュアップ: 第 2 回 Ajax マッシュアップを構築する

ソーシャル・ネットワークでは、画期的な新しい Web アプリケーションを作成するためのデータを容易に収集してマッシュアップできるようになっていますが、それでも、スケーラブルな Web アプリケーションを作成する上で常に伴う問題のすべては自分で処理しなければなりません。この部分の負担を軽くしてくれるのが、GAE (Google App Engine) です。GAE を使用すれば、アプリケーション・サーバーのプールの管理について一切忘れ、優れたマッシュアップを作成することに専念できます。3 回連載の「Google App Engine をベースに Eclipse を使用して作成するマッシュアップ」の第 2 回目では、第 1 回で作成したアプリケーションを拡張します。まず、アプリケーションのパフォーマンスを改善するために GAE のデータ・モデリング機能を追加します。次に GAE の Memcache サービスを利用して、さらにパフォーマンスを改良していきます。

Michael Galpin, Software architect, eBay

Galpin Michael photoMichael Galpin は、1998年以来、プロとして Java ソフトウェアを開発してきています。彼は現在 eBay に勤務しています。彼は California Institute of Technology で数学の学位を取得しています。



2008年 8月 12日

この連載について

この連載では、GAE (Google App Engine) を使い始める方法を紹介します。第 1 回で焦点としたのは、GAE で実行するアプリケーションの作成を開始できるように開発環境をセットアップすることでした。さらに、Eclipse を利用してアプリケーションの開発とデバッグを容易にする方法も説明しました。この第 2 回では、Eclipse を使って Ajax マッシュアップを構築し、そのマッシュアップを GAE にデプロイします。そして最終回の第 3 回目で、アプリケーションにRESTful なサービスを作成することによってエコシステムに還元し、他の開発者もこのアプリケーションを使ってそれぞれ独自のマッシュアップを作成できるようにします。

GAE は、Web アプリケーションを作成するためのプラットフォームです。GAE で使用するのは Python (現在、Python V2.5.2) であることから、GAE を使うにはこのプログラミング言語についての知識があることが最大の前提条件となります。この連載を読むにあたっては、ある程度の一般的な Web 開発スキル (HTML、JavaScript、CSS の知識) があると役立ちます。GAE 対応の開発を行うには、以下の 3 つのソフトウェア・パッケージをダウンロードする必要があります。

Eclipse Classic
この連載では Eclipse Classic V3.3.2 を使用しました。これ以降のバージョンでも有効です。
Google App Engine SDK
GAE サイトに、公式マニュアルと SDK のダウンロード・リンクが用意されています。
PyDev
Eclipse を Python IDE に変身させる PyDev は、Eclipse 内の更新サイト (http://pydev.sourceforge.net/updates/) を指定してインストールすることができます。

最後の 2 つのソフトウェア・パッケージをインストールする方法については、第 1 回で説明しました。Eclipse を使用するのが初めての方は、まずは「参考文献」を参照してください。


機能の強化

第 1 回では、GAE を使用してコンテンツ・フィードを集約して提供する、小さなアプリケーションを作成しました。このアプリケーションはすぐにでも GAE にデプロイできる状態になっていますが、まずはその前に、いくつかの機能強化をすることにします。最初に行うのは、パフォーマンスに関する強化です。第 1 回で作成したアプリケーションは、ページが要求されるたびに、購読しているサービスからデータをプルします。しかし、応答時間の遅いサービスが 1 つでも購読しているサービスのなかに含まれていたり、ユーザーが数多くのサービスを購読したりしている場合などには、このような方法では時間がかかる場合があります。速度は基本的な問題ですが、GAE で実行しているアプリケーションにとっては特に大きな問題です。GAE をスケーラブルにするためには、長時間実行されるリクエストにかかりきりになるわけにはいきません。リクエストの処理にあまりにも時間がかかっていると、そのリクエストはアボートされ、エラー・メッセージがユーザーに送られることになります。そのような事態は避けたいので、これから GAE のデータ・モデリング機能と Bigtable 機能を大いに活用してパフォーマンスを改善します。Bigtable は、構造化データを管理するための分散ストレージ・システムです (詳しくは「参考文献」を参照)。さらにこのシステムの Memcache API も使用して、さらに改善を図ります。

この記事では、ユーザー・エクスペリエンスに関する機能強化も行います。Ajax 要素をアプリケーションに追加することによってユーザー・インターフェースを改善しますが、これは Ajax のためだけの Ajax 化ではありません。データ・モデリングおよびキャッシュの機能強化も結びつけて、アプリケーションのパフォーマンスを一層改善するという目的があります。アプリケーションを GAE にデプロイする作業は、以上の機能強化をすべて行った後に取り掛かります。まずは、データ・モデリングの強化から始めることにしましょう。

関係を使用する

第 1 回で使用したデータ・モデルは唯一、Account だけでした。このデータ・モデルは GAE の Expando プロパティー機能を使ってサービスの URL を保存します。パフォーマンスを改善するには、フィードの実際のデータを保存する必要があります。Bigtable へのアクセスは、従来のリレーショナル・データベース (少なくとも、軽い負荷の下でのリレーショナル・データベース) にアクセスするほど短時間で行えませんが、ソースからフィードをプルするよりは時間が掛からないはずです。ただし、Bigtable だけに依存するのでは、新しいデータは取得することはできません。そこで、いつライブ・データをプルして Bigtable に挿入したのかを追跡し、データがあまりにも古くなっている場合には、ソースに戻れるようにします。

新しいデータ・モデルを作成する前に、もう 1 つ考慮しなければならないことがあります。それは、複数のユーザーが同じフィードを持つ可能性があることです。実際には、フィードとアカウントの間には多対多の関係があります。この点を念頭に、新しいモデルを検討していきましょう。リスト 1 に、変更後の Account モデルを記載します。

リスト 1. Account モデル
class Account(db.Model):
    user = db.UserProperty(required=True)

上記のモデルでの大きな変更点は、サービス情報がモデルから削除されていることです。それでは、どうやってサービスの URL を判断するのでしょうか。この情報は以下のように、別のモジュール・レベルのデータ構造 (ディクショナリー) に移されています。

リスト 2. サービス・データ
service_templates = {
    'twitter': "http://twitter.com/statuses/user_timeline/%s.rss",
    'del.icio.us': "http://del.icio.us/rss/%s",
    'last.fm': "http://ws.audioscrobbler.com/1.0/user/%s/recenttracks.rss",
    'YouTube': "http://www.youtube.com/rss/user/%s/videos.rss",
    }

このようにすると、単純なストリング置換を使って、ユーザー名をベースにサービス URL を作成することができます。つまり、サービス名 (service_templates ディクショナリーへのキーとして使用) とユーザー名 (ディクショナリーから取得した値でストリング置換を行うために使用) の組み合わせによって、URL を計算できるということです。したがって、Feed データ・モデルは以下のようになります。

リスト 3. Feed モデル
class Feed(db.Model):
    service = db.StringProperty(required=True)
    username = db.StringProperty(required=True)
    content = db.TextProperty()
    timestamp = db.DateTimeProperty(auto_now=True)

service と username は上記で説明したとおりで、service プロパティーは service_templates ディクショナリーへのキーとしての役割を持ち、username はディクショナリーの値とともに URL を計算するために使用されます。content プロパティーは、Web サービスから取り込む実際のコンテンツです。timestamp はコンテンツを取り込んだ日時で、ここに指定された auto_now=True が Bigtable に対し、レコードを更新するたびにプロパティーを更新するように指示します。次に必要となるのは、Account と Feed との間の多対多の関係を定義する結合テーブルです。このテーブルを以下に記載します。

リスト 4. AccountFeed モデル
class AccountFeed(db.Model):
    account = db.ReferenceProperty(Account, required=True, collection_name='feeds')
    feed = db.ReferenceProperty(Feed, required=True, collection_name='accounts')

ReferenceProperty は、Bigtable 内でモデル同士を関連付ける方法を指定します。これはリレーショナル・データベースでの外部キーのようなものです。collection_name 属性が使用されていることにお気付きでしょうか。この名前は、クエリーで参照を使用する必要がある場合に、その参照を指定する際に使用されます。この属性を設定しなければ、モデル名の最後に _set を追加した名前 (例えば、account_set)) に設定されます。

これで、データ・モデリングは完了です。フィードのモデルを作成し、これらのモデルを多対多の関係でアカウントに関連付けました。このように、Bigtable と GAE の API によってエンティティーは簡単にモデル化できますが、バージョンについてはどうでしょう。データ・モデルのバージョンは変更されています。次は、GAE ではどのようにバージョンに対処するのかを説明します。

開発中にスキーマを変更する

多くの場合、スキーマを進化させるのは厄介なことですが、幸いにも私たちはまだ開発段階にいるので、実動アプリケーションで変更するよりは遥かに容易です。開発中にスキーマを変更することはよくあることです。GAE でスキーマを変更するのは簡単な作業で、GAE のローカル Web サーバーにパラメーターを追加すればよいだけの話です (図 1 を参照)。

図 1. ローカル・データ・ストアをクリアするパラメーターの追加
ローカル・データ・ストアをクリアするパラメーターの追加

上記では、起動スクリプトに渡されるコマンドラインの引数として --clear-datastore パラメーターを追加しているだけです。Eclipse と PyDev では、このようなパラメーターを必要に応じて簡単に追加できるようになっています。少々注意しなければならない点は、Eclipse はこれらの引数を記憶するということです。そのため引数をこのままにしておくと、開発サーバーを起動するたびにローカル・データ・ストアが削除されることになります。問題にはなりませんが、この点については認識しておいてください。

新しいスキーマによって、Bigtable を使用してフィードを保存できるようになりました。ただし、Bigtable のデータをルックアップするのは容易なことではありません。多くの開発者にとっては、リレーショナル・データベースからのルックアップよりも慣れるまでに時間がかかりますが、幸い GAE にはデータに素早くアクセスするための API が追加で用意されています。それが、Memcache です。

Memcache

GAE にはメモリー内キャッシュ、Memcache が組み込まれています。発想はオープンソースの分散キャッシュ、memcached から得ていますが、この実装は GAE に特化されています。セマンティクスは同様で、単に Memcache に対して名前と値のペアを入力または取得するだけです。この Memcache を使用することで、アプリケーションのパフォーマンスを劇的に改善することができます。

aggroGator アプリケーションでは、2 つのデータをキャッシュします。キャッシュするデータとして最も明らかなのは、ユーザーのサービスです。ユーザーのサービスは AddService アクションのなかでしか変更されないため、キャッシュが正確であることを確実にするのは簡単です。以下の Cache クラスに、これに該当するコードが示されています。

リスト 5. ユーザー・サービスのキャッシュ
class Cache:
    @staticmethod
    def setUserServices(account):
        userServices = [{'service': accountFeed.feed.service, 'username': 
            accountFeed.feed.username} 
        for accountFeed in account.feeds]
        if not memcache.set(account.user.email(), 
                pickle.dumps(userServices)):
            logging.error('Cache set failed: userServices')
        return userServices
    @classmethod
    def getUserServices(cls, user):
        userServices_pickled = memcache.get(user.email())
        if userServices_pickled:
            userServices = pickle.loads(userServices_pickled)
        else:
            account = DB.getAccount(user)
            userServices = cls.setUserServices(account)
        return userServices

このコードを簡単に説明すると、まず始めに静的メソッド (クラスとは独立したメソッド) でユーザーのサービスをキャッシュ内に設定します。リストを包括して作成されるオブジェクトの配列では、各オブジェクトがサービスとそのサービスのユーザーのユーザー名で構成されます。すると、ユーザーの E メールが Memcache のキーとして使用されます。データをシリアライズして Memcache に入力するために使用しているのは、pickle モジュールです。

getUserServices メソッドも同様です。これはクラス・メソッドで静的ですが、キャッシュ・ミスがあった場合には setUserServices メソッドを呼び出させるようになっていなければなりません。このメソッドは上記で説明したシリアライズされたオブジェクトの取得を試みて、キャッシュ内に何も見つからなければ Bigtable のデータをルックアップして、そのデータをキャッシュに入力します。

フィード内にエントリーをキャッシュする場合にも、同じような方法を使用します。しかし、この場合には 1 つの大きな違いがあります。それは、エントリーが古くなっていないか注意しなければならないという点です。結局のところ、ユーザーはいつでも新しいエントリーを作成できるため、いずれはソースに戻らなければなりません。そこで必要となるのが、以下に示す有効期限のポリシーです。

リスト 6. エントリーのキャッシュ
class Cache:
    #user service methods omitted
    @staticmethod 
    def setEntries(feed):
        entries = GenericFeed.entries(feed)
        if not memcache.set("%s_%s" % (feed.service, feed.username), 
                pickle.dumps(entries), CACHE_FEED_TIME):
            logging.error('Cache set failed: entries')
        return entries
    @classmethod
    def getEntries(cls, service, username):
        entries_pickled = memcache.get("%s_%s" % (service, username))
        if entries_pickled:
            entries = pickle.loads(entries_pickled)
        else:
            feed = DB.getFeed(service, username)
            entries = cls.setEntries(feed)
        return entries

ここでも同じく同様のパターンを使用しています。つまりデータをシリアライズして Memcache に保存するというパターンですが、この場合に使用するのはサービスとユーザー名の組み合わせです。この組み合わせを使用することにより、さまざまなユーザーを対象としたキャッシュを遥かに効率的に行うことができます。キャッシュからロードしようとするときに、キャッシュ・ミスがあった場合には Bigtable にアクセスします。また、CACHE_FEED_TIME という有効期限の値を使用してキャッシュのデータに有効期限を設定していることにも注目してください。この値を設定しなければ、Memcache はキャッシュ内のすべてのものをメモリーが無くなるまで保持します。ユーザー・サービスとエントリーについては、DB クラスを使用して Bigtable にクエリーを実行します。このクラスは、以下のとおりです。

リスト 7. DB アクセス・クラス
class DB:
    @staticmethod
    def getAccount(user):
        return Account.gql("WHERE user = :1", user).get()

    @staticmethod
    def getFeed(service, username):
        return Feed.gql("WHERE service = :1 AND username = :2", service, username).get()

上記のクラスは、GAE の GQL 構文を用いた極めて単純なクエリーを使用します。この構文は簡潔なものの、SQL 構文の強力なサブセットです。上記の例で使用したのは番号付きパラメーターですが、複雑なクエリーには名前付きパラメーターを使うこともできます。Bigtable のフィードに対してクエリーを実行することによって、Bigtable をまさに別のキャッシュ層として使用しています。次のセクションでは、Ajax によって、クライアントからこのハイパフォーマンス・キャッシュの操作を行う方法を説明します。


Ajax

大抵の人は Ajax について考えるとき、Ajax によってどのようにユーザー・エクスペリエンスを改善できるかを考えるものです。もちろん Ajax によってユーザー・エクスペリエンスを改善できますが、Ajax には他にも多くの利点があります。なかでもアーキテクチャーにはさまざまな利点をもたらします。Ajax ではアプリケーション・ロジックの大部分をクライアントに移し、サーバーから取得するデータの量を減らすことができるからです。このことは GAE で実行する場合でもまったく変わりませんが、GAE ではサンプル・アプリケーションのスケーラビリティーを生かすことができます。ここからは、aggroGator アプリケーションに Ajax を組み込む方法を説明します。

初期ページ・ビュー

aggroGator の前のバージョンでは、ページがロードされると各種サービスのエントリーの一覧が表示されるようにしていました。そのために使用したのは Django スタイルのテンプレートです。このページを一層動的にするには、データとプレゼンテーション・ロジックを分離させなければなりません。データとプレゼンテーション・ロジックを分離することでどのような効果があるのかは、テンプレートの変更内容を見れば理解できます。

リスト 8. メイン・ページのテンプレート
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Aggrogator</title>
    <link rel="stylesheet" href="/css/aggrogator.css" type="text/css" />
    <script type="text/javascript" src="/js/prototype.js"></script>
    <script type="text/javascript" src="/js/effects.js"></script>
    <script type="text/javascript" src="/js/builder.js"></script>
    <script type="text/javascript" src="/js/aggrogator.js"></script>
  </head>
  <body onload="initialize();">

    <ul id="cache"></ul>

    <img id="spinner" alt="spinner" src="/gfx/spinner.gif" 
                  style="display: none; float: left;" />

    <p id="logout">
      {{ user.nickname }}
      <a href="{{ logout_url }}">Logout</a>
    </p>

    <div class="clearboth"></div>

    <form id="form_addService" onsubmit="addService(); return false;">
      <fieldset>
      <legend>Add New Service</legend>
      <label for="service">Service: </label>
      <select name="service" id="service">
        <option>twitter</option>
        <option>del.icio.us</option>
        <option>last.fm</option>
        <option>YouTube</option>
      </select><br/>
      <label for="username">Username: </label>
      <input type="text" name="username" id="username" />
      <input type="submit" value="Add" />
      </fieldset>
    </form>

    <table>
      <tbody style="vertical-align: top;">
        <tr>
          <td>
            <div id="userServices"><span /></div>
            <div id="entries"><span /></div>
	  <td>
            <table><tbody id="allEntries"></tbody></table>
          </td>
        </tr>
      </tbody>
    </table>

  </body>
</html>

このテンプレートには重要な変更点が 2 つあります。1 つ目の点として、このテンプレートにはユーザー名とログイン/ログアウト・リンクを除き、動的なものがすべて取り去られています。2 つ目の点は、大量の JavaScript が盛り込まれていることです。ここでは Prototype と script.aculo.us の 2 つの JavaScript ライブラリー (詳細については「参考文献」を参照) を使用し、さらにはカスタム JavaScript ライブラリー、aggrogator.js も組み込んでいます。ページがロードされると、このテンプレートの initialize() メソッドが呼び出されます (以下を参照)。

リスト 9. ページの初期化
function initialize() {
  getUserServices();
  new PeriodicalExecuter(getUserServices, 300);
}

function getUserServices() {
  var handler = function(xhr) {
    var json = xhr.responseJSON;
    if (json.error) {
      // display the error
    }
    else {
      cacheStats(json.stats);

      userServicesTable(json.userServices);
      updateEntries(json.userServices);
    }
  };

  // create options for request
  var options = {
    method: "get",
    onSuccess: handler
  };

  // send the request
  new Ajax.Request("/getUserServices", options);
}

見ての通り、この初期化コードは単に別の関数、getUserServices を呼び出し、ポーリング・プロセスを開始するだけです。ポーリング・プロセスは、Prototype の PeriodicalExecutor クラスを使って getUserServices を定期的に呼び出します。この例の場合、getUserServices を呼び出す間隔は 300 秒 (5 分) です。ポーリングはサーバーからデータが常にプッシュされているような錯覚 (Comet、またはリバース Ajax としても知られます) をユーザーに与えます。例えば、Twitter に対する新しい投稿はすぐに aggroGator にプッシュされるというような錯覚です。

getUserServices クラスにはさらにたくさんの興味深い機能があります。このクラスは、Ajax リクエストを行って現行ユーザーが購読しているサービスをロードし、ロードしたサービスを一覧にした表を構成します (以下を参照)。

リスト 10. ユーザー・サービスを一覧にした表の作成
function userServicesTable(json) {
  var table = Builder.node('table',
    Builder.node('tbody',
      function() {
        var l = [];
        json.each(function(s) {
          l.push(Builder.node('tr', [
            Builder.node('td',
              Builder.node('a', {href: "", 
            	  onclick: "getEntries('" + s.service + "', '" + 
            	  	s.username + "'); return false;"}, 
            	  	s.service + ':' + s.username)
            )
          ]));
        });
        return l;
      }()
    )
  );

  $('userServices').replaceChild(table, $('userServices').firstChild);
}

上記の関数がユーザーのすべてのサービスを表示するための HTML による表を作成する上で大いに活用しているのは、script.aculo.us の Builder ライブラリーです。詳細に入る前に、このサービスで使用しているデータについて説明しておきます。リスト 9 で説明したように、初期化コードでは GetUserServices に対して要求をおこなっているので、アプリケーションの main メソッドには以下の構成が必要になります。

リスト 11. ルーティング・ルールの設定
def main():
    app = webapp.WSGIApplication([
        ('/', MainPage),
        ('/addService', AddService),
        ('/getEntries', GetEntries),
        ('/getUserServices', GetUserServices),
        ], debug=True)
    util.run_wsgi_app(app)

ご覧のように、/getUserServices URL がマッピングされているのは GetUserServices という新しいクラスです。以下に、このクラスを記載します。

リスト 12. GetUserServices
class GetUserServices(webapp.RequestHandler):
    def get(self):
        user = users.get_current_user()

        # get the user's services from the cache
        userServices = Cache.getUserServices(user)

        stats = memcache.get_stats()
        self.response.headers['content-type']  = 'application/json'
self.response.out.write(simplejson.dumps({'stats': stats, 'userServices': userServices}))

このクラスはかなり単純なものですが、非常に強力です。このクラスは、実際には Bigtable と Memcache をベースに抽象化された Cache クラスのデータを取得し、取得したデータを JSON として返します。Python オブジェクトと JSON との間での変換を行うサード・パーティーのライブラリーはさまざまにありますが、このクラスのおかげでそのようなライブラリーは必要になりません。GAE SDK には Django が組み込まれているため、ここでは Django の django.utils.simplejson 関数を使用して Python オブジェクトを JSON にシリアライズしています。キャッシュの統計も返していることにお気付きでしょうか。これらの統計は、Memcache でデータが見つかった頻度と見つからなかった頻度に関する単純な統計です。もちろん、キャッシュの統計は必須ではありませんが、少なくとも開発者にとっては興味の対象となります。これらの統計は、Web ページ上でソースを表示すれば確認することができます。最後に、content-type ヘッダーを application/json に設定している点に注目してください。この設定は、ペイロードが JSON であることを Prototype に示し、JSON を安全にデシリアライズさせるために使用されます。

GAE 上で実行されるアプリケーションからどのようにデータが提供されるかを説明したところで、リスト 9 をもう一度見てください。サービスの一覧を表として作成しているだけではないことに気付くはずです。このコードでは updateEntries 関数を呼び出すことによって、各サービスのすべてのエントリーも取得しています。この関数と、この関数を扱う Python クラスは、この記事に付属の完全なコードに記載されています。このコードは、以下のように先ほど説明したのと同様のパターンに従います。

  1. サーバーを呼び出します。
  2. Memcache でデータを検索します。
  3. Memcache にデータがなければ、Bigtable にアクセスします。
  4. Bigtable にもデータがない場合、あるいはデータが古すぎる場合にはソースにアクセスします。
  5. データを JSON としてシリアライズします。
  6. クライアントでは、プログラムによって UI をビルドします。

このサンプル・アプリケーションにはさらに優れた機能を作成することもできますが、どこかの時点でアプリケーションを GAE にデプロイしなければなりません。次のセクションでは、アプリケーションをデプロイする方法を説明し、実動アプリケーションを監視およびデバッグする方法に目を向けます。


デプロイメント

本番環境へのデプロイメントが厄介なプロセスになることは珍しくありません。コードを FTP で送れるようにしたり、ビルドを実行したりする必要があるためですが、GAE の場合、デプロイメントは極めて単純化されている機能の 1 つです。GAE インストーラーによって、appcfg.py という単純なデプロイメント・スクリプトがパスに組み込まれています (インストーラーを使わずにパッケージを解凍しただけの場合、このスクリプトは GAE のホーム・ディレクトリーに配置されています)。このスクリプトの update コマンドと、アプリケーションの該当するディレクトリー (スクリプトが読み取る必要のある app.yaml ファイルが置かれたディレクトリー) を使ってスクリプトを呼び出すだけで、リスト 13 のような出力が表示されます。

リスト 13. appcfg.py スクリプトを使ったデプロイメント
$ appcfg.py update aggrogator/src/
Loaded authentication cookies from /Users/michael/.appcfg_cookies
Scanning files on local disk.
Initiating update.
Email: your_email@here
Password for your_email@here: 
Saving authentication cookies to /Users/michael/.appcfg_cookies
Cloning 7 static files.
Cloning 3 application files.
Uploading 1 files.
Closing update.
Uploading index definitions.

たったこれだけの作業で、アプリケーションは GAE にデプロイされているはずです。ブラウザーでアプリケーションにアクセスして試してみてください。デプロイするアプリケーションは、あらかじめ GAE に登録しておく必要があります。app.yaml (第 1 回に記載したファイル) にはアプリケーションの名前が指定されていなければならないためです。このファイルに指定するアプリケーション名には、このサンプル・アプリケーションにすでに使用した aggroGator という名前は使用できません。アプリケーションの実際の動作を確認するには、http://aggrogator.appspot.com にアクセスしてください。

アプリケーションを監視する

実動 Web サイトについては、その正常性と適切な実行状態を確実にするために監視を行う必要があります。サイトの監視は、GAE では簡単な話です。Google App Engine にログインすると、以下のように自分が所有しているアプリケーションの一覧が表示されます。

図 2. GAE でのマイ・アプリケーション
GAE でのマイ・アプリケーション

リンクをクリックすると、アプリケーションのダッシュボードが表示されます。

図 3. アプリケーションのダッシュボード
アプリケーションのダッシュボード

このダッシュボードには、活用できる有益な情報が豊富にあります。なかでもとりわけ便利なのは Logs です。aggroGator を最初にデプロイした時点では、このアプリケーションに備わっているサービスのうちの 1 つ、del.icio.us は機能していませんでした。このサービスは開発段階では有効に機能していましたが、本番環境では機能しなかったわけです。幸い、GAE SDK にはロギング機能があります。問題は del.icio.us から RSS フィードを抽出するコードにあったため、そのコードに以下のロギング・コードを追加しました。

リスト 14. ロギング・コードの追加
class GenericFeed:
    @staticmethod
    def fetch(service, username):
        content = None
        # construct service url
        service_url = SERVICE_TEMPLATES[service] % username
        # fetch feed from service
        result = urlfetch.fetch(service_url)
        if result.status_code == 200:
            content = unicode(result.content, 'utf-8')
        else:
logging.error("Error fetching content, HTTP status code = " + str(result.status_code))
        return content

これで、GAE の Logs コンソールを使ってログを表示できるようになります (以下を参照)。

図 4. Logs コンソール
Logs コンソール

上記の図に示されているように、del.icio.us は HTTP 503 (サービス利用不可のステータス・コード) を返しています。つまり、コード自体には何の問題もなく、GAE と del.icio.us Web サイトとの通信に何らかの問題があっただけでした。


まとめ

この記事では、Google App Engine の機能を利用してアプリケーションのスケーラビリティーとパフォーマンスを向上させる方法を説明しました。改善の一環として、「コストの高い」データ、つまりリモート・リソースから取得するには時間がかかるデータをキャッシュするために、Bigtable と Memcache を併せて使用しています。このデータ・キャッシュに Ajax を組み合わせることにより、GAE の効率的な使用、そしてサーバーからデータがプッシュされるなどのエンド・ユーザーの心を引き付ける機能を実現しています。第 3 回では、GAE のデータ・モデリング機能をさらに詳しく検討しながら引き続き機能セットを拡張し、aggroGator を他のマッシュアップのためのデータ・プロバイダーに変換する方法を説明します。


謝辞

この記事に記載したコードの品質とパフォーマンスを大幅に改善してくださった Python の達人、Chris Gilmore 氏に深く感謝します。


ダウンロード

内容ファイル名サイズ
Sample codeos-eclipse-mashup-google-pt2-aggrogator2.zip178KB

参考文献

学ぶために

製品や技術を入手するために

議論するために

  • Eclipse に関する質問を投じる最初の場所として、Eclipse Platform newsgroups があります (このリンクをクリックすると、デフォルト Usenet ニュース・リーダー・アプリケーションが起動され、eclipse.platform が開きます)。
  • Eclipse newsgroups には Eclipse を利用し、拡張することに関心を持つ人達のために、さまざまなリソースが用意されています。
  • developerWorks blogs から developerWorks コミュニティーに加わってください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=339704
ArticleTitle=Google App Engine をベースに Eclipse を使用して作成するマッシュアップ: 第 2 回 Ajax マッシュアップを構築する
publish-date=08122008