土曜日, 3月 27, 2010

【Google App Engine】 1.3.2の機能強化に見る方向性、それから議論のまとめ このエントリーを含むはてなブックマーク


App Engine 1.3.2の目についたところ

1.3.2では、URLFetchやTaskQueueの強化がはかられた(非同期URLFetchは1.3.1で対応済)
MLより

- URLFetch API - We’ve expanded the number of ports you can access with
the URLFetch API. You can now access ports 80-90, 440-450, and 1024-65535.
- Mail API - We’ve expanded the allowed mail attachments to include
common document extensions including .doc, .ppt, and .xls.
- Task Queue API - We’ve increased the maximum total Task Queue refill
rate to 50 per second.


 一方で、エンドユーザに対しては同期的な仕組みを、バックエンドについては実行時間の延長が計画されている。GAEでは、効率よく資源活用しながら、同時に必要なものは充実させていく方針のようである。


http://code.google.com/appengine/docs/roadmap.html

Features on Deck

- SSL for third-party domains
- Background servers capable of running for longer than 30s
- Ability to reserve instances to reduce application loading overhead
- Ability to select different availability vs. latency options for
Datastore
- Support for mapping operations across datasets
- Datastore dump and restore facility
- Raise request/response size limits for some APIs
- Improved monitoring and alerting of application serving
- Support for Browser Push (Comet) communication
- Built-in support for OAuth & OpenID



 特に、Spin up問題を解決する件(Ability to reserve instances to reduce application loading overhead)については、ココにあるように、かなり自信もあるようだ。(Googleの真骨頂!?)


Keeping reserved instances has been added to our public roadmap:

http://code.google.com/appengine/docs/roadmap.html

As far as spinning up
additional instances, there are probably a few good solutions here. We'll be
best off collecting feedback when we ship reserved instances on which
solution works best.


また、MapReduceのような並列実行の仕組みを導入して大規模処理を可能にする予定があるらしい。(参照


30 sec execution limitation only to web requests or to all requests ?

Yes, queued tasks and scheduled tasks have an execution time limit as
well. You'll want to break your large tasks into smaller pieces. We've
committed to map/reduce support to help make this easier on our
roadmap for a future release.


クラウドの最大の特長は全体最適化


以上をふまえて、また、先日の中島さんとの議論を通じて感じたことをまとめてみる。(御丁寧に、また返事をいただいた。反省:思考はもっと自由に. カオスの奇跡を求めて多様性に飛翔すべし

GAEでは、 Datastore、Memcache、TaskQueueなど、リソースへのアクセスがすべてNW経由となるのが基本である。疎結合・ステートレスという特長を活かしてスケーラブルにする。だが、WebServicesのAPI(RESTあるいはProtocol Buffers)となるので、この時点でBASEが必然的に導入されることになる。

重要なのは、リソースの占有時間を縮小することで全体最適化をはかること。
資源の有効活用では、物理的なリソース占有時間の短縮を図ることと、アプリの静的な配置から動的な配置(Spin up/down)をオンデマンドで実現することで対応する。


・scalabilityやスループットの高さよりも、すべてのアプリをpartition-tolerantに書くよう強制して巨大インフラに細粒度で集約し、桁違いの全体最適を実現できることが重要と思う。でないと大規模サービス以外はあまり必要ない
NoSQLとかKVS(App EngineやNoSQLはスケーラブルだからエラいのではない)


リソース占有時間に関しては、GAEには有名な、30秒(WebRequest)、10秒(TaskQueue起動)、5秒(URLFetch)、5分(インスタンス生存期間)といったタイムアウトルールがあるが、非同期コールが導入されたことで、アプリへの実質的な制限は緩和される。(アプリからみると実質的に同期と同じ扱いにできる)物理的なリソースの占有時間を短縮するという意味では、Spin up/downや非同期コールの目的は同じである。このあたりは、できるだけ相手に干渉しないという非協調性(楽観的ロックなど)と同じ発想のように思う。(参照
また、MapReduceではTaskを細かくすることで並列実行度を上げているように、Taskを小さくすることで、リソース利用効率は上がる。
ただし、MapReduceは何でもかんでも並列実行できるわけではないし、BASEトランザクションの不完全さは開発コストとして跳ね返ってくるので、注意が必要である。

BASEなどの不完全な部分をソフトウェア技術により補うというのがGoogleの方針なのだが、開発や運用コスト、パフォーマンス、セキュリティなどのトレードオフとはうまく付き合う必要がある。

例えば、外部システムと内部システムの中間領域(厳密には内部システムの範疇、GAEでいうところのGlobal)において、エンドユーザに対して隠蔽するようなフレームワークが重要になってくる。中間領域には、MemcachedといったDHT(分散ハッシュテーブル)と、Queueing Systemがある。(GAEでいえば、MemcacheとTaskQueue)この中間領域のフレームワークにNFRを実装してBASEを隠蔽する。ミッションクリティカルなシステムの Consistencyを満たすようなACID環境をエンドユーザに提供できればよい。

なお、同期型アプリケーションが必要な部分は限定的なので全体アーキテクチャーとはせず、要件に応じてパフォーマンスを優先する(BASE)のか整合性を優先する(ACID)のかを判断する。(そういう意味ではEventually Consistencyのオプションが選択できるのは嬉しいはず)

中島さんとの議論を通じて、「クラウド・プラットフォームの不特定大量のプロセッサー資源をエラスティック(柔軟)にプロビジョニング(配置)して効率よく資源活用することが最大の効果であり、全体の最適化によりコスト削減を実現する仕組みが重要(参照)」ということを改めて認識させられた次第である。

<関連>
Scale OutアンチテーゼとeCloud構想:質問
Scale OutアンチテーゼとeCloud構想
Scale OutアンチテーゼとeCloud構想:中島の考え
Scale OutアンチテーゼとeCloud構想:中島の考えを読んで
Scale-outアンチテーゼ 思いついた反論、というか疑問
竹嵜さんの疑問に対するお答え
ホメオトシスぎるお答え
反省:思考はもっと自由に. カオスの奇跡を求めて多様性に飛翔すべし

金曜日, 3月 26, 2010

【Google App Engine】 GDataAPIを非同期に実行するライブラリ このエントリーを含むはてなブックマーク


 App Engine 1.3.1からJavaでも非同期URLFetchが使えるようになって、これまで悩まされ続けていた5秒タイムアウト問題が解消されつつある。

 非同期URLFetchは以下のような感じ。

非同期URLFetchのサンプル


import java.io.IOException;
import java.net.URL;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;

public class URLFetchServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    URL url = new URL("http://www.virtual-tech.net/");
    URLFetchService service = URLFetchServiceFactory.getURLFetchService();
    // FetchOptions.Builder. fetchOptions = new FetchOptions();
    // HTTPRequest freq = new HTTPRequest(url, method);
    // Future asyncHttpResp = service.fetchAsync(freq);
    Future asyncHttpResp = service.fetchAsync(url);
    resp.getWriter().println("async fetch");
    try {
      HTTPResponse httpResp = asyncHttpResp.get();
      resp.getWriter().println(
          "content : " + httpResp.getContent());
      for (HTTPHeader header : httpResp.getHeaders()) {
        resp.getWriter().println(
            header.getName() + ": " + header.getValue());
      }
    } catch (InterruptedException e) {
      resp.getWriter().println(e);
    } catch (ExecutionException e) {
      resp.getWriter().println(e);
    }
  }
}



Asynchronous GData API


 せっかくなので、GData APIを非同期実行できるようなパッチを作ってみた。

 これは、5秒を超えて使えると思う。Google Docsで全文検索とか、Google Appsと連携とか、これまで紹介したような外部連携アプリを(やっと)本格的に作れるようになった。

ソース:
gdataclientpatch

 使い方は簡単で、GoogleGDataRequest.javaとHTTPGDataRequest.javaとMediaService.javaを自分のアプリに置くだけ。(パッケージ名は変えないで)

 jarについては、gdata client libにもいくつか入れているが、必要であれば、GData APIの最新版(http://gdata-java-client.googlecode.com/files/gdata-src.java-1.41.1.zip)をダウンロードして必要なjarを入れてほしい。

注意点


 1. AppEngine内でnewできるDocServiceは同時に一つだけとなる。 <= APPNAMEパラメータ(下の例では"Google Service")を適当に変えることで複数のインスタンスを保持できた。スレッドセーフか!?
 2. GoogleDocsにはアクセス制限が存在し、1つのIPから同時に接続できるのは10程度である。アカウントを複数に別けても増えることはない。フロントエンドがリクエストIPを見てるっぽい。
 3. privateでは認証が必要になるが以下のようにリトライしないとうまく取れない。

<追記>
getRequestStream()に一部不具合があったので修正しています。≧‐≦(2010/3/30)




  DocsService service = null;
  for (int r = 0; r < Constants.NUM_RETRIES; r++) {
   try {
    service = new DocsService("Google Service");
    service.setUserCredentials(email, password);
    logger.warning("login OK!");
    return service;

   } catch (ServiceException e) {
    logger.warning(""+e.getStackTrace());
    
    if (r == (Constants.NUM_RETRIES - 1)) {
     return null;
    }
    sleep(Constants.SLEEP_MILL_SECOND);
   }
  }
  return null;





月曜日, 3月 22, 2010

【クラウドコンピューティング】 ホメオトシスぎるお答え このエントリーを含むはてなブックマーク


 ラ・マンチャ通信に、竹嵜さんの疑問に対するお答えを書いていただいた。いつもいつもリップサービスありがとうございます。アプライアンスについては誤解していたようなのでよく読んで勉強しておきます。

 1点だけ、気になるところがあったので、ここに挙げておきます。


 BASEトランザクションのイデオロギーには、オートノミック・コンピューティングに似た、理論信奉による全自動化追求の危険な匂いがします。
本来、セッション型が重要な部分を占めるようなビジネスのトランザクショナルなプロセスまで、理論化・自動化出来ると考えている節が垣間見えます。


これに対する私の返事(メール)の抜粋。


トランザクショナルなプロセスをトランザクションシステムにできるような、中間領域(外部システムと内部システムの間)があるのではないか、ということを最近発見?しました。中間領域には、MemcachedといったDHT(分散ハッシュテーブル)と、Queueing Systemがあります。実は、Google App Engineもそうですが、Windows Azureも同様のアーキテクチャーをしています。

私の考えでは、中間領域は厳密には内部システムの範疇ですが、エンドユーザに対しては隠蔽するのが重要になってくるのではないかと。

この中間領域のミドルウェアにNFRを実装してBASEを隠蔽する。そうすることで、エンドユーザにACID環境を提供できればよいのかなと思います。もちろん、それがミッションクリティカルなシステムのConsistencyを満たせればの話ですが、ACIDというからには満たせるんじゃないかなあと。

また、RDB(特にSQL)の運用コストなどの別のメリットも当然あって、スケーラビリティというだけで、RDBを簡単に放棄するわけにはいかないのはよくわかっています。
 その点、MSのSQL Azureは現実的な解で、既存の.Netシステムをほとんど変更なしに、Azureに移行できたという話もあります。

ただ、API化が進む昨今のアプリケーションのスタイルでは、コンポーネント化とWebサービス(REST)により抽象化や隠蔽が進んだことで、RDBとKVSとでそれほど大きく違わないという事実もあります。アプリケーションは元来OOで設計しており、RDB使用時にはO/Rマッパを通じてアクセスしています。

もしオンライントランザクションをKVSにできれば、同時にScalabilityも手に入れることができます。

ここは適材適所で考え、オンライントランザクションはKVS、情報分析や意思決定などではDWHといったように、使い分けるのがベストかなと個人的には考えています。


ちなみに、中間領域におけるConsistency実現の具体例は、GAEのSlim3実装がある。また、CassandraのConsistencyの実装など(READにおけるQUORUM)を見てもわかるように、KVSそのものもConsistencyが強化されてきている。Consistencyとは直接関係はないが、SEDAは要チェックだ。

<関連>
スケールするかどうか、それが問題だ
RDBをクラウドに載せるべきか

金曜日, 3月 12, 2010

【クラウドコンピューティング】 Scale-outアンチテーゼ 思いついた反論、というか疑問 このエントリーを含むはてなブックマーク


 Scale OutアンチテーゼとeCloud構想
 Scale OutアンチテーゼとeCloud構想:中島の考え
 RDMA実装で明らかになってきたクラウド時代の新データモデル
 
この話、反論を思いついたら書きますといっておいて放置していた。
中島さんは一言でも反論しようもんなら100倍になって返ってくることで恐れられているが、萎縮してたからではなく今は単に忙しいのだ。でも反論すると後で怖いので疑問ということにしておきます。

中島さん自身、10年くらい前に、e-Datacenterの話の関連で、マルチテナントのSaaSモデルを予言されていたので、今のPublicクラウドには異論はないのだと思う。
今回のお話を聞く限りでは、Publicクラウドは肯定されているが、それをそのままPrivateクラウドに適用することがだめといっておられるように思う。要は、NFRの作りこみがナンセンスであることと、CPUの高い処理能力が今後も見込めるということ。これは私もある程度納得できる話である。

ただ、アプライアンス志向については、何となく私でもイメージできたものの、もう少しPrivateクラウドの標準化の重要性が認知されてこないと一般の人にはまだ馬の耳に念仏かもしれないとは思う。社内の資源をデータセンターに集約してガバナンスをきかせる、そのためには標準化が重要であるという話は出てきてはいる(プライベートクラウドを構築した大手国内企業、その理由と利点を語る)が、中島さんの話を理解するには、もっと踏み込まなければならないと思う。

この考えをさらに発展させると、NW設備なども集約した1アプライアンスが、データセンター1個分ぐらいのインパクトをもつということになる。でもこのイメージが、果たしてどれくらいの人に受け入れられるかは全くわからない。昔、e-Datacenterの話を聞いたときもそうだったが、私はさっぱりわからず拒絶反応を起こしかけた。そう、この世の中にないものをイメージしろっていわれても拒絶反応を起こすのが普通なのだ。
 そのときは何もわかってないけど、とりあえずわかったふりしておくのが無難と考え、そして、10年たって流行りだすと、知ったかぶってウンチクをしゃべりだすというのが(私を含め)大多数の人の行動でもある。
 でも現実を直視すると、今の私はどうやって飯を食べていくかが問題で、10年後なんて、そんとき考えりゃいいじゃんというのも本音。誰かが投資してくれて10年間飯食わしてくれるなら話は別だが、体育会系のノリでガチンコできるわけがない。10年後はもしかしたら、HWアプライアンスのウンチクをいっているかもしれないけど。

ということで、本題に移る。

3つの疑問



疑問1 結局、CPU単体の能力は無視できないじゃないか

 大規模なデータ処理をマルチコアで走らせた場合、16コア以上ではスピードが減少するということが、IBM名誉フェローのFran Allen氏の昨日3月10日に行われた日本の情報処理学会創立50周年記念全国大会の招待講演
で紹介された。
 たしかに、Intel QPIで懸案だったレイテンシを解決できるのかもしれない。それは、AT互換機の黎明期に登場したローカルバス以上のショックはあるとは思うけれども、リニアに無限にスケールしていく話ではないように思う。(わかりませんけど)

一般にアーキテクチャは、計算ユニットとデータストア、この2つのあいだにあるバンド幅、レイテンシといったものを解決するためにあります。そしてここにパフォーマンスのバリアがあるのです。マシンの性能をより向上させていくには、こうしたバリアを乗り越えていくことが必要です。そしてこうしたバリアを乗り越えるためにキャッシュなどのデータをやりとりをするために多くの資源がつぎ込まれていきました。
計算ユニットをもっとストレージの中に分散して入れていく、というモデルがあるのかもしれません。
そして性能向上のための並列処理はこれ以上ハードウェアではできないのです。この道具をどう使うのか、それはソフトウェアにまかせられているのです。
・・・
最後に、この講義に非常に関連しているニュースを紹介しましょう。大規模なデータ処理をマルチコアで走らせた場合にどうなるか、という最新の研究です。
これによると、16コア以上ではスピードが減少するというのです。そしてこうしたマルチコアのCPUは市場に登場します。
これを解決するための答えは私にはありません。おそらくマルチコア時代にのこの問題を解決するには、デザインを見直し、アルゴリズムを再考する必要があるのでしょう。


疑問2 同期的処理が必要なのは部分的なのになぜ全体アーキテクチャが必要か

 以下はECの概要図(下)で実際に私が構築したものである。点線から上の段がインターネットの外部システム。真ん中が社内の受注システム。さらに点線の下が社外の外部システムと社内の基幹システムである。真ん中の点線の丸には、受注、在庫、出荷、販売の各サブシステムがあるが、それらは実質的には1つのシステムであり同期的に連携していた。外部システムであるYahooや楽天サイトからの受注データの入力、および、配送システム、会計システムへの出力については非同期のバッチにて行っていた。

 これは、受付時の在庫引当など、お互いに連動しあう部分のデータにおいては同期的な処理が必要だったが、会計処理など時間が過ぎて〆められた部分のデータにおいては、非同期的な処理で十分だったということ。同期的な部分はせいぜい直近の3か月分で、それ以外の3ヶ月以前のものは同期処理は必要ない。データの割合からいえば1対9。大部分が非同期処理で可能なものであった。何がいいたいかというと、本当に同期処理が必要な部分というのは極めて少ないということ。



 さらに受注処理のなかでも、商品検索、カート機能、在庫引当機能は、下図のように、それぞれ設計のスタンス(観点)が異なり、本当に同期処理が必要なのは在庫引当機能だけであったことも付け加えておく。



 外部システム連携では、例外的なエラーデータが含まれることも多いので、大量のデータをFTPなどで一旦受け付けておいて、エラーがあった行だけを送り返すといった感じで処理が進む。これは一種のEventually Consistencyである。
 このシステムでは、Yahoo、楽天からの受注取込時におけるエラーデータ修正処理(出荷日にお届けできないなど)。配送業者へのデータ送信時におけるエラーデータ修正処理(配送不能地域など)などがあった。これらはほとんどがコールセンターオペレータによるお客様(ゴメンナサイ)対応になってしまっていた。
 このように、外部システム連携では不確実性があるため、必要であれば人手を介してエラー修正することは、ある程度覚悟しなければならないと思われる。

 この事例はECという特殊なものなので、企業システムのすべてにあてはまるものじゃないとは思うが、一般的に外部システム連携に関しては、疎結合・非同期処理で十分なのではないか。
 それから、外部システムの境界は、社内外という意味だけではないことも付け加えておきたい。社内の2つの異なるシステムがそうかもしれないし、同一システム内のコンポーネントをサービス化してもそうなるかもしれない。(ECシステムの場合は、受注と在庫が別々のサービスとして立っていた)
 同様に、プライベートクラウドにおいてパワフルなコンピュータリソースが登場したとしても、複数のVMに区切って様々なサービスを立てる場合においては、同様の話になってしまうと思う。その場合、サブシステム間の連携は非同期と割り切るしかない。

 ちょっと話がそれるが、一般的なWebスケールのAPI(WebServices)は、外部システム連携の不確実性を考慮して提供しなければならないため、結果としてシンプルなRESTが流行ることになったと思う。Webスケールとは外部システムの集合と考えてよい。WS-*がサブシステム間の同期処理を念頭に置いているのに対し、RESTは基本ステートレス(非同期)である。WebスケールのAPIは、不確実性を想定していないとうまく機能しないし、同期的な処理にはなじまない。同期ではステートフルな実装が強要されるが、それは時間とリソースの束縛を意味する。もちろん、WSDLでインターオペラビリティが確保できるなんて話は幻想であることはいうまでもない。

疑問3 BASEトランザクションはそんなにダメなのか


 前述のECシステムにおいて、受注、在庫は同期的に連携していたといったが、実はこれはWebサービス連携であった。私はどうしても2つのコンポーネントに別けたかった。というか、在庫は第二フェーズの機能なのでそうせざるを得なかった。それで、どのようにしたかというと補償トランザクションで連携することにした。その結果、 補償トランザクションの悪夢のような話になった。ほれ、いわんこっちゃない、といわれそうだが、一方でコンポーネント化やサービス化のメリットも大変大きかったので、これはこれで正しかったとは思っている。

【EC開発体験記-サービス志向-】 疎結合で真価を発揮

言語の記事でも触れたが、私たちのECシステムでは、PHPと Javaの両方を使っている。PHPが主にリクエスター、Javaが主にプロバイダーだ。実は、PHPの開発者はJavaを知らないし、Javaの開発者はPHPを知らない。共通スキルはMySQLとLinuxだけだ。一昔前であれば考えられなかったチーム編成なんじゃなかろうか。お互いを干渉しない。干渉しようと思ってもできない。結果、自然と自分の責任範囲に集中することになる。これで本当の意味でサブシステム化が可能となる。私はサブシステム化する目的の一つは並行開発にあると考えている。密結合では実質的に無理な状況であった並行開発を、サービス志向であれば可能にできる。「完全なる隠蔽」によりシステム全体に及ぼす影響を最小限にできるため、分業・並行開発ができるようになるのだ。


 前述したように、コンポーネントを単位とするのが理想であり、その点では各処理はサービス化して疎結合が普通なのだけれども、受注と在庫のように、同期的処理が必要な場合もある。
 GAEでは、下図のように、DatastoreやMemcache、TaskQueueなど、すべてのリソースがネットワークだけで繋がっているため、基本的にWebサービスの呼び出しとなり、トランザクションは、EntityGroupのBASEトランザクションとなる。
 EntityGroupのロックは楽観的ロックを基本にしたもので、ロックをかけない非協調型であることが特徴だ。協調とは相手に合わせて自分が振舞うことで、非協調とは相手のことを考えない空気の読めないようなやつのことをいう。麻雀やると必ずいるだろう?自分のパイだけ直視して何の根拠もなくアンパーイとかいって全ツッパするヤシだ。
 悲観的ロックは、協調して動作することが基本。電車が運行管理システムで発射時間や速度が厳密にコントロールされている世界。一方の楽観的ロックは、各自が信号機を守って運転する自律の世界。ダイヤどおりに到着する電車に対し、渋滞もあって時間通りに着かないリスクがあるのがタクシー。でもこっちの方がとても効率がいいのである。
 リアルタイムで非協調な世界の代表はTwitter。ゆるく繋がって返事をしてもしなくても構わない世界。「リアルタイムで非協調」はWebスケールにおける重要なキーワードである。

 
 例えば、受注と在庫引当の2つがBASEトランザクションで連携すると、以下のように、アプリケーションによる2フェーズ処理のような感じになる。(BASEトランザクションの話は、送金のトランザクション処理パターンなどが詳しい)


 ① 1つのリクエストに対して受注を行いそれから在庫引当を非同期に実行する。在庫引当は必ず1回実行することは保証できるが数量によっては引当失敗が起こりうる。しかし、それを検知して受注をキャンセルすることはできない。
 ②  ①のリクエストに対して、引当失敗かどうかをクライアントからポーリングするなどしてチェックする。一定時間以内に確認できなければ失敗とみなすなど、例外処理も考慮する。


 結論からいうと、補償トランザクションはダメである。上記のようなBASEトランザクションも大変である。ではどうすればよいか。

 まず第一に、サブシステムの境界をちょっとだけ大きく広げ、サブシステム内であれば、ACIDトランザクションが可能になるようなフレームワークを作る。そのフレームワークはBASEトランザクションになるのだけれども、開発者はそれを意識せずに使えるというものである。

 ECシステムでいえば、受注、在庫、出荷、販売はそれぞれのコンポーネントとして独立しているが、サブシステムとしてみると1つであり、それぞれが同期的に連携可能とする。

 GAEのフレームワークであるSlim3は、Google App EngineでGlobal Transactionを実現している。
 Slim3では、CoordinatorがMemcacheやTaskQueueを使って2フェーズコミットを実行することでGlobal Transactionを実現している。つまり、BASEの仕組みで動作するACIDトランザクションシステムである。なんのこっちゃ!?と思われるかもしれないが、これは本当にすごいことだと私は思う。
 MemcahcedやTaskQueueといったCoordinatorの補佐をする機能は別途必要ではあるが、これらを含め、Scale Outのアーキテクチャーとして有効性が確認できれば、その応用としてマルチコア問題を解決できる可能性もあると思う。複数のCPUに共有メモリと高速バス ≒ Memcached+Apps+Datastoreに単純に置き換えて考えられなくもないと思う。
 Eventually Consistencyにも同期型、非同期型の2つのタイプがあり、同期型が実質的にACIDと同じ効果をもたらすならば、そんなに毛嫌いする必要もない。もしろ、HWを補完する機能はこういったソフトウェア技術かもしれないじゃないか。
 楽観的ロックもしかり、BASEトランザクションの応用はいろいろ可能だと思うのだが、どうだろうか。
 

木曜日, 3月 11, 2010

【Google App Engine】 In-page Integration of GFC JS API このエントリーを含むはてなブックマーク


 先日、WSSEチケット認証の話を書いたのだが、WSSEはパスワードをサーバで保持しなければいけないのが難点である。shin1ogawaさんがいうように、同じことがOAuthでできればそれに越したことはない。
 いろいろ調べてみると、In-page Integration of GFC JS APIというのが見つかった。これでいけるかもしれないので、ちょっと調べてみる。

<追記>
 ああ、shin1ogawaさんがいっていたのは、コンシューマとサービスプロバイダ間を2LeggedOAuthで繋ぐって話か。(こんなのあったのね。知らんかった)2LeggedOAuthはHMAC-SHA1を使った共通鍵認証みたいなので鍵の保持が必要だけど、”2LeggedOAuth”ってすごそうな名前なので、古臭いWSSEよりはいいかもしれない。
 ついでに調べておきます。

 GFC JS APIは、GAEを経由しないでGoogle Docsにアップロードするのに使えるかもしれないので、これはこれで調べます。

火曜日, 3月 09, 2010

【Google App Engine】 WSSEチケット認証でGoogleDocsとFederationさせる このエントリーを含むはてなブックマーク


GoogleDocsとGAEの連携
 GoogleDocsやGoogleAppsとGAEは相性がよく、これらを組み合わせたアプリを作りたいと考えている方も多いだろう。私もこの組み合わせは気に入っていて、下図のような、Google Apps 3層アーキテクチャーを基本にしたマッシュアップアプリが作れないか日々、研究している。
 この記事を書いてるあいだに、タイムリーにグーグル、Google Apps向けマーケットを開設なんて発表があった。今、Google Apps連携が熱い。



 Google Docsについては、先日、すごいのはGDriveより全文検索でしょ!?にも書いたが、PDFやEXCELといった様々なタイプのコンテンツをほぼ無制限に格納でき、かつ全文検索もできるので大変便利である。Google Docs List APIのAnyType Uploadなどは、Google AppsのPremier Edition向けとされているが、普通のGoogleアカウントでもPDFであればUploadができて、テキストファイルもcreateできるので、Premier Editionでなくても実質的にAnyTypeを扱えている。(保証はできないが・・)

 このように、Google AppsとGAEを連携させるのは非常に有効なのだが若干問題もある。一つには、Google認証だけでは、サイトを跨ぐ認証機能が不十分でうまくアプリケーションを連携できないこと。二つ目は、GAEには30秒ルールやURL Fetchの最大1MBという制約があり、GAEを介してしまうとGoogle Docsの大きなサイズのコンテンツを扱えないこと。

 これを解決するには、OAuthのような、サイトを跨ったアクセス権(認可)委譲の仕組みが必要となるが、OAuthを使ったとしても第3者の所有するリソースにはアクセスできない。
 そこで、下図のようなWSSEチケットを使った仕組みで解決してみることを考えた。これは、Federationと呼んでいるが、WSSEチケット認証機能をサービスプロバイダで受け持ち、Google Docsにユーザのアクセス権を付与することで第三者によるコンテンツ取得を可能にするものである。狭い意味でシングルサインオンのことであるが、第三者への認可を行えるという意味では別物と考えてもらいたい。
 WSSEは、WS-Securityと実質的に同じものであり、古くから存在するがあまり普及していない。Atom Pubで本命視されながら結局採用されなかった。


<やりたいこと>
  • ユーザYはサービスプロバイダZのコンテンツを取得したい。
  • YはコンシューマXのアカウントである。ZはYのことを知らないし知ってはいけない。
  • ZにとってYからのリクエストはXコンシューマからのリクエストとして処理しなければならない。

  • 一方で、コンテンツ取得の際はXを経由せずに行いたい。(1MB制限回避のため)

  • ZのコンテンツはXに対して常に開放されているわけではなく、普段は閉じられていてリクエストがあってはじめて提供できるようにしたい。
  • また課金などに使うためアクセスログも取りたい。



<処理の流れ>

  1. ユーザ(Yアカウント)はXアカウントが所有するGoogle Appsの一員である。

  2. ユーザ(Yアカウント)がZアカウントのGoogle Docsにアクセスしたい場合、サービスプロバイダにリクエストすることで、Xアカウントのドメイン名がGoogle DocsのACLに追加されてREAD権限がつく。

  3. ダウンロード後(数分後)サービスプロバイダが登録したTaskQueueによりXドメインのREAD権限が削除される。



<制約など>
  • Google DocsのACLには、Googleアカウントかドメイン名のどちらかを指定する。
  • すべての人への公開はGoogle Docs List API(Premier Editionを除く)からはできない。
  • 今回のケースでは、ZはYのことを知ってはいけないので、Xのドメイン名を追加する方法をとった。





 ① ユーザのアクションによりブラウザから以下のGETメッセージがコンシューマに飛ぶ。すると、コンシューマはWSSEリダイレクトURLを生成してブラウザに返す。

http://consumer.com/getcontent?key=2004000001.pdf


 ② WSSEチケットは、実際には以下のようなリダイレクトメッセージ(HTTP 302)であり、これを受けたブラウザは自動的にプロバイダに遷移する。nonceはランダムな値、createdは作成日時、userはXユーザである。

HTTP/1.1 302 Moved Temporarily
Location: http://provider.com/getcontent?key=2004000001.pdf&user=xxx&nonce=nnn&created=ccc&digest=ddd


 ③ WSSE URLを受けたサービスプロバイダは、保持しているXアカウントのpaswordとnonceとcreatedを結合してSHA1でハッシュ値を計算し、digestと比較する。一致すれば認証成功、一致しないか、既に使用済みのdigestであれば認証失敗としてブラウザに結果を返す。(digestはDatastoreに記録するなどして使用済かどうかを判定する)
   (パスワードを保持しているのはコンシューマXのサーバ内であり、ユーザYにはワンタイムだけ有効なハッシュ値だけが渡されるのがミソ。また、チケットを発行するには、必ずXサーバにログインしなければならないので、この時点で認可されていると考えてよい。)

 ④ 認証成功の場合、以下のような実際のダウンロードURLを返す。(これもリダイレクト)

HTTP/1.1 302 Moved Temporarily
Location: https://docs.google.com/uc?id=xxxxxxxxxxxxxxxxxxxxxxxxxxx&export=download


はこのとき、GAEだけではなく、Google Docsにもログインしていなければ、ダウンロード失敗となる。これの回避方法は後ほど述べる。また、document idが一瞬見えてしまうのが気になるところだが、コピーできるわけでもないので、まあこれはしょうがないと考えるしかないだろう。(わかったところで通常はアクセス権がないので大丈夫)

WSSE Sample




【コラム】FederationとGlobalの違い

 Globalは、アプリ内の連携という意味で使われることが多い。GAEでGlobalにアクセスできるものといえば、DatastoreやMemcacheなどがある。同じアプリであっても、内部変数などのLocalなリソースは1インスタンス内でしか利用できないが、Globalなリソースでは複数のインスタンス間で共有できる。(ちなみに、GAEでは異なるバージョンを10個持てるが、これらは1つのアプリとしてみなされるため、Globalなリソースを各々から参照できる。)
 ところが、アプリ(サイト)が異なれば、Globalなリソースであってもアクセスすることができない。Federationには「連携」という意味があり、サイトを跨ぐ連携という意味でよく用いられる。今回の話は、GAEのアプリとGoogle Docsとの連携なので、サイトを跨ぐFederationの仕組みが必要になる。


Googleのログイン認証について


 ちょっと本題からずれるが、Googleのログイン認証についていろいろ調査したことをまとめてみる。(話が複雑になるのでOAuthやAuthSubの話は省略する。)
 
ログイン画面からログインする

 ユーザアプリが勝手に画面をカストマイズすることは(たぶん)できない。以下のように、Bloggerなどは、ServiceLoginBoxを呼び出すことで独自のログイン画面を表示しているが、GAEの場合はServiceLoginが呼び出されている。


Blogger:
https://www.google.com/accounts/ServiceLoginBox?service=blogger&continue=https://www.blogger.com/loginz%3Fd%3Dhttps%253A%252F%252Fwww.blogger.com%252Fstart%253Fhl%253Dja%26a%3DALL&passive=true&alinsu=1&aplinsu=1&alwf=true&skipvpage=true&rm=false&showra=1&fpui=2&naui=8




GAE:
https://www.google.com/a/virtual-tech.net/ServiceLogin?service=ah&passive=true&continue=http://XXXXXX/_ah/login%3Fcontinue%3Dhttp://XXXXXX<mpl=ga&ahname=YYYYY&sig=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx



 また、Googleトークのようにユーザ認証なしに1クリックでGMAILボックスが開くような操作はブラウザからはできない。その理由は、ログイン画面には、GALXというソルトが埋め込まれており、表示されるたびに毎回生成されるからである。ソルト(塩)とは、ダイジェストを生成するときに含める乱数のことで、認証が成功して認証キーが生成されるまでセッションに含まれている。GALXはログイン画面を表示させないと取得できないので自動化は無理というもの。ちなみにGoogleトークではGALXを独自に生成しているっぽいが、これはGoogleだから成せる技である。

 認証キーの取得では、ログイン画面から以外に、ClientLoginサービスを呼び出す方法がある。ClientLogin for Installed Applicationsにあるように、USERIDなどをPOSTすることで、SID,LSID,Authといった認証キーを取得できるが、ブラウザから直接取得することはできない。ClientLoginサービスをサーブレットにして、認証キーを取得できたとしても、ブラウザからHTTPヘッダを付けることができないので、Authorization: GoogleLogin auth=your-authentication-tokenをつけてGETすることができない。SID,LSIDをCookieにつけようと思ってもクロスドメインにひっかかるのでできない。(ブラウザからgoogle.comにアクセスしてセットしなければならない。)XHR(AJAX)を使ってやるのも同様の理由でできない。要するにClientLogin認証はブラウザからは使えないということ。

 ブラウザ以外であれば、いろいろやり方はあるようである。


The Chromium Projects

For our needs, the normal Google Accounts ClientLogin API is not enough, as it is designed to provide cookies that allow a client application to authenticate to a single Google service. We want the cookies your browser gets when you run through a normal web-based login, so that we can get them into Chromium after the user's session has begun. Therefore, we currently go through a three-step process:

1. https://www.google.com/accounts/ClientLogin, to get a Google cookie
2. https://www.google.com/accounts/IssueAuthToken, to get a one-time use token (good for a couple of minutes) that will authenticate the user to any service
3. https://www.google.com/accounts/TokenAuth, to exchange the token for the full set of browser cookies we need to do SSO


Picasaの場合


When I run Picasa and tried to log in to Picasa Web, it connected to XSP and I was finally able to see how they were authenticating. The two interesting URLs were:

* https://www.google.com/accounts/ClientAuth
Posting the user name and password here gives us back a LSID and a SID value.
* https://www.google.com/accounts/IssueAuthToken
Posting the SID, LSID and service name (lh2 for Picasa Web) gives us an AuthToken.

Now that we've got the AuthToken, we just need to append it to the query string as auth=AuthToken when getting Picasa Web API information from http://picasaweb.google.com/api/urls?version=1. If you get that URL without the AuthToken, you will only get the read-only stuff, but adding the AuthToken gives you the post value, which is the URL used when sending commands to the server and also a cookie that should be used on the rest of the session to prove that you're authorized.

The authentication process now requires 2 POSTs and 1 GET, while before it was trying to emulate a web browser and got a bunch of redirections and lots of cookies being set and unset. Oh, and now google-sharp works on windows with the MS runtime too!


 このPython Sample(セキュリティ例外を認めて強制表示させてください)のなかでは、X-GOOGLE-TOKENを取得するというところで、上記と同じような処理を実装している。

GAEでログイン後にGoogle Docsにログインする

 一度、GAEアプリにログインできれば、Google Docsにログインすることは難しくない。ただ、GAEアプリ(service=ah)のSIDしか取得できていない状態なので、Google Docs(service=writely)のSIDを取得する必要がある。それは、以下のstatコマンドをGETリクエストしてあげればよい。(URL中のvirtual-tech.netはGoogle Appsのドメイン名)


https://www.google.com/a/virtual-tech.net/ServiceLogin?service=writely&passive=true&nui=1&continue=https%3A%2F%2Fdocs.google.com%3A443%2Fa%2Fvirtual-tech.net%2Fstat


 画面表示の際についでにこのGETリクエストも発行されるようにしておけば、再度ログインを要求されることなく静かにGoogle Docsにアクセスできる。(シングルサインオン)例えば、以下のGETリクエストを実行すると実際にコンテンツをダウンロードできる。(idはドキュメントID。Google Docsの画面で共有にしてダウンロードすると確認できる)


https://docs.google.com/uc?id=xxxxxxxxxxxxxxxxxxxxxxxxxxx&export=download



 ちなみに、downloadに続けて.htmlとすると、ブラウザはhtmlとして判断するので表示できてしまう。JavaScriptも同様に動かすことができる。ただ、提供元が、docs.google.comからdoc-xx-xx-docs.googleusercontent.comに遷移しているので、setCookieなどを仕込んで悪さをしようと思っても無駄である。


https://docs.google.com/uc?id=xxxxxxxxxxxxxxxxxxxxxxxxxxx&export=download.html



以下は、ServiceLoginを実行してからダウンロードを実行する方法。遅いのであまりおすすめではない。


https://www.google.com/a/virtual-tech.net/ServiceLogin?service=writely&passive=true&nui=1&continue=https%3A%2F%2Fdocs.google.com%2Fa%2Fvirtual-tech.net%2Fuc%3Fexport%3Ddownload%26id%3Dxxxxxxxxxxxxx



 ダウンロードはGAEを経由せずにできるようになったが、アップロードはブラウザからは無理のようである。何とかしたいけどよい方法はないだろうか。

今回の解析に使ったツール:

Live HTTP Header
FireBug+FireCookie

shin1ogawaさんからコメントをいただいたので追記:

イマイチ目的がわからない。gmail.comドメインのAppengineアプリから、userdomain.comのDocsのリソースにアクセスしたいなら、OAuthで普通に可能だけど、それとはまた違うのかな。 - shin1ogawa SoraUsagi 経由
うーん、ひょっとしてAppsの2LeggedOAuthでやれば良い話? - shin1ogawa SoraUsagi 経由


私もOAuthでやれるんじゃないかと思っていろいろ調べてみたのですがだめでした。 ><;。OAuthと周辺技術の勉強会を見ると、GFCの仕組みを使えばできそうな雰囲気なのですが・・・。もしわかったら教えてください。

<追記>
 ああ、shin1ogawaさんがいっていたのは、コンシューマとサービスプロバイダ間を2LeggedOAuthで繋ぐって話か。(こんなのあったのね。知らんかった)2LeggedOAuthはHMAC-SHA1を使った共通鍵認証みたいなので鍵の保持が必要だけど、”2LeggedOAuth”ってすごそうな名前なので、古臭いWSSEよりはいいかもしれない。
 ついでに調べておきます。
 
© 2006-2015 Virtual Technology
当サイトではGoogle Analyticsを使ってウェブサイトのトラフィック情報を収集しています。詳しくは、プライバシーポリシーを参照してください。