日曜日, 12月 20, 2009

【クラウドコンピューティング】 2009年振り返りとクラウド事例と記事のまとめ このエントリーを含むはてなブックマーク


今年はクラウドが普及した1年だった

 2009年もあとわずか、ということで、今年一年を振り返ってみたい。まずは年初に書いた「クラウド予想2009」についてから。
 このなかでいえば、「クラウドを利用したWebサービス」は思ったほど出なかった。「Google Appsの一人勝ちだが一般的には広がらない」は、4月にGAE/Jが登場してからは圧倒的だったと思う。(まあ、まだ開発者中心ではあるが)
 予想外だったのは、事例が出てくるのが非常に早かったこと。クラウドが流行るのはまだ先の話で、事例は当分出ないだろうと思っていたので、正直、そのスピード感には驚かされた。私自身、最初にクラウドに衝撃を受けたのが2008年の5月頃(※)だったので、それからまだ2年も経っていないことを考えると、本当にもの凄い広がり方であった。どれくらい凄かったか、以下の事例を見ていただくとよくわかるだろう。

(※ 私はこの記事を見て、Reflex iTextをクラウドに載せようと決心した。また、そのことを「究極のWebサービス。やられた感いっぱい ><。」 という記事に書いた。そして、公にクラウドという言葉を使い出したのが、「バーチャルテクノロジーは第二ステージへ」の記事を書いた同年8月頃だ。でもこの頃はまだ世間で通用する言葉でなかったのをよく覚えている。)
 
多くのクラウド事例が登場した

 クラウド事例の代名詞的になった「エコポイントシステム」は、安く早く作れることを実証した事例となった。また、100万PVをこなすmixiアプリモバイルコンテンツやモバツイッターは、スケーラビリティの凄さを見せつけた。海外では、BuddyPoke!やPixverseなどが出てきた。
 他のGoogle App Engineの事例では、プログラム申込予約システムや会員管理タスクマネージメントシステムや、ECまでもある。
 GAE以外の事例では、AzureのWebアルバムサービスなんてのがある。
 Google App Engineには、セキュリティに懸念があり、また、月平均に1~2回の定期メンテナンスがあって可用性も低い。過去に大きなトラブル「Google App Engineにデータストアの障害発生。復帰まで約6時間、原因は現在も不明」も引き起こしている。実運用に耐えうる可用性や信頼性を確保するにはまだまだ時間がかかりそうという印象をもっている。なので、実際の受注システムや会員管理などは時期尚早と思われたが、意外と多くの事例が出てきている印象がある。
 あと面白いのが、R-SPICE。これは、Salesforce.comとGAEを組み合わせたアプリケーション。この事例のように、ChartAPIや他のGDataAPIをWebサービスをマッシュアップして利用するパターンが今後増えてくると思われる。WebサービスはAtomやRESTといった単なるAPIとして使うことを前提とされており、汎用性が高く、クラウドのスケーラビリティ、使いたいときに必要なだけ使えるというユーティリティ性をいかんなく発揮できるのが特長だ。GAEのWebサービス事例としては、画像変換サービス や、弊社のReflex iTextサービスなどがある。GDataAPIのなかでは、個人的には、PicasaやGoogle Analitics APIに大きな可能性を感じている。欲を言えば、大量のテキストを格納できるストレージサービスと全文検索サービスを今後提供してほしいところ。全文検索はPicasaの説明文に文字を格納することで一応可能だが、最大1024文字であることと、写真付きでなければならないのが痛い。また、大量に保存する場合、GMAIL/PICASA並の料金にまで下げて欲しいところである。非常に安く提供できるという点はクラウドにとって最も重要なところである。
 これらクラウドサービスを提供し始めている企業は、大手ITベンダーというよりはむしろ中小ソフトウェア開発会社の方が多いという傾向があるように思う。弊社もそうだが、前述した写真サービス変換は有限会社アルコルさん。先日サービスされた、雨の日メールのplusvisonさんなどが事例を発表している。数人規模の有限会社でもサービスを提供できる点がGAEの凄いところで、それを可能にしているのがPublicクラウドのインフラ技術。私たちが実際に利用しているデータセンターはGoogleであり、胸を張って大手と張り合えるだけのインフラがあるといえるのが強みである。

不景気だからこそクラウド

 昨年の夏、景気が悪くなるからこそクラウドという記事を書いた。9・15に起きる、リーマンショックの約1ヶ月前のことで、まだ景気は悪くなっていなかった時期のことである。今は不景気の話でもちきりだが、当時は周りに話しても( ゚Д゚)ハァ? みたいな感じで、特に興味をもたれなかったのを覚えている。だが正直、言った本人もこれほど悪くなるとは思っていなかったので、全然説得力がなかったかなとも思う。
 いざ大不況になると、大変な変化が起きていることが、実感として感じざるを得ないようになってくる。IT業界でも単価の下落だけにとどまらず、案件そのものが無いという現実を経験した。かねてから下請けに未来は無いとは思っていて、クラウドビジネスへの転換は必然と考えてはいたが、これほど早い時期に不況が訪れて、急転換を迫られるなんて思いもしなかった。
 だが、お客様企業においても、不況に対応するために、大きな変化が要求されているのも事実である。
 いくつか相談をいただいたなかで多いのはコスト削減が一番であった。企業にとってITコスト削減要求が大きくなってきている現状をみると、部分的な利用に限っては、意外と早い時期にクラウド利用が進むのではないかとも思えてきた。
 というのは、削減目標が1/10とか、企業の管理者の間で飛び交っている話は、もはや、これまでのアーキテクチャーではとても成し遂げられないような無茶なレベルで要求されており、それを踏まえたうえで、私たちへの依頼についても、「常識にとらわれない提案をしてほしい」と、案にクラウドを期待するものも多くなってきているのもあった。これは、昨今のクラウドブームが背景にあるのも大きい。しかし、お客様自身に、現状のコストを抜本的に抑えられないアーキテクチャーのままではいけないという認識が大きくなっているのも事実であった。おそらく、この大不況がなければ、ここまで切羽詰った状態にならなかったのではないかと思う。これが原動力となって、エンタープライズへの移行シナリオにあるように、段階を得ながらクラウドに移行していく企業も出てくるかもしれない。さらには、ビジネスを変える8つの方法にあるように、ビジネスを変革できる付加価値も出てくるとなれば、当然、期待も大きくなっていくだろう。そう考えていくと、大不況は私たちにとって追い風であり、苦しいけれども、ITイノベーションを発展させていくうえで、必要な過程であると納得せざるを得ない。
 しかし、一方で「自治体クラウド」といったような、ITイノベーションに逆行する酷いものもある。3割~4割カットなんてお手盛りミエミエの自治体クラウドは、ビジネス的にも技術的にもクラウドとは全く関係ない世界であり、霞ヶ関と大手ITベンダーによる「雲の上」の茶番劇にすぎない。これは税金を使った大手ITベンダーの延命策にすぎず、ITイノベーション発展には何の寄与もしない。こんなことやっていると、せっかくお客様に芽生えたクラウド化への意識や原動力を削ぐことにもなりかねない。そして、税金が尽きたころには日本はクラウド技術で取り残され、世界に無残な姿を晒すことになるだろう。ああ、いっそのこと予算なんて付かなきゃいいのにとさえ思う。

クラウドの定義とエンタープライズへの移行シナリオ

 クラウドが普及してバズワード化してくるなかでミソクソになる、というのは年初の私の予想通りであった(弱者連合とスケーラビリティ、最近では、CBoCプライベートクラウド スタートアップキットなんてのも出てきた)
 ここでひとまず、モヤモヤしているクラウドの定義について、あらためて過去の記事を列挙することで、整理したいと思う。

 クラウドは言われだした当時から定義がモヤモヤしていたことは事実で、クラウドを15の否定で表現なんてのがあった。
 私が考えるクラウドとは、クラウドの本質と動向に書いたとおり、Scale outするシステムのことであり、抽象的に定義されたデータベースサービスへのアクセス手段を提供するものである。これは、昨年10月に書いたものなのでちょっと古いと感じるかもしれないが、今のところ私の定義は変わっていない。付け加えていうなら、クラウドは、Webサービスによる疎結合アーキテクチャーを基本としていることが特徴である。(ちなみに、弊社のReflexは、 RESTfulな新3層アーキテクチャ)にあるように、疎結合を基本に考えているので、クラウドに馴染みやすいという特徴をもつ。)
 大きく分けて、PublicクラウドとPrivateクラウドがあるが、どちらもScale outするという基本的なアーキテクチャーは同じである。Privateクラウドは、エンタープライズシステムのクラウド移行にあるように、コモディティサーバとオープンソースを基本とする分散KVSの基幹システムへの適用である。下図の価格シミュレーションは単純にHW価格だけを比較したものだが、1/4から1/3のコストダウンを図れる計算になる。


また、下図のサーバインフラの変遷にあるように、突き詰めれば複数のCPUが疎結合で連携するような処理を、1チップのなかで実現することを可能にする。Scale outアーキテクチャーが1Chipにまで応用可能であるということは、クラウドがイノベーションである証拠といえよう。(参考:流行りのクラウドは48コア・プロセッサで?



エンタープライズへの移行シナリオは、M先生にいわせると楽観的である。これに関連する話で、タイムリーに以下の投稿がMLにあった。


アマゾンのOです。
初めて投稿致します。
技術系の話題でなくて恐縮なのですが。。。

今週1週間、本社(シアトル)のほうに来ておりまして、いろいろ
情報を仕入れているのですが、個人的に驚いたのがこちらでは
大手企業でのクラウド利用状況が思いのほか進んでいることです。

本社営業チームの2010年向けの案件レビューに参加する機会が
あったのですが、大手どころに毎月かなりの金額でご利用
いただいていることが分かり、またその規模も急速に伸び
ているのがわかり、ちょっと驚きました。
で、Enterpriseでのクラウドビジネスは(もちろんスポット
利用も広告、メディア業界には多いのですが)保守ビジネスと
同じようなストックビジネスなので、毎年売上が積み上がって
いく感じなんですね。

頭では理解していたつもりなのですが、実際に営業報告のグラフ
等で見ると、改めてこのビジネスの伸び、将来性がわかった気
がしました。

こうしたトレンドは近いうちに日本でも見られるようになると
思うとなんだか楽しみな気がしてきました。
皆様にもこのワクワク感をお知らせしたく、シアトルからメール
している次第です。

機会があれば、こうしたビジネスでの広がりについても
研究会でお話ができればと思います。

それでは。


Publicクラウドの特徴と覇権争い

 一方、Publicクラウドは、 格安ストレージを利用するのなかで説明した図のように、べらぼうに安いのが特長である。運用コストも含めて、とにかく安くできるというのがウリで、逆にそれ以外のメリットはあまりないかもしれない。例えば、可用性は低くなり、セキュリティやベンダーロックインといったリスクは大きくなる。Scale Outアーキテクチャーを重視するなら、RDBをクラウドに載せるべきかという記事で書いたように、RDBというアーキテクチャーを諦めなければならないケースもある。柔軟に考えているAzureはRDBを採用することも可能だがこの場合はスケールしない。
 繰り返しになるがPublicクラウドの最大のメリットは低いコストである。GoogleやAmazonがこれを可能にしているのは、大規模なデータセンタをもっているスケールメリットや、コンテナ型で電気代などを含む徹底した管理コストの削減を図っていることもあるが、本業で使っている余剰インフラを提供している面も大きいのではないかと思う。(Googleは検索エンジン、AmazonはEC) そう考えると、弱者連合は太刀打ちできないだろう。
 特に、Googleの強さが際立つのであるが、ChromeOSと世界の対立軸で述べたように、IBM vs Google というよりは、MS vs Googleといった色合いが濃いと思う。企業向けという意味ではAzure(※)もかなり善戦しそうである。
 (※)大規模コンテナ型データセンターを構築している。参考 日経BPの中田さんによるAzure IT PACの写真

 ただ現時点でのGoogleは、失敗しないことがよいことかでも述べたように、イノベーションリーダであることは間違いないだろう。



Publicクラウドのpros cons

 パブリッククラウドとビジネスモデルで述べたように、Publicクラウドはセキュリティや信頼性がトレードオフになる分、安くなければ意味がない。
 また、Scaleするかどうか、それが問題である。
 Publicクラウドにおいては、セキュリティは最後の難問題であり、クラウドに企業情報や個人情報に近い情報を蓄積するにはセキュリティ面で不安が残るのは当然である。そもそも、クラウドにおいても企業内同様のレベルで守られて当然と考えるには無理がある。それは、内部規定で関係各所のPマークの取得やIPS構成の報告が定められている場合など、企業内データを外に出して保管すること自体、セキュリティポリシーとして禁じられているからだ。これを回避するには規定そのものを改正する必要がある。
 前述したが、現時点におけるPublicクラウドの可用性はまだ低いといわざるを得ない。オンスケジュールとはいえ、平均2回/月の停止は回数が多いと感じる。
 また、技術的に冒険的要素が強く、コスト面のメリットを勘案しても慎重にならざるを得ないと考えているお客様も多いだろう。特に、開発や運用における技術的ハードルが上がる点で不安が大きいと感じているようである。ただ、これに関しては、事例が増えて開発運用ノウハウが蓄積されていくことで解消されていくだろうと楽観している。

 クラウドには向き不向きがあり、業務アプリによってはむしろ自前でやった方がいいのも多い。クラウドでやるか、自前でやるかの判断は、最終的にはリスクを取られるお客様の判断になるという点は強調すべきである。最近ではクラウドのメリットばかり強調され、これらデメリットについて深く考えることは少ないのだが、むしろお客様にはデメリットを正直に説明することで、正しく理解していただくことが重要である。

 前述した事例のように、それでもクラウドに向いている業務アプリは多くある。それらをうまく拾っていきながら私たちはビジネスを広げていければいいと思う。

クラウドをどうやってビジネスにしていくか

 エンタープライズの移行シナリオでは利用する企業の視点で述べたが、最後に、私たちクラウドサービスを提供する側のビジネスについて述べたいと思う。

 クラウドで商売をやるには、もっと根本的にやり方を変えていかなければならないと感じている。従来は、偽装請負のススメで説明されているような発想が基本だったが、ビジネスモデルが変わるのだから、単にGAEで請負をやるという考え方ではなく、パッケージ販売のような小売業に近いビジネスへの転換が必要だと思う。
 以前、Publicクラウドはサブスクリプションモデルがいいという記事で、GAEやAmazonEC2などのpublicクラウドは、大企業から請負で開発するというビジネスモデルより、コンシューマをターゲットに小売的な商売を行って、使ってもらってナンボといった感じのビジネスモデル方が合うような気がしていると述べた。たぶん、これぐらいの大きな発想の転換は必要で、GAEを使ったアプリの開発を請負ってもいいけれど、作ったものを収めるというよりは、利用してもらって料金を徴収するといったモデルに少しでも近づけていくのがいいのではないか。Publicクラウドというすばらしい環境があるのだから、やってやれないことはない。

 ポイントは、顧客の欲しい“最終製品” を迅速に提供していくこと。

 5年後のIT市場でいったように、「ソリューション提供とかシステム構築などの業界用語は死語になる。顧客の欲しい“最終製品” がクラウドから出荷されるからだ」という話には非常に説得力があると思う。

 ただ、“最終製品”を売っていくには、相当のクオリティが求められるということも付け加えておく。小売に共通していえることは、クオリティの高いものでなければ、生き抜いていけないということ。クラウドに言い換えるとパフォーマンスやスケーラビリティ、あるいは、安くて短納期といった要素が重要になってくるだろう。Sales Forceの事例は、そのあたりのアピールが非常にうまいと感じている。要はカスタム化することで、様々な要求に迅速に対応できることをいっているのだと思うが、同じことをGAEでもやれることを示していけばよい。

 高いクオリティのものを安く売る秘訣については、小売の盟主ユニクロに学ぶことも多いと思う。例えば、UNIQLOCKのようなBlogパーツは、なかなか思いつかない。
 私自身、暮らしのデザインという小売を経験して、それが企業相手の商売とは全く異なるものだということだけは少しわかったつもりでいる。

金曜日, 12月 11, 2009

【Google App Engine】 トランザクションのIsolation Levelについて このエントリーを含むはてなブックマーク


 ajn3のあおうささんのところで議論になったトランザクションのIsolation LevelがSERIALIZABLEであるという件。実はこれまでREAD COMMITEDじゃないかと思われていたふしがあった。ちょっとそれについて触れてみたい。
 そのように思い込まされた元凶がMaxさんの、 App Engine におけるトランザクション分離という記事。ここに、ははっきりと「Google App Engine データストアのトランザクション分離レベルは、Read Committed とほぼ同等です」とある。
 でもこれは、とりあえず、getTallPeople()がQueryだということで納得することにしよう。そもそもQueryはトランザクションに参加できないのでトランザクションの分離レベルの話をするには片手落ちである。Maxさんの記事は、分離レベルの話をしたいんじゃなくて、Datastore書き込みには2つのマイルストーンがあって、updatePerson()で実際にEntityやIndexを更新するタイミングによって見え方が異なるよという話がメインのような気がする。
 一方のajn3は、Low Level APIのGET/PUTの話。あるトランザクションのPUTがコミットされるタイミングと、同時に他のトランザクションでGETされるときのタイミングの話だった。GETとPUTのタイミングを考えると、SERIALIZABLE相当と考えるのが正しい。
 ちなみに、READ COMMITEDとSERIALIZABLEについては、以下の記事がわかりやすい。

トランザクションの隔離レベル

READ COMMITTEDに設定されている場合,トランザクションの実行中でも他のトランザクションによる更新処理や削除処理が行われれば,それらの処理が完了した時点から更新されたデータが参照されるようになる。一方,SERIALIZABLEに設定されている場合,トランザクションの実行中は他のトランザクションによるデータの更新処理や削除処理に関係なく,トランザクションが開始した時点のデータが参照される。つまり,SERIALIZABLEでは,他のトランザクションから完全に隔離された状態になる。


水曜日, 12月 09, 2009

【クラウドコンピューティング】 格安ストレージサービスを活用する このエントリーを含むはてなブックマーク


クラウドは本当に安いのか!?

 Azureの登場もあって下図のようなクラウド比較情報が充実してきた。

Windows Azureの料金はGAEやEC2より安いのか


(昨日、AWSの値下げがあった)

 クラウドのコストを考える際に重要だと思うポイントは、CPUコストとストレージコストの2つを分けて考えるということ。CPUコストは、できれば、ココで示したような、TPC-Cを元にしたPrice/Performanceといった値で比較するとよいと思うのだが、Publicクラウドについては各社とても安いので、単純にCPU利用料で比較してもいいのかもしれない。ただ、Amazon EC2のc1.xlargeといったハイエンドサーバを長時間使うと割高になるので、必要なときに必要なだけ利用するといったように、短時間に効率よく利用する工夫は必要になると思う。
 問題はストレージコストの方だ。ココで指摘されているように、ストレージは決して安くはない。月額 $0.15/G でほぼ各社で足並みを揃えているが、8TB利用すると年間$14700/年かかることになる。1年間利用すると、これはカスタムサーバ1台買える計算になってしまう。

安いストレージサービスとの連携

 実は、ものすごく安いストレージサービスがある。それは、GMAIL/Picasaで、なんと年額4096ドルで16TB利用できる。GAEやEC2のStorageにはトランザクションを格納し、GMAIL/Picasaには画像やファイルといったコンテンツを格納することで、全体的なコストを大きく下げることができる。

 ということで、これを何とか活用できないか、ちょっと考えてみた。

GMAIL

 Gmailにアクセスする方法は限られている。https://mail.google.com/mail/feed/atom/で一覧は取れるものの、そこからさらに本文や添付ファイルを取得することはできない。もちろん、ブラウザでGmail画面を開いて、中の添付ファイルを右クリックすればURLを取得することはできるが、これを自動的に取得できるようなAPIは公開されていない。これはGoogleのセキュリティポリシーなのだろう。世の中にはGmailをStorage代わりに使うGPhoto Spaceや、Gmail Driveといったものがあるが、おそらく不正なアクセスを行っているものであり、切断されて使えなくなってしまうリスクがあるので要注意である。オープンソースのJavaのAPI、g4jといったものもあるが、現在は接続できないし、開発もストップしているようだ。これらは個人で使用するのは結構かもしれないが、決してお客様に提案してはいけないものである。
 ただ、Google Apps Premium Editionにおいて、データを格納するGData API、GMAIL Migration APIは正式に公開されているもので、このAPIを使用することで、GAEからでも様々なファイルをGmailに格納することができる。実際に検証してみたところ、本文および添付ファイルを格納することができた。Gmailをデータ格納用に使用しても違法ではない。このAPIを利用することで、DatastoreからExportしたデータのバックアップなどに使えるかもしれない。

Picasa

  Picasaについては、Picasa Web Albums Data APIを使ってファイルの登録および取得が可能である。価格はGmaiと同じで非常に安い。
 画像はURLを非公開(検索非対象)にできるが、知られてしまうとアクセス制限はできないので、基本的に公開されるものと考えるべきである。
  1024文字の説明を画像に付けることができ、全文検索もできるので、ECの商品検索などに応用できそうである。


 ・1つの画像は最大20MB
 ・ 画像に説明を1つ追加可能(最大1024文字)。APIを使って追加可能。
 ・ コメント(最大512文字)を複数追加可能だがAPIからはできない。
 ・ JPEG、GIF、PNGなど様々なフォーマットに対応とあるが、実際にはjpegに変換されて格納される


Google Docs

Goole Docsには、PDFなどのドキュメントを保存できるが、全体の件数に制限がある。
Google ドキュメントの概要: サイズの制限

次のとおりです。
文書: 各文書は最大 500 KB まで、埋め込み画像は最大 2 MB まで可能です。
スプレッドシート: それぞれ最大 256 列、最大 200,000 セル、最大 100 シートとなっており、いずれか 1 つでも達すると制限が適用されます。行数には制限がありません。
プレゼンテーション: 作成できるファイルの最大サイズは、.ppt、.pps 形式の場合は 10 MB もしくは 200 スライド、ウェブからアップロードする場合は 2 MB、メールで送信する場合 500 KB です。
PDF: アップロードできる PDF の最大サイズは、パソコンからの場合は 10 MB、ウェブからの場合は 2 MB で、ドキュメント リストへは 100 個まで保存できます。


上記はどれも一長一短ある。
大容量コンテンツを格納できる格安ストレージサービスが欲しいところである。

月曜日, 12月 07, 2009

【クラウドコンピューティング】 クラウドとイノベーションについて このエントリーを含むはてなブックマーク


ajn3の反響を読んで感じたこと

 疎結合・非同期アプリケーションを快適に動作させるためのフレームワークはこれまでにない新しいものになるだろう。以下のつぶやきを読んであらためてそう思った。


クラウド・アプリケーションは、仕事を並列動作できる小さな単位に分割するのが基本という感を改めて #ajn3 で受けた。処理が完結しない場合は待つのではなくて、continuationを残してイベント駆動で待つ。reactiveという側面で捉えても面白い。
・・asami224


 パラダイムも変わったのだから常識も変わる。ただ気をつけなければならないのは、これまでのデザインパターンも重要だけど、アンチパターンだからという理由で否定されてきたもののなかに、むしろ正解があるかもしれないということ。非正規化だってRDBからみたらアンチパターンなわけだから。いろいろ試行錯誤しながら最適解を見出すことが大事な気がする。その気持ちをわかっていただけて嬉しい。

昨日の #ajn3 で得た教訓。「なんでも試すことが重要」やってみることでわかることがある、覚えることができる。改めて心に響いた。早速会社で使おう。・・mochawan


#ajn3 の議論を見ているとKVSの同時性制御や隔離レベルに対する混乱とアプリケーション設計でのベストプラクティスの探求の様子が分かります。masayh



人生は見たり、聞いたり、試したりの三つの知恵でまとまってるが、世の中の技術者たちは見たり聞いたりばかりで一番重要な「試したり」をほとんどしてない。ありふれたことだが、失敗と成功は裏腹になっている。みんな失敗を厭うもんだから成功のチャンスも少ない。(本田宗一郎)・・404 ないわー (・∀・)キムティ♪ Not Foundの日記


とっても残念なこと

 クラウドは破壊的イノベーションとはよくいったもので、これまで地球を中心に太陽がまわっていたのが逆になったぐらいの大きなインパクトをもつ革命である。私自身、クラウドがのもつイノベーションに驚き、可能性を探求しているところであるが、まだまだ認識が甘い気もしている。
 その影響は、フレームワーク、言語やOSといったものにとどまらず、CPUまでにも及ぶ。(参照:シングルチップ・クラウドコンピュータ)これは、「ムーアの法則」限界説を覆す、まさにIT世界を変えるものでもある。



 クラウドイノベーションの話はここまで。

 で、何が残念かというと、今日の日経新聞の1面の記事


 自治体クラウド、IT大手一斉参入、運用費3~4割減


 自治体クラウドに関しては、ITproの記事が詳しいが、よく読むとクラウドでも何でもないことがよくわかる。いつものようにバズワードを利用した手口で、大手ITベンダーに発注させるネタに使われているだけ。内部統制JSOXの次はクラウドというわけだ。
 この大きなイノベーションが起きているときに、こんなことでいいのかよ。クラウド化を真剣にやれば、少なくともコスト1/4以下にできるはずで、Publicクラウドであれば2桁減が常識だ。3割~4割カットなんて鉛筆なめてやっているだけだろう?お手盛りミエミエなんですよ。
 我々から見たら自治体クラウドは霞ヶ関と大手ITベンダーによる「雲の上」の茶番劇にすぎない。ビジネスも技術的にも全く関係ない世界。
 いっそのこと予算なんて付かなきゃいいのにとさえ思う。そうすりゃ本当に困って我々のところに相談しにくることだろうに。
 クラウド研究会も手弁当、ajnも手弁当。ああ、国に期待できることは何もない。こんなんでいいのかね?国はもっと真剣にクラウドに取り組むべきだよね。
 1個人でさえ、「高速道路をつくりたいんだ」といってるんだよ。いやあ、泣かせるねえ。ホトトギス。


この遠慮のない濃さが #ajn3 クオリティ。良くも悪くも。俺は #appengine に関する高速道路をつくりたいんだ。そのための努力は惜しまない・・higayasuo


土曜日, 12月 05, 2009

【Google App Engine】 #ajn3資料 このエントリーを含むはてなブックマーク


 皆さん、お疲れ様でした~。あっという間に23時半になっていた。いやあ、楽しかったですね。以下に資料を置いておきます。

ぶいてく流 スケーラブルアプリの作り方
ご意見、感想をお待ちしております。

<驚いたこと3つ>
 ・ tx処理でも必ずしもルートエンティティが存在する必要はない。つまり、タイムスタンプはエンティティ単位ではなくキーの単位で管理されている
 ・ 負荷をかけてあっためることでTaskQueueはスケールしそう
 ・ runqueryをasyncに実行できること

<まとめ>
 ・ ハッシュタグの検索結果一覧をまとめてみた
・ appengine java night #3に行ってきた。 #appengine #ajn3
   (雨の日サービスに早速登録しますた)
 ・ #appengine java night #3( #ajn3 )に参加した
 ・ 最近読んだ論文
 ・ 恵比寿で働く社長のアメブロ
 ・ スティルハウスの書庫
しかし、皆まとめがうまいですね。   

<追記補足>
 ・ PUTの速度は1行のときは100ms~150ms(GETの5~6倍)ぐらいです。資料はEntityGroupによる2行更新の速度です。
 ・ 受注Kindを分けるのは意味がないかもしれないと自分でもいったのですが、それはスケーラビリティに意味がないということではなく、非常に多くの受注が同時に集中することは現実的にあまり考えられないという意味です。私のモデルでは挿入時にカウンタを作成するので、1Kindに集中するとリトライ回数が増えますが、複数Kindに分けることでカウンタ作成を分散できるのでリトライ回数を減らすことができます。(ShadingCounterと同じ理屈)でも普通はKind1つでOKな気がします。
 ・ 商品画像などの扱いをどうするかという質問が懇談会のときにありました。私のおすすめはPicasaです。.jpgみたいなキーで関連づけることで、GAEと別々に管理することができます。また、大量の画像を登録する際にGAEを介さなくてもよいという利点もあります。そして、何より安いこと。8TB格納したとすると、Datastoreだと$14400/年ですが、Picasa(GMAIL)だと$2048/年で済みます。PicasaにはGDataAPIで操作できます。

金曜日, 11月 27, 2009

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


トランザクションは意識しないのが一番

 歴史は繰り返すんだろうか。トランザクションに関しては、こんな感じで話がループしている気がしている。
 
1) トランザクション宣言を明示的にコードに記述(ホスト、ODBC/JDBC) 
2) 生産性が低くなり新しいフレームワークが出現(EJB、Seasar2)
3) 新しいイノベーション誕生(cloud)、トランザクションを明示的に宣言、以下繰り返し

 トランザクションについては、2年前にも一度述べたことがある。実装が難しく不具合になりやすいので、生産性、品質を上げるには、開発者にトランザクションを意識させないことが一番である、といった感じのことを書いたつもりであった。

【EC開発体験記-トランザクション処理-】古くて新しい永遠のテーマ

 GAEにおいても方法論は異なるとは思うが、開発者にトランザクション処理を意識させたくないという私のスタンスは今でも変わっていない。GAEにもそのうち新しいフレームワークが現れて生産性が高くなっていくかもしれない。
 でもエンジニアの性というか、トランザクションを議論するのは、いつの時代でも好まれるようである。何百時間とかけて、そのくせ不完全だったりする(私も例外ではない)。
 モデリングの話もそうだったが、エンジニアは基本的に穀潰しである。
 とはいえ、過去の考え方をあらためて学ぶことは、それはそれで有益に思えることもある。 ということで、一応、復習することにする。

トランザクションコンテキストは野球のボール

 EJBが登場したとき、メソッドの呼び出しだけで、何でトランザクション処理が可能になるのかわからなかった。実はコンテナがメソッドを管理していて、呼び出し時にトランザクションコンテキストの受け渡しを行うことで実現していたのだが、これを理解できたときはとても感動したものだった。(しかし、重いEJBはパフォーマンスが悪かったので普及せず、Seasar2がそれを改善した。AOPによりトランザクションを実現しているのを知ったときは一本取られたと思った。)

 で、その具体的な仕組みを説明する。

 トランザクションコンテキストは何かというと、マジックで番号が記入された野球のボールのようなものと考えればよい。担当者に仕事を依頼する際に、受付番号を書いたボールも一緒に渡すことにして、もし担当者が処理に失敗した場合は、ボールに×を書いて返すようにする。担当者は別の担当者に再委託もできるが、その際にも必ずボールも渡すこととする。最後に依頼者にボールが戻ってきたときに、×が付いているかをチェックして、処理の成功失敗を判断する。成功であればその受付番号の処理を最終的にコミットとする。失敗であれば何もしないでボールを捨てる。受付番号が記入されているので同時並行処理が可能なのである。

受注と在庫引当を分散トランザクション実行

 以下は、分散トランザクション実行のアイデアである。(このBlogでは、受注とか在庫とかのEC単語が唐突に出てくる。)上記のトランザクションコンテキストを受け渡す方法を元に考えたものだ。図には書いていないが、TaskQueueによる在庫引当では冪等性(べきとうせい)も考慮する。
 受注受付のタイミングと在庫更新のタイミングは異なるが、2重引当などの致命的な問題は起こらないはずである。ただ、実際より在庫が少ないとみなされる瞬間がある。
 ブラウザーでステータスを確認するか、ステータス更新時にメールするなど、基本的にはステータス参照で対応するところが非同期の特徴である。
 なお、口座送金処理といったものへの応用は、もしかしたら可能かもしれないが、参照のタイミングが一致しないので、この方法ではたぶん無理だと思う。

 

木曜日, 11月 26, 2009

【クラウドコンピューティング】 エンタープライズシステムのクラウド移行について このエントリーを含むはてなブックマーク


 世界的な不況が続く中、競争力のある強い体質の企業になるためには、大幅なコスト削減が必要である。とりわけ、ITシステムへのコスト削減圧力は大きく、これに対応するためには、アーキテクチャー見直しや内製化などを含め、抜本的な改善していかなければならない。
 Privateクラウドは、ITシステムのコスト削減の一手段として考えることができ、こういった課題に対応できる可能性をもつと考えられる。
 今回は、Privateクラウドをテーマに思うところを述べてみたい。

Privateクラウドはクラウドではない?

 11/22の日経新聞にクラウドの特集があった。
 そこには、クラウドの定義ははっきりしないが、「ネットの向こう側にあるソフトなどのIT資産を使う」という概念が基本となると書いてあった。また、誤解に関して以下のような感じで書かれてあった。


 クラウドがブームになり、最近は関連する一部技術を入れ込んだだけのサービスにもその名が付く。「企業内クラウド(プライベートクラウド)」。「仮想化」という技術を企業内のIT資産に適用したもので、効率的な社内システムを構築できるが、「ネットの向こう側」を利用するクラウドの本質からは外れている。こうした拡大解釈が広がれば、利用者の誤解を招きかねない。利用する側も注意が必要だ。


 まあ、クラウドは一般的には「ネットの向こう側」と解釈されるのだろう。
 でも私は、クラウドの本質と動向にも書いたとおり、あくまで「抽象的に定義されたデータベースサービスへのアクセス手段」と考えていて、また、べらぼうに安いこと、およびリニアにスケールすること、の2つのポイントも重要だと考えている。なので、企業内にあっても外部にあっても、上記が満たせるのであれば別にクラウドと呼んでもいいと思っている。別に「ネットの向こう側」になきゃいけないわけではない。具体的には、今後はDHT(Distributed Hash Table)技術を使った分散KVS(Key Value Storage)などを応用したものが企業内クラウドの代表的なものになっていくんじゃないかと考えている。
 一方で、「ネットの向こう側」にありながら、全然安くないクラウドもたくさんある。GAEやAmazonEC2などは、CPU時間あたり$0.10、Storageは$0.15 (GB/Month)でほぼ足並みをそろえているのに対し、日本の某通信会社のPublicクラウドは十数万円と2桁の差がある。利用する側が注意しなければならないのは実はこっちの方である。

企業内クラウドもコストとスケーラビリティは重要


 分散KVSといったクラウド技術を企業内システムに応用することで得られるメリットは、コスト削減とスケーラビリティ向上の2つである。
 まず、トランザクション増加に対応するためには、ノードの追加だけでリニアにスケールするような仕組みが重要である。それから、基幹システムであるからにはAvailabilityも重要となる。一部が故障しても自動的に切り離されて問題なく動作しつづけるような高可用性システムをいかに構築できるかが鍵となる。
 コスト削減効果として期待できるところは、コモディティサーバとオープンソースの活用によるインフラコストの削減である。実際に、ノードはIAアーキテクチャでLinuxベースのコモディティサーバを使えばいいし、ソフトウェアに関しては、実際に動的なノードの追加削除を可能とするDynamoクローンのオープンソースもある。このように、クラウドの仕組みを、今ではコモディティサーバとオープンソースで実現できる。
 2点目は、内製化により「脱ベンダー依存」ができるという点。つまり、コモディティサーバとクラウド技術を活用したオープンなアーキテクチャーを構築することで、ITベンダーの固有のアーキテクチャーに依存しないシステムを構築することが可能になるということ。これまでベンダーによる見積もり(お手盛り)をせいぜい値引きするぐらいしかできないのが現状であった。
 オープン技術の組み合わせでエンタープライズシステムを構築できれば「脱ベンダー依存」が進み、ベンダー独自の技術に依存してきた弱みから脱却できるようになる。
 ITベンダーによるボトムアップ提案から利用者自身によるトップダウン調達。コモディティサーバなどの部品調達レベルの取引になっていくことで、エンタープライズシステムの価格破壊は必至となる。

価格シミュレーション

 実際にどれくらい安くできるのかを調べるため、具体的にTPC-Cベンチマークを元に価格比を計算してみた。

 1) Scale-up型の最高モデルであるIBM p-595とコモディティサーバHP ML350とを比較すると、単純CPU能力換算でCostは1/4となる。
 2) HA構成(マシンは2台)とクラウド構成(マシンは3台)で比較するとCostは1/3となる。(クラウド構成でマシン3台を根拠とする理由は、Quorumプロトコルで推奨されるのがN:R:W=3:2:2であること。実際のDynamoの冗長ノード数が3台であることなど)

 もちろん、場所代や電気代など、計算に含まれない要素はある。極めて一面的な見方しかしていないが、PrivateクラウドのHWコストメリットは、ざっくり、1/3~1/4となる。
 面白いのは、IBMの機種どおしで比較してもコストメリットは見出せないということ。おそらく、全プラットフォームでPrice/Performanceが2.5前後になるような戦略的な値を設定しているのだろう。



 もう一つ、特筆したいのがHWに及ぼす影響についてである。
クラウドは、Scale-up型のハイエンドサーバから、Scale-out型のIntel Xeonベースのコモディティサーバへの変化をもたらし、さらには、FAWNといったようなローエンドプロセッサとフラッシュメモリのクラスタへの変化を促すだろうと考えられている。そして最後には、シングルチップ・クラウドコンピュータへの進化に行きつく。クラウドのイノベーションは、複数のCPUが疎結合で連携するような処理を、1チップのなかで実現することを可能にする。



クラウド化による新たな課題


 安価なハードウェアの導入とオープンソースの利用を中心とした、エンタープライズ・システムのコモディティ化は、コスト面やスケーラビリティにおけるメリットが大きい一方で、以下の新たな問題を生むこともわかっている。

 
 1)リスクを利用者自身が負うことになる
 2)システムが複雑になる分、運用管理が大変になる


 脱ベンダー依存を成し遂げたら、すべての責任は利用者自らが負うことになる。これまで、システムの調子が悪かったら何でもかんでもベンダーの責任にできたところが、そうはいかなくなり、自分自身で大きな不安と苦しみを抱え込むことになる。
 また、これまでの2~3台であったシステムが100台近くに増えると、運用の仕組みが大きく変わってくる。例えば、バックアップを取る際には、全ノードを一旦Read Only状態にしたうえで実行しなければならない。また、ソフトウェアの配布などを100台一斉に実行するような仕組みも必要になってくるだろう。このあたりは実際に運用してみると、もっと大きな課題が出てくると思われるが、いずれにしても運用をいかに効率よくやっていくかが重要なポイントであることは間違いない。(Googleはこの点がすばらしいと思う)

マイクロソフトとAzure戦略

 日経新聞のいうところのクラウドの本質は「ネットの向こう側」を利用することであった。前述したように、私から言わせれば、PrivateとPublicの違いはコストだけなのだが、コストメリットだけ考えると、Publicクラウドの利用は大変魅力的であり、エンタープライズにおいて活用できれば非常に大きな競争力となるのは間違いない。とにかく、Publicクラウドはべらぼうに安い。
 AmazonやGoogleには、それぞれがEC、検索エンジンといった本業をもっていて、システムの余剰なリソースを貸し出せるという利点がある。もちろん、スケールメリットがあり、電力効率などを含めたデータセンターの効率的な運用数値PUEがずば抜けてよいことも知られている。

 当分の間は2強の時代が続くと思われるが、猛烈に追随しているマイクロソフトのAzureも気になるところである。繰り返しになるが、ポイントはコストとスケーラビリティの2つ。安さという点で2強を追随できるのか、あるいは、スケーラビリティの点で優れた技術を提供できるのか。

 本来、Windowsはコンシューマから発展したOSで、基幹システムでの利用が進んでいるとはいえ、ミッションクリティカルで大規模なシステムはあまり得意ではなかった。前述したような、IBM p-595サーバとは、比較すらできなかったのであるが、Azureでもってスケーラブルシステムが構築できるようになれば話は別である。セキュリティ、コストといった課題はあるものの、エンタープライズに踏み込める可能性がさらに大きくなったといえる。

<追記>
 日経BPの中田さんによるAzure IT PACの写真

<関連>

Chrome OS戦略とIT世界の対立軸

水曜日, 11月 25, 2009

【Google App Engine】 頑張って全文検索 このエントリーを含むはてなブックマーク


TaskQueueを使った全文検索


 非同期処理、並列処理はPDF生成に限った話ではない。これを応用して全文検索も考えることができる。例えば、下図のように、データを分割して複数のTaskで並行処理することで、検索にかかる時間を短縮することができる。これまで説明してきたように、Datastoreでは、前方一致検索はできるが全文検索はできない。もしこれで全文検索が可能になるのであれば嬉しいことである。Indexを使わずに全件検索するなんて無謀のように思えるかもしれない。でもこれこそがクラウドの醍醐味なのだ。



 実装は下図のような感じになる。商品マスタ※検索では、商品名、基本説明、詳細説明を全件検索して、部分的にでもヒットしたら結果を返すようにした。(※ 今ECを作っているので、このBlogでは唐突に商品マスタなどの単語が現れる) 
 

  • 検索項目
    • 商品名
    • 基本説明
    • 詳細説明
  • URLパラメータ
    • fulltext : 全文検索する正規表現
    • 例えば「コーヒー」という文字列を検索条件とする場合、以下のように指定する必要あり
      .*コーヒー.*
    • pagesize : 1レスポンスで返される最大件数(デフォルト100)
    • next : 次の行番号。先頭から何番目かを指定する。
      • 数値を指定すると、その値からpagesize件数を返す。
      • 「101-200」のように、範囲指定も可能。これが指定されているとpagesizeは無視
      • nextパラメータが指定されている場合、検索を行わずMemcacheをチェックする。要するに2回目以降(場合によってTaskQueueへの追加登録を行う。)
    • taskunit : 1タスクで処理される件数(デフォルト500)
    • queueadd : 1度に登録されるタスクの数(デフォルト4)
      • tasknoをmemcacheで管理しているので各taskは、(taskno-1)*taskunit+1 ~ taskunit分を処理することになる


実行結果

  • データを10000件登録してテスト。
    • 2000件だと、重すぎてエラーになる場合が多い。
      以下のメッセージが返される。サーバ側にログは出力されていない。

      Error: Server Error
      The server encountered an error and could not complete your request.
       
      If the problem persists, please report your problem and mention this error message and the query that caused it.

    • TaskQueueに4件処理が登録されるが、以下のWarningログが大量に出力され、いつまでたっても処理が終わらない。

      Request was aborted after waiting too long to attempt to service your request. Most likely, this indicates that you have reached your simultaneous active request limit. This is almost always due to excessively high latency in your app. Please see http://code.google.com/appengine/docs/quotas.html for more details.

    • 1000件でも同様。
    • 1タスク500件、一度に4タスク登録。(合計20タスク)
      • 全データ検索まで : 1分25秒
      • 1タスクの処理時間 : 5~7秒
      • 最初に1回タスクが最初に実行される前に"Request was aborted after waiting too long ..."のWarningログが1件出力された。
        その後は順調に処理された。
    • 一度に2タスク登録。
      • 全データ検索まで : 1分34秒
      • "Request was aborted after waiting too long ..."のWarningログは出力されなかった。
    • 一度に6タスク登録。
      • 全データ検索まで : 1分30秒
      • 最初に1回タスクが最初に実行される前に"Request was aborted after waiting too long ..."のWarningログが3件出力された。
        その後は順調に処理された。
    • 一度に8タスク登録。
      • 全データ検索まで : 1分47秒
      • 1タスクの処理時間 : 6~7秒
      • 1回目と2回目に登録したタスクが最初に実行される前に"Request was aborted after waiting too long ..."のWarningログが3件出力された。
        (最後の3回目はタスクの登録件数が3件なので出力されなかったと推測される。)
    • タスク登録なし
      -> 本来レスポンスを受けたインスタンスが全文検索を行うのは最初の1回のみなので、とりあえずテスト用に、最初に件数取得リクエスト。それからデータストアから500件検索してインメモリでPattern-Matcherチェック。これを開始データを500件ずらして20回行う。
      • 全データ検索まで : 6分4秒
      • 1リクエストの処理時間 : 6~7秒
      • レスポンスを1回+20回投げたうち、5回以下のエラー発生。
        サーバにログは出力されていない。リクエストをはじかれている感じ。

        java.io.IOException: Server returned HTTP response code: 500 for URL:


  • 1タスク1000件時のエラーについて
    • 最初のリクエストから10分経過した時点で、登録された4件のタスクは残ったまま。
    • それぞれのタスクはリトライを12回しているようである。(TaskQueueのRetries = 12)
    • ログには"Request was aborted after waiting too long ..."のWarningログが大量に出力されているが、タスクのServletに記述した開始ログが出力されていない。
      -> "Request was aborted ..."の場合、処理がロールバックされてログも出力されない??


 ちなみに、1回目の実行時(Coldstart時)はすぐに完了するようにすることで、"Request was aborted after waiting too long ..."のWarningログが出ないようにすることができる。そうすることで約30秒実行時間を短縮できるが、それでも同時実行インスタンスは4つ以上にならないようである。その様子は、前記事に書いたとおり。

Relation Indexによる全文検索


 TaskQueueを使った全文検索では、1万件のデータで1分40秒かかるため、とても実用的とはいえない。それから、商品名で検索して安い順で表示させたいことはよくあるが、「安い順」という新たな条件が追加されることで、もうお手上げになる。「安い順」であれば、価格に対してaddSort条件を追加してやればいいのだが、そうすると、ID順の時のように、件数で区切ってTaskQueueに登録することができなくなってしまうのだ。ムリクリやるなら、最初のページの最後のレコードを、次ページの開始点(greater than)とすればよいが、いずれにしても最初ページを検索しないと次ページも検索できないことになってしまう。これでは並列処理はできない。

Like+「安い順」検索 データを10000件登録してテスト
1タスク500件、一度に4タスク登録。(合計20タスク)
  • 全データ検索まで : 1分36秒
  • 1タスクの処理時間 : 6~11秒
  • "Request was aborted after waiting too long ..."のWarningログは出力されなかった。
  • ほかに待っているタスクが無いせいか、タスクを登録したらすぐに実行されているように見える。
    • (登録元タスクの終了前に、登録先タスクが開始されているので)
  • インスタンスの起動状況が安定してくると、4タスクがほぼ同時に実行される箇所も出てきた。
    taskno=10~13、14~17)
    • データストアの検索には短くて0.06秒くらいしかかかっていない。その後のPattern-Matcherに時間がかかっているよう。


 そもそも、無尽蔵にTaskを起動して並行処理させようなんて発想自体が無謀である。クラウドにはCPU資源が潤沢にあるとはいえ、今はエコが当然とされる時代なのである。ということで、Brett Slatkinさんがいっている、関連Index(Relation Index 資料のP23-P25)を実装することで解決しようと思う。

  • Relation Index Solution
    • Do a key-only query to fetch the MessageIndexes
      • MessageIndexをKey(word)で検索する
    • Transform returned keys to retrieve parent entity
      • 検索結果のKeysを取得
    • Fetch Message entities in batch
      • Keysからまとめてメッセージを取得
  • 具体的な設計
    • ProductのRelation IndexをProductIndexとする。
    • 1つのwordにつき、すべてのSuffix ArrayをProductIndexに登録する(下図の例だと11個)
    • 文をスペースで区切ったTokenをWordとする
    • 登録するもの
      • Key(wordのSuffix Array)、Value(位置情報のList)
    • 位置情報のList
      • ProductのKey(shop_code+product_code+revision)#項目名

        (property name),位置(offset)
        例) key#product_name,5
        コーヒー,「アメリカンコーヒー」





 また、これは、Brett Slatkinさんのものと異なり、EntityGroupを構成しない。その理由はパフォーマンスに大きく影響するからである。若干の時間差があったとしても、以下のように非同期にIndexを作成する方がよい気がしている。スケーラビリティを確保するためには、あまり神経質にならない方がいいことは、この記事でも述べたとおり。



考慮点

  • IndexはKey検索である(直接Wordを指定しての検索) 前方一致検索である。このため、Entityの方にも、Suffix Arrayの値もつことにする。(重複を避けるため、KeyにSuffix Arrayの文字列を代入することはする)
  • 商品削除時はIndexも削除する
  • Suffix Arrayが可能な最大文字数
    • 1000文字未満とする(なぜなら1000行は一度にPUT(KEYS)できる限界だから)
    • Productの商品名(Product_name)および、基本説明(Summary1)について、全文検索できるようにする。
    • 1WORDは20文字程度が望ましいかも
      • 20文字*50個の登録で1000行

<追記>
Suffix Arrayから任意の文字を検索するには、前方一致検索をしないとダメ。
  • 例えば、「アメリカンコーヒー」という商品のSuffix Arrayは
    1. アメリカンコーヒー
    2. メリカンコーヒー
    3. リカンコーヒー
    4. カンコーヒー
    5. ンコーヒー
    6. コーヒー
    7. ーヒー
    8. ヒー

  • となる。
    ここで「コーヒー」という単語で検索すると、6番目の値と合致するが、もし「アメリカン」という単語で検索すると、key検索では一致しない。前方一致検索をすれば、1番目の値と合致する。

月曜日, 11月 23, 2009

【Google App Engine】 TaskQueueのスケーラビリティを阻害している原因について このエントリーを含むはてなブックマーク


Request was aborted after waiting too long が出る理由

 前記事の実行結果を見てもらえばわかるとおり、"Request was aborted after waiting too long・・"というエラーのせいで、処理が中断されてリトライが頻発しているものが多くある。これがTaskQueueのスケーラビリティを阻害しているのは間違いない。これは、queryの実行時間が10秒を超えると出るようである。(PDF生成処理Taskでは10秒を超えるものがあるのでtaskの経過時間というよりqueryの実行時間によるのだと思う。たぶん)
 別にquotaを上回るような処理を流したつもりはないのになぜ出るのか。
 この現象については、ココでも質問されているが、Googleからの返事はまだない。

total queue execution rateの定義

 Taskの実行時間が10秒を超えるとキャンセルされる、とはどこにも書いていないので、エラーメッセージにあるとおり、何かしらquotaを上回る処理があって、それが原因で待ちが発生して、これ以上待つのは無理だからキャンセルされた、というふうに理解していた。quotaといっても、ココにあるように、いろいろあるが、私は、total queue execution rateが一番あやしいと思っている。
 MLでJamesさんが自己レスしているが、total queue execution rate が 20 task invocations per second というのは、dequeueするtaskの数ではなく、処理可能なquery数ということらしい。つまり、taskqueueを使って処理できるのは最大20qps/sということ。これなら(納得はできないが)理解はできる。

 この件に関して、ありがたいことに、ひがさんからコメントいただいた。このあたりの話は、GAE Nightでいろいろ聞くことにしよう。

 あのwarningは、リクエスト処理中に後続のリクエストが来てqueueにつまれ10秒以内に処理されなかったときに発生します。

 つまり、AppEgnineは30秒ルール以外にも、負荷が集中するときには、10秒以内に処理しないければいけないという10秒ルールも存在するのです。

 10秒ルールを守ったとしても完璧にスケールするかはまた別の話ですが、今のAppEngineの能力を最も引き出せるということはいえると思います。


エラーが発生する原因

 「Request was aborted after waiting too long 」が出る原因を考えてみた。

 仮定1)同じインスタンス(同じVM上アプリ)に複数のリクエストが入ろうとして待たされている。
 仮定2)spin up(アプリケーションの起動)までに時間がかかるのが原因。例えば、cold start時には、まっさらなVMにクラスをロードする時間などが余分にかかる。それが加算される。
 
 これを突き止めるために、以下の実験をやってみた。

実験1) 同じvmであればEnQueueしなおしてすぐに終了させるようにするとどうなるか
実験2) 新規のvmhashの場合、一回目はすぐに終了させるとどうなるか



 同じVMであるかどうかは、こちらの記事にあるとおり、Runtime.getRuntime().hashCode()を比較することで判別するようにした。(memcacheを使ってhashcodeを管理する)

 そしたら、hashcodeが重なってた(下図)ので、こちらの記事のように、ServletContext+UUIDで判別するようにした。



実験結果


 前記事の全文検索アプリを使って実験した結果、以下のようになった。

  • 1タスク500件、一度に4タスク登録。(合計20タスク)
    • 全データ検索まで : 1分41秒
    • 1タスクの処理時間 : 5~7秒
    • ただし、初期起動の場合は8~9秒
    • RuntimeのhashCodeが同じでも、違うuuidが最初に起動する場合、8~9秒かかる。
    • "Request was aborted ..."が1回出現。
  • 起動チェックをRuntimeのhashCodeでなく、uuidで行う。
  • uuidごとだと仮定1(複数のTASKが同じVM(マシン)で実行される)は起こらないので、実験1は行わない。実験2のみ行う。
  • 以上の条件で、1タスク500件、一度に4タスク登録。(合計20タスク)
    • 全データ検索まで : 1分4秒
    • 1タスクの処理時間 : 6~7秒
    • 起動インスタンス : 4個
    • "Request was aborted ..."のWarningログは1回も出力されていない。
  • 1タスク500件、一度に8タスク登録。(合計20タスク)
    • 全データ検索まで : 1分6秒
    • 1タスクの処理時間 : 6~7秒
    • 起動インスタンス : 4個
    • "Request was aborted ..."のWarningログは1回も出力されていない。
  • 1タスク500件、一度に16タスク登録。(合計20タスク)
    • 全データ検索まで : 1分34秒
    • 1タスクの処理時間 : 6~7秒
    • 起動インスタンス : 5個
    • "Request was aborted ..."のWarningログは1回も出力されていない。
  • 1タスク1000件、一度に4タスク登録。(合計10タスク)
    • 最初にリクエストして約5分経過しても、1個のタスクも終了しない。
    • 新しいインスタンス起動 : 22回
      • 実験2)新しいインスタンスの場合QueueにTaskを登録し直してすぐに終了する、という処理は正常にできている。
    • "Request was aborted ..." : 18回
    • なぜかQueueのTaskが4個から9個に増えた。(プログラムバグ?)
  • TaskQueueの設定を変えてテスト。rate=10, bucket size=10に設定。
    • 4タスク : 1分4秒
    • 8タスク : 58秒
    • 16タスク : 1分12秒
      • 16タスクの場合のみ"Request was aborted ..."のWarningログが数件出力されている。
  • TaskQueueの設定を変えてテスト。rate=10, bucket size=20に設定。
    • 4タスク : 1分5秒
    • 8タスク : 1分
    • 16タスク : 1分22秒
      • 8,16タスクの場合"Request was aborted ..."のWarningログが数件出力されている。
  • 1タスクあたりの処理件数を変えてテスト。(TaskQueueの設定 : rate=10, bucket size=10、一度に4タスク登録)
    • 700件(合計15タスク) : 2分54秒
      • 1タスクの処理時間 : 8秒~10秒弱(10秒は超えていない)
      • "Request was aborted ..." : 4回
    • 600件(合計17タスク) : 1分57秒
      • 1タスクの処理時間 : 7秒~8秒
      • "Request was aborted ..." : 2回
    • 400件(合計25タスク) : 57秒
      • 1タスクの処理時間 : 4秒~6秒
      • "Request was aborted ..." : なし
    • 300件(合計34タスク) : 1分2秒
      • 1タスクの処理時間 : 4秒~5秒
      • "Request was aborted ..." : なし
    • 200件(合計50タスク) : 1分16秒
      • 1タスクの処理時間 : 2秒~3秒
      • "Request was aborted ..." : なし
  • まとめ
    • uuidごと(ServletContextごと)にcold startされている。
    • Taskの処理時間が10秒を過ぎると"Request was aborted ..."とWarningログが出力され、リトライされているようだ。
    • 仮定2)cold startの場合はQueueに再度処理を登録し直して自分は終了する、という方法は、しない場合と比べて速くなった。
      • cold startの場合は処理時間が10秒を超えてリトライされていたためと推測される。
    • インスタンスは4~5個立ち上がるが、同時に処理を行うのは最大4個。
    • 1タスクあたりの処理時間が4秒~6秒以下の場合、Taskの同時処理数が4個で安定する。
      10秒近くなるとTask同時処理数が減ってくるため、総処理時間が遅くなる。
    • 1タスクあたりの処理時間を4秒~6秒とすると、総処理時間が最も速くなった。


 エラーメッセージは出ないようになったが、相変わらずインスタンスの最大が4つというところが納得いかない。

日曜日, 11月 22, 2009

【Google App Engine】 TaskQueueはスケールしない!?3 このエントリーを含むはてなブックマーク


TaskQueueを使ってPDFを生成する

 先の記事で疎結合、バージョニング、非同期がスケーラブルにするための要素だと述べた。私たちが提供するReflex iTextサービスでは、PDF生成サービス(図中 pdfservice)やデータサービスなどは、独立したサービスとして立てられる。独立したサービスは(カッコつけていえば)サービスコンポーネントとして考えることができる。サービスコンポーネントは、ServiceとReferenceの2つの口をもち、いわゆる芋ずる式に(疎)結合できる。そして、TaskQueueを利用することで、それらを非同期かつ並列に実行することができる。これを、ぶいてく流サービスコンポーネントアーキテクチャーと呼んでいる。



 例えば、請求書アプリでは、
http://invoice.latest.reflexcontainer.appspot.com/invoice?invoiceNo=DEMO1&xml

でGETすることで、データサービスからデータを取得することができる。(&jsonとすることでjsonも取得も可能)
 そして、以下のようにデータとtemplateを同時に与えることでPDFを生成する。内部的には、pdfservice(service)に与えられたurlを元に参照(reference)を実行している。
http://pdf.latest.reflex-itext.appspot.com/pdfservicegae?template=http://invoice.latest.reflexcontainer.appspot.com/invoice.html&entity=http://invoice.latest.reflexcontainer.appspot.com/invoice?invoiceNo=DEMO1&xml


 さらに、TaskQueueによって起動された複数のTaskからこのリクエストを実行すれば、大量のPDFを瞬時に生成することができる。これは以下のような仕組みになっている。



スケールしない現実

だが、ココや、ココに書いたとおり、実際にはスケールしない。
 700ページを12リクエスト(合計8400ページ)処理させた場合について計測してみると、unit=70の場合(700/70*12=120タスク)で約15分かかってしまう。unit=70の場合で1タスクの処理時間が30秒未満なので、本当に並列処理されているとすれば、30秒で完了しなければならないはずである。ちなみに、unitの数と処理時間の関係は以下のとおりである。unit=50で最速であるが、それでも1分かかっている。

  • 1 : 生成(全タスク完了まで)
  • 2 : 取得(PDFマージ処理からクライアントで文書を開くまで)
    • unit=70,total=700 (タスク件数 : 10件)
      1. 1分20秒,1分10秒
      2. 57秒。42秒
    • unit=60,total=700 (タスク件数 : 12件)
      1. 1分20秒。
      2. 1分。
    • unit=50,total=700 (タスク件数 : 14件)
      1. 1分2秒,1分15秒。
      2. 1分4秒,47秒。
    • unit=40,total=700 (タスク件数 : 18件)
      1. 1分15秒。
      2. 54秒。
    • unit=30,total=700 (タスク件数 : 24件)
      1. 1分20秒,1分35秒,1分14秒。
      2. 50秒,43秒。
    • unit=10,total=700 (タスク件数 : 70件)
      1. 1分52秒,1分33秒。
      2. 52秒,50秒。


詳細分析
  • どれも以下のWarningが多発

    Request was aborted after waiting too long to attempt to service your request. Most likely, this indicates that you have reached your simultaneous active request limit. This is almost always due to excessively high latency in your app. Please see http://code.google.com/appengine/docs/quotas.html for more details.

    TaskQueueのワーカー側から出力されている様子。
    2個目の終了ログの前に上記ログが複数出力されているケースが多い。
    処理の合間にもちらほら出力される。
    Maximum RateとBucket Sizeを下げる(どちらも5に設定)と、abort発生頻度が下がる。
  • 1はインスタンス2個、2,3はインスタンス3個
  • 全ての処理が終了するのは、どれも15分程度で変わらない。
  • あるインスタンスが最初に処理を行う場合、2回目以降と比較して処理時間が長い。
  • 一度に4個程度並行処理を行っている。そのうち3個は同じJVMで、1個だけ別のJVMというパターンが多い。
  • また、クライアントから連続してリクエストを投げた際(今回のテストでは4秒間隔)、エラーが時々発生する。1回目のリクエストは成功し、2回目のリクエストでエラーが発生することが多い。


 ボトルネックがどこにあるかわからなかったため、下図のように、Memcacheにデータを格納することで、ネットワークI/Oをなくすケースについても計ってみた。




  • TaskQueue処理データをMemcacheに登録するテスト

  • 1Taskあたり700ページ作成すると、dispatcherでの処理で以下のエラーが発生してタイムアウトする。240~300ページあたりで、エラーになったり正常終了したりした。
    • jp.reflexworks.pdfservice.PdfDispatcher doGet: com.google.apphosting.api.DeadlineExceededException: This request (789cb38865d5e3d2) started at 2009/10/27 06:10:31.448 UTC and was still executing at 2009/10/27 06:10:59.946 UTC.・・・

  • 8400ページ出力処理(以下、1Taskあたり240ページ作成とする)
    TaskQueueのMaximum Rate : 5.0/s、Bucket Size : 5.0に設定。
    1. unit=20,total=240を35回リクエスト(クライアントからのリクエスト間隔:4秒)
      • タスク数は、12×35=420個。
      • 1タスクにつき処理時間が6~11秒かかる。
  • 実行結果
    • dispatcherへの1回目のリクエストはエラーになった。2回目から正常終了。
    • やはり"Request was aborted ..."のWarningが所々出力されている。(4回正常、1回Warningログというパターンが多い)
    • dispatcherの応答時間が長い。
      • Memcacheを使わない場合は2~7秒、今回は9~10秒。
    • 1タスクあたりの処理時間は、Memcacheを使わない場合とあまり変わらない。
    • 総処理時間は19分。(エラー分の2回を追加でリクエストした。)
    • VM起動数は4個。
    • リクエストを投げ終わる時間は10分。


  • この結果でわかることは、

     1)リクエストを投げ終わる時間は10分だが、リクエストを投げてから、すぐにTaskが起動されるため、Task処理開始から終了までにかかる時間は19分である
     2)1タスクの処理時間に変化がなかったことで、一括検索+Memcaheに格納のメリットは見い出せなかった
     3)1回目のリクエストでエラーとなるケースが多い(この件については、次回の記事にて、詳細に分析した結果を報告する)

    ちなみに、TaskQueueを使わないで、すべてdispacheで処理したケース(つまり、多重度1)では、

  • 8400ページ出力処理、クライアントからのリクエスト間隔を0秒にしてテスト
    • リクエストを投げ終わる時間は7分。
    • dispatcherへのリクエストは、35回中2回エラー
    • 総処理時間は19分。(エラー分の2回を追加でリクエストした。)


  • また、MemcacheのI/O回数を減らし、20件分をまとめてgetしたケースでは、

  • Memcacheへのデータ登録単位を1件からunit(20)件にし、Task側ではMemcacheからgetする回数を1回にしてテスト
    (Memcacheに、1個のキーに対しデータunit(20)件分のjson文字列を格納する。)
    • データ登録単位1件の場合と変わりなし。


<続く>

<追記>
 ・ 軽いWorkerタスクを別途用意して連続して負荷をかけることでVMは16程度起動することがわかっている。今度はServletContext+UUIDで負荷分散状況を調べてみた
 ・ WdWeaverさんの実験 スケールアウトの真実

木曜日, 11月 19, 2009

【Google App Engine】 疎結合とバージョニングについて このエントリーを含むはてなブックマーク


ぶいてく流スケーラブル設計3大要素

 私たちがスケーラブルなアプリを作る際に重要だと考えている要素は、疎結合、バージョニング、非同期の3つである。
 今回は、疎結合、特にバージョニングについて詳しく述べる。非同期(TaskQueue)は次回の予定。

疎結合

 GAEといったスケーラブルなプラットフォームを利用することで、単純なWebアプリでもスケーラビリティを得られるわけだが、さらにそれをRESTfulなWebサービスにすることで、より柔軟なスケーラビリティを享受できる。マッシュアップアプリがいい例で、ワンソース・マルチビューを実現できる。それは、この記事や、実装例で示してきたとおりである。これらはReflexやReflexGaeフレームワークにより、EntityからJSONやXML等に変換することで実現している。

バージョニング

この記事の最後の一文は、なんのこっちゃ!?と思った方も多いと思う。

 「ただし、これをやるには、レコードをRevision番号で管理するという前提で考える必要があり、更新において新しいRevision番号でレコードを追加する必要がある。」

 要はバージョニングのことなのだが、その目的は、楽観的排他によるスケーラビリティの向上と、履歴管理によるトレーサビリティの向上の2つである。削除したデータも保存しておくことでUndo機能もつけることができる。
 楽観的排他はロックを起こさないのでスケーラビリティ向上につながる。つまり、更新の際にRevison番号を比較することで不整合を起こしていないかチェックする仕組みで、それをアプリレベルで実装しようというものである。
 HTML5のDataStorageのように、疎結合、非同期を前提とした「いまどき」のアプリでは、ブラウザで何かアクションを起こしても、すぐにはサーバに問い合わせにはいかない。そうなると不整合を起こす可能性も大きいわけだが、レコードがバージョニングされてさえいれば、整合性を解決することはそれほど難しくない。Revison番号が同じレコードは常に同じ内容であることが重要で、また、サーバのDatastoreやMemcache、あるいはクライアントのStorageなど、どこにあろうと同じものとして扱われることになる。もし同じRevisionで異なる情報があったとしたらDatastoreの内容を正とすることで整合性は保たれる。したがって、クライアントに存在する情報とサーバにある情報は時間差こそあるものの、全体的に見ると常に整合性が取れた状態とみなすことができる。(これが私が理解している、Eventually Consistencyの概念だ)
 というわけなので、オフライン時はクライアントのStorageを読みこめばいいし、オンラインでもMemcacheにデータが残っていればそれを返せばいい。そのとき最新のRevisionがDatastoreにあったとしても、神経質にならずに、Memcacheのタイムアウトまでは古い情報であっても返しちゃえばいいのだ。もちろん、在庫引当や座席予約など、このやり方が通用しない要件はあるが、ほとんどは「遅延」を許容できるアプリと思うので、このように実装することでスケーラブルになることは間違いないと思われる。(そういえば、DatastoreのIndex作成も遅延が起きるような・・appengineはRead Committed相当だがcommit()には2つのマイルストーンがあることを忘れてはいけない
 関連として、レコードにシーケンス番号をつけることも挙げておく。これは、Pagingや件数管理のために使用する。シーケンス番号をつけることで、最新レコードを取得できるようになる。また、大量のレコードを分割してTaskQueueで並列処理させたい場合には、シーケンス番号で範囲指定を行える。
 件数管理は、GAEは件数を数えるのが苦手で数十万件以上になるとタイムアウトを起こして検索できなくなるので、最新レコードで常に管理しておこうというアイデアである。
 最新レコードのQueryは件数に影響されずにそこそこ高速に検索できる(約0.6s)ことを利用している。
 削除済みデータなど、不必要に思える(むしろ消すべきと思える)ものを残して履歴管理する第一の理由は、データ量がパフォーマンスに影響しないということ。その点は特に強調したい。

 以下に、バージョニングのロジックを示す。大福帳管理モデルというのは私が勝手につけた名前で、あまり人に吹聴するとイタイ目にあうかも。multidimensionalの方が近い意味だと思う。
 
大福帳管理モデル(別名:カーボンコピーモデル 英語名:multidimensional)

  • 大福帳管理モデル(multidimensional,カーボンコピーモデル)(勝手に命名)
    • 楽観的排他でスケーラビリティ向上
    • 履歴管理によるトレーサビリティ向上、Undo機能
    • 件数管理によるパフォーマンス向上
    • すべてのレコードは、Key+Revison番号で管理される。
      • Key(※)+Revisonが同じであれば、同じ内容のレコードであるとみなす。ただし、削除は例外扱い。(※このKeyはアプリのキーでありDatastoreのKeyではない。詳細は、ココのKeyに何を入れるべきかを参照のこと)
    • 追加では、レコードが存在しなければ、Revisonを0にセットして追加する。レコードが存在していれば、エラーか強制登録を実行する。強制登録は、削 除フラグが立っていることを確認して、revisonを+1したうえで追加する。このとき削除フラグが立っていなければエラーとする。この処理は GET/PUTによるトランザクションで実行されなければならない。
      •  ただし、強制登録では一度削除されたものと同じKeyでの再登録をすることになるので、それを許すかどうかはアプリの要件による。(Forceオプションか何かで区別すべき)
    • 更新では、現在のレコードがあることを確認して、更新レコードのRevisonと同じであるかチェックする。NGであれば楽観的排他エラーとする。OKで あればRevisonを+1して更新すると同時に現在のレコードには削除フラグを立てる。この処理はGET/PUTによるトランザクションで実行されなけ ればならない。また、現在のレコードに削除フラグが立っていてもエラーとする。(これで既に削除されているときの楽観的排他ができる)
    • 検索ではRevison番号は更新されない
    • 削除は論理削除であり実際には消さない。削除フラグに削除された日時を入れて更新することで削除されていることを示す。Revison番号は変わらない。
      •  削除レコードを追加するという考え方ではなく、削除フラグに日時を入れることで対応する。その理由は、古いRevisonレコードに削除フラグが立たないと検索対象に含まれてしまって不都合だから。また、削除フラグのみの更新であれば、元の情報が消されることもない。
  • 情報の鮮度(強弱)
    • 同一KeyのレコードではRevisonの大きいものが最新となるが、同一Revisonで削除フラグが立っているものがある場合には、それが最新となる。
      • Rev.3(削除なし)>Rev.2(削除あり)>Rev.2(削除なし)>Rev.1(削除ありなし)


大福帳管理モデル 更新のサンプルとパフォーマンス

 以下にサンプルコードを示す。これはカウンタをもつEntityGroupを構成するタイプで、最新レコードに件数をもつタイプではない。

 拙作のReflexGaeを利用している。

 ReflexGaeの特長は、JDOのEntityをLow Level APIを使ってGET/PUTできるところ。なんのこっちゃ!?と思うかもしれないが、要はJDOがヘタレなのでLow Level APIでラッパーを作った。その際、EntityはJDOと完全に互換性をもたせるようにした。(JDOがダメダメと言い出したのはたぶん私が最初だと思うが、概念というか、Entityの構造まで否定したつもりはなく、実はこれはこれで気に入っていたりする)

 ReflexGaeには、KeyUtils、EntityConverter、FieldMapperというものがあり、以下のようなことができる。
  1.KeyUtilsを使って、EntityGroupを作成する
  2.EntityConverter().convert(RECORD_KIND_CLASS, entity); で、EntityクラスからJDOクラスに変換する
  3.fieldMapper.setValue(current, target); で、JDOの@Persistent項目で更新があるものだけをセットする

 Entity変換ではReflectionを使っているが高速である。

速度比較
  • 登録(登録 + カウンタ更新・・EntityGroup更新)
    • JDO : 0.251s
    • ReflexGAE : 0.163s

  • 更新(1レコード登録 + 1レコード(旧Revision)・・同一Kind更新)
    • JDO : 2.790s
    • ReflexGAE : 0.250s

検索ではJDOは1万件以上でタイムアウトになるので単純に比較できないが、ReflexGaeは次の通り。
  • keyによるget : 0.030s
  • query : 0.622s (92300件対象にSort条件をつけて1件取得)

 ちなみに、Memcacheへの登録参照はココによれば、0.015s程度とのこと。


水曜日, 11月 18, 2009

【Google App Engine】 TaskQueueはスケールしない!?2 このエントリーを含むはてなブックマーク


 GAEのスケーラビリティ、特にTaskQueueについては、いろいろ調査してわかってきたので、このあたりで報告したいと思う。GAE Night#3で話すネタと若干かぶるかもしれないが、全部は時間の関係で話せないと思うので、あらかじめこのBlogに晒しておくことにする。もちろん、被らないネタもある。(今回はさわりだけで、次回以降に詳細を書く。複数回に分けて書くつもり)

GAEのスケーラビリティについて

「リニアにスケールするように作れる」からこそのGoogle App Engineより抜粋

その中でも一番収穫として大きいのは、「Google App Engineを使えば、リニアにスケールするサービスを作ることが可能」だということが実感できたこと。(中略)・・・
ユーザーの数が100万人から1000万人に増えた時には、単にマシンの台数を10倍にすれば良いという話ではなく、それに応じてデータベースを新たにクラスタリングさせたり、スケーラビリティの確保のために新たなデータキャッシュの仕組みを導入したり、ということがどうしても必要になる。その結果、マシンの数が10倍ではなく20倍必要になったり、スケーラビリティのことだけを専任で担当するエンジニアが何人も必要になったりする。


 私もGAEがリニアにスケールすることに感動して、Scaleするかどうか、それが問題だとか、いろいろいってきた一人である。しかし、皆さんも経験しているとおり、GAEといえどもリニアにスケールするアプリを作るのは一筋縄にはいかない。単純なアプリでデータ件数も少ないのであれば、AppServerのインスタンスが勝手に増えてスケールしていくのだが、アプリがちょっと複雑になったり、データが多くなってくると、すぐに30秒ルールの壁にぶちあたってスケールしなくなる。スケーラビリティは、リクエストの数というより、特に大量のデータ処理をやろうとしたときに問題となるようである。

並列処理とTaskQueue

 30秒ルールがあるからスケールしない理由と考えるのは本末転倒である。そもそも、30秒以内にレスポンスを返せないと使い勝手が悪くなり、ユーザビリティ要件を満たせなくなる。なので、そこはアプリの方でなんとか頑張らんといけないのであるが、GAEのAppServerのCPU処理能力は低いので、なかなか思ったように捌けないのが現実である。
 なんとか素早くレスポンスするために、並列処理させるのも一案である。TaskQueueを使えば、大量のデータを分割して複数のマシンで同時に実行できる。簡単にいえば、1台のサーバで10分かかる処理は、20台のサーバであれば30秒で処理できるようになる。無尽蔵にあるサーバに対して、複数のTaskで並列処理させる。それがクラウドの醍醐味であり、本当の意味でリニアにスケールする仕組みであるといえる。しかも、20台を30秒使うのは、1台を10分使うのと同じCPU利用時間なので、同じ利用料で済む。これが本当ならば、まるで夢みたいな話である。
 だが残念なことに、現在のところはまだ夢のようである。Googleは、無尽蔵にあるサーバを無尽蔵には使わせてはくれない。私たちの検証の結果では、TaskQueueの並行実行には限界があり、10/sという設定をしているにも関わらず、インスタンスの起動は同時に4つまでしか行われなかった。MLにも同じようなことが報告されている。


Hi there,

Last night I experimented with task queues to see what level of
concurrency I could achieve when running on the live environment.

Summary of the test app:
- Bulk load 30,000 entities of a given type (3 properties / entity
object).
- Command line job I ran from my PC that hit an URL to queue the
entries
- This program was multi-threaded so I could simulate a bit of
load (10 concurrent threads)
- Queueing URL created a task queue entry within the same app
- 2nd URL handled the task queue request and stored entity to the
Datastore

I watched the task queue dashboard for a few minutes and observed a
few things:
- Enqueue rate quickly outpaced dequeue rate
- I was enqueing at about 12 requests / second, but dequeuing at
4 requests / second
- GAE did not appear to increase the dequeue rate over time in
response to my queue depth

Result: It took a very long time to dequeue 30,000 tasks (over 2
hours). It seemed that GAE was running one instance of my app.

Expected: Much higher throughput.

Is this expected behavior? It seems that given the 30 second request
limit that task queues are an important way to increase throughput
(ala MapReduce). But the "swarm" of app instances never seemed to
arrive.

thanks

-- James

aha! I missed that. I wonder if "task invocations/second" means
"dequeues/second".

If it means dequeues/second then in theory you could write a request
handler that burns through a queue of work items in the datastore, re-
queueing itself and exiting after 25 seconds and achieve 250 CPU
seconds/second of concurrency.

Is that crazy talk?

I hope these limits go up when Tasks Queues exits beta. Google is
selling us computer time but is setting some fairly low limits on what
we're allowed to buy. 10 cores of 1.2ghz CPU time is roughly
equivalent to a modern 4 core desktop machine right?


 そりゃ、1つの検索リクエストに20台のサーバを使われたら、Googleといえども、たまったもんじゃないのかもしれない。いずれにしても、TaskQueueはまだLabs releaseであり、今は実験中であって、使われ方とサーバへのインパクトを見ながら徐々に増やしていくのだろう。実際にTask Queue Quota Increasesにもあるように、QuotaやTotal execution rateも上がってきているし、今後はもっと改善されていくと思われる。

<続く>

<追記>
 ・ 軽いWorkerタスクを別途用意して連続して負荷をかけることでVMは16程度起動することがわかっている。今度はServletContext+UUIDで負荷分散状況を調べてみた
 ・ WdWeaverさんの実験 スケールアウトの真実

月曜日, 11月 16, 2009

【Google App Engine】 レコードのシーケンス番号をカウンタを使わずにつける このエントリーを含むはてなブックマーク


カウンタとレコードのシーケンス番号

 レコードにシーケンス番号(連番)をつけると、全体の件数を取得できたり、Pagingできたりするので、いろいろと好都合である。Pagingだけであれば、わざわざシーケンス番号を付ける必要はないが、大量のレコードを分割してTaskQueueで並列処理させたい場合には、シーケンス番号で範囲指定を行うとよい。(全体の件数はStatics APIでも取得できる)

 シーケンス番号を付けるには、カウンタを管理するEntityを用意して、挿入のたびにインクリメントする方法が一般的だと思うが、これだと前記事で述べたように、カウンタのEntityとデータのEntityをEntityGroupとして括ってしまうことが大きなボトルネックとなってしまう。しかし、トランザクションで括らないと挿入に失敗することがあるため歯抜けのシーケンス番号となってしまう。
 そこで、カウンタのEntityを使わないで、うまくシーケンス番号をつける方法がないものか考えてみた。
 
EntityGroupを使わないでシーケンス番号をつける方法


 すぐに思いつくのが、既存データのシーケンス番号の最大値を取得して、それに+1したものを、新規に追加するレコード番号としてセットする方法。

 まず、シーケンス番号をidとすると、
 query.addSort("id", Query.SortDirection.DESCENDING);


 とし、最初の1件を取得することでレコードの最大値を取得する。(これにかかる実行時間は10万件レコードでも約0.6秒と高速である。)そして、この値に+1したものを新規レコードのidとしてセットする。

 これは一見うまくいくように思えるが、取得して更新する間に他の人が追加してしまう場合があるので意味がない。トランザクションで括ればいいと思うかもしれないが、やっかいなことに、queryはトランザクションに参加することができないので、一旦、最大値をqueryで取得しておいて、ユニーク制限を行ったPUTを実行することになる。これは、少々冗長かもしれないが、EntityGroupを使う場合に比べ、排他対象がKindからレコードに狭まるので、その分並行実行可能になって高速になると思われる。 ユニーク制限を行ったPUTについては、詳しくは、ひがさんのBlog、App Engineのユニーク制限を正しく理解しよう)を参照してもらいたい。


レコード挿入時
 1.queryでシーケンス番号idの最大値を得る
 2.+1したidをセットした新規レコードを作成してputUniqueValue()する
 3.エラーで返ってきた場合、さらにidを+1してputUniqueValue()を再実行

putUniqueValue(String uniqueIndexName, String value)
 1.valueを元にKeyを生成
 2.トランザクション開始
 3.生成したKeyでGET。既に存在していればエラーで返す。
 4.もしなかったらPUTしてコミット


 これは、どんなにデータが増えても、Query(0.62s)とKeyによるGET(0.03s)の2回の検索および、PUT(0.25s)でシーケンス番号を作成できる。しかし、リクエストが増えてしまうと、コンテンションが多発してリトライ回数が増えるため、もう少し時間はかかる。それは、QueryからPUTまでの0.62s+0.03s+0.25s)の間にどれだけのリクエストが入ってくるかによる。リトライは2回目以降はQueryはしないので、GET(0.03s)を繰り返すことになる。また、GETで成功してもPUTまでの間に他によって更新されていると、ConcurrentModificationExceptionが発生してリトライとなるが、よほどのリクエストがこない限り大丈夫だと思う。たぶん。ちなみに、件数が増えてもQueryが速いのはPropertyIndexのおかげである。

さらなる応用、最新レコードに件数を格納する

 履歴をレコードとして持つ場合、idの最大値やDatastore statisticsのcountだと履歴分も全て件数に含まれてしまうので注意が必要である。また論理削除する場合も同様に削除データが件数に含まれる。履歴を持つ、または論理削除する場合では、条件を指定してKeyの個数を検索する方法もアリだが、10万件で約17秒かかるので、十数万件になると30秒ルール(あるいはTaskQueueの10秒ルール)にひっかかって使えなくなってしまうだろう。
 これには、以下のように最新のレコードに件数を格納することで、対応することができる。(もちろん、カウンタEntityを別途用意してもよい)

レコード挿入時に最新の件数を格納する
 1.queryでシーケンス番号idの最大値を得る
 2.+1したidをセットした新規レコードを作成する。その際、新規レコードのcountプロパティの値を更新する。(追加であれば+1、削除であれば-1、更新であれば何もしない)
 3.putUniqueValue()する
 4.エラーで返ってきた場合、queryを再実行してidやcountを更新してputUniqueValue()を再実行


 ただし、これをやるには、レコードをRevision番号で管理するという前提で考える必要があり、更新において新しいRevision番号でレコードを追加する必要がある。

詳しくは、疎結合とバージョニングについてを参照のこと。

金曜日, 10月 30, 2009

【Google App Engine】 Keyとカウンタは別々に考えるといいかも このエントリーを含むはてなブックマーク



この記事書いているあいだにこんなニュースを見つけた。これはすごいや。
100万PV/日のmixiモバイルアプリをGoogle App Engineで実装した@gclue_akira氏に直撃インタビュー

さて、本題

不可能と思い込んでいたことができることもある

 何度かイタい目に遭うことで、またどうせ出来ないだろうと思い込んでしまうことを学習性無力感という。長期間にわたって鎖に繋がれたままの犬は、鎖を外してもしばらくは逃げないそうである。GAEはもともと情報が少ないのと、嵌りどころ満載なこともあって、学習性無力感に陥ることがしばしばある。だから、一人悶々とやるより、Blogに晒したり、GAE Nightなどの集まりに出向くなどして、情報共有に努めるのがよろしいかと思う。そこで重要なのは、ありのままの事実を晒すということ。現時点で正しい答えを識っているものは皆無に等しいのだから、誰かに正しい答えを訊くよりかは、自分で実際にやってみて、その結果だけを信じるというスタンスが重要な気がしている。また、思い込みによる間違った情報でも、公開することがきっかけとなり、いろいろと議論が深まって、正しい結論へと導き出せるかもしれない。というわけで、今度のGAE Nightへは、そういうスタンスで乗り込むつもりなので、そこんとこよろしく。(間違った情報を晒すつもりはないが言い訳はさせてね・・)
 ところで、当Blogで公開したもののうち、私自身が「学習性無力感」によって不可能と思っていたことがいくつかある。例えば、ココで紹介した前方一致検索は、Low Level APIの2つのFilterを使って、次のように書ける。このように、1つの項目に2つのFilterを指定することが可能で、Bigtable的には、これはRange Scanと解釈されるようである。

 String value = "コーヒー";
 Query query = new Query("Product");
 query.addFilter("product_name",FilterOperator.GREATER_THAN_OR_EQUAL,value);
 query.addFilter("product_name",FilterOperator.LESS_THAN,value + "\ufffd"); // "\ufffd"はUNICODEの最大値


 前記事では、GREATER_THAN_OR_EQUALの一つしか使っていなかったが、LESS_THANを同時に指定することで、”コーヒー”で始まっていないものを省くことができる。これまでaddFilterは1つだけが指定できると思い込んでいたのだが、内部の動きを知って、なるほどと納得した次第である。

1000件までしか検索できないことはない件

 ココでも述べているが、以前はたしかに1000件しか検索できなかった。正確にいうと、JDOでは1000件以上検索できたが、Low Level APIだと1000件が限界だった。ところが、SDK1.2.5では、FetchOptionsのlimitの値を増やして実行することで、2000件以上検索できることがわかった。しかし、30秒ルールの壁があるため、実際には2000件程度が表示の限界だろうと思われる。私たちのアプリでは、2468件の時点で30秒以上となりエラーとなった。(まあ、鎖から開放された犬が全速力で逃げようとして、30M付近の透明の壁にぶち当たったといった感じかな)
 また、Keyによる検索や更新については制限があり、Quotas_and_Limitsによれば、Batch Getは最大1000件まで可能で、Batch Put/Deleteでは最大500件まで可能である。また、countEntities()で取得できる件数も最大1000件であるが、datastore statistics APIを使うと1000件以上カウントできるとのこと(MLより)

The query restrictions are an artifact of the way App Engine's datastore is constructed, which makes certain operations (e.g. queries and reads) very fast and scalable but does limit the types of queries you can make, though you can typically get around these restrictions by re-thinking your model a bit.

We are working on adding built-in cursor support for easier paging through entities and have just added a datastore statistics API for, among other things, getting the total entity count, even if it exceeds 1,000. More details here:

http://code.google.com/appengine/docs/java/datastore/stats.html

And we also have a data export utility included with the SDK to make it easier for you to back up or even move off of App Engine should you choose to, and we're continuing to look at ways of making App Engine, particularly the datastore component, easier to use.

http://code.google.com/appengine/docs/python/tools/uploadingdata.html#Downloading_Data_from_App_Engine


使用例


 Query query = new Query("__Stat_Kind__");
 query.addFilter("kind_name", Query.FilterOperator.EQUAL, "ここにkind名を指定");
 Entity stat = datastore.prepare(query).asSingleEntity();
 Long count = (Long)stat.getProperty("count");


 また、以下のようにすれば、既存のAPIでもcountを取得できる。
 これは、Keyだけを対象にした検索であり実際の値までは取りにいかない。また、Keyはメモリー上のSSTableに存在するため非常に高速と思われる。(これはこれで非常におもしろいアイデアである)
 でもGoogleの人によると、「実際に計ったわけではないがStaticsの方が速いだろう」とのこと。その理由は、全件読むのではなくKindごと管理している件数を返すだけだから。MLのひがさんのポストより


Low level API:
 DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
 int count = ds.prepare(new Query("your kind").setKeysOnly()).asList(FetchOptions.Builder.withOffset(0)).size();


パフォーマンス

  • 92300件のデータをカウントするのにかかる時間を計測
    • Datastore Statistics : 0.553秒
    • 自前カウンタ(base)をkeyでget : 0.030秒
    • prepare.asList().size() : 17.338秒
    • prepare.countEntities()は結果が1000件

カウンタの実装とボトルネックについて

 アプリが扱うデータは無限個が前提だが、30秒ルールや件数取得の問題があって、実際に無限個のデータを全部扱うのは難しい。なので、私たちは、各々のレコードにシーケンス番号をつけ、Pagingなどの部分的な処理を基本とすることで、これらの制約を回避するように工夫してきた。
 シーケンス番号は、レコードのプロパティに保持しており、また、レコードを生成する都度、カウンタを管理するEntityの値をインクリメントする仕組みである。こうすることで、Pagingの際に、どこからどこまで処理したかを特定できるし、全体の個数もわかるようになる。ただし、カウンタ更新とレコード挿入は1つのトランザクションとして実行しなければならず、これがとても重いのが難点であった。例えば、トランザクション処理させるためには、カウンタのEntityとデータのEntityをEntityGroupとして括らなければならないが、そうすると、挿入の都度、Kind全体がロックされることになるので、大きなボトルネックとなってしまう。元来、Datastore書込は読込の10倍以上遅い。さらにDatastoreのトランザクション処理も遅い。そのうえ、テーブルロックに近い設計をしてしまうのは、ありえないぐらい遅くなる。とはいえ、トランザクション処理を諦めるわけにはいかないので、書込に関しては遅くなるのはしょうがない。(せいぜい、Memcacheを駆使した遅延書込で改善できたらいいなあぐらいは考えてはいたが・・。まあ、無力感に満ちた犬ですな)
 でも、いろいろ実装してみると、実は、レコードにつけるシーケンス番号はいらないかもしれないということがわかってきた。たしかに、ユニークなKeyは必要だが、Paging処理であれば、keyだけあれば十分。また、前方一致検索の例のように、複数件ヒットした場合でも、Keyと「商品名」を連結した新たな項目を作ることで対応できる。
 問題は全体の件数をどう取得するかであるが、これは先ほどの例のようなQueryか、Statistics APIを使えば可能である。Keyについては、KeyRange allocateIds()の自動採番を利用する方法もある。どうしても連番が欲しい場合には、カウンタEntityを作成しても構わないが、レコードのKeyとしては使わず、EntityGroupも構成しない方がいいだろう。そうすれば、カウンタそのものをスケールさせることもできる。(例えば、カウンタEntityを複数もちランダムに選択したものをインクリメントする。そして、すべての合計値をシーケンス番号とする、といった具合に。そんなサンプルがGoogle I/Oで紹介されていた。)
 ちなみに、トランザクション処理をしないとどうなるかというと、レコードの更新失敗が起きたときに、カウンタEnitityだけがインクリメントされてしまい、いわゆる歯抜けの状態が起きてしまう。(ユニークな番号としては使える)これは実際に起こる可能性があるものと考えた方がよいが、補償トランザクションなどでデクリメントするような処理は絶対にやってはいけない。(その時点でカウンタEnitityが更新されている可能性があるため)

Keyに何を入れるべきか

 当初はシーケンス番号をKeyとしていたが、普通のプロパティに入れてもFetchできるので、これをわざわざKeyにする必要はない。また前述したように、そもそもシーケンス番号でなくてもPagingはできる。Keyにはユニークなものを入れさえすればいいのだが、allocateIds()などで作成した機械だけがわかる値を入れても、何かもったいない気がする。なので、KeyFactory createKey()を使って、アプリのKeyを入れるのがよいのではないかと思う。このように、Keyさえ作成できれば、allocateIds()で取得したときのように、Batch PUTも可能である。
 実際に私たちはこのような方法で、以下のような商品マスタ管理用のKindを作成した。ここでは、Shop_code+Product_code+Revisionといったように、アプリでユニークになるKeyを作成している。Revisionは、レコードが更新されるたびにインクリメントされる番号で、楽観的ロックに使っている。また、更新前のレコードは消さないでそのまま保存している。そうすることで履歴管理にもなる。Datastoreの容量は膨大にあるので、こういった大福帳のような感じの設計もありかなと考えている。
ただし、履歴をレコードとして持つ場合、Datastore statisticsのcountだと履歴分も全て件数に含まれてしまうので注意が必要。また論理削除する場合も同様に削除データが件数に含まれる。履歴を持つ、または論理削除する設計の場合で、カウンタ機能が必要な場合は自前のカウンタが必要な気がしている。



日曜日, 10月 18, 2009

【雑記】 そろそろMVCモデルについて一言いっておくか このエントリーを含むはてなブックマーク


 なーんて、MVCを語れるほどの知識はないのだが、琴線に触れてしまったので、私なりに言いたいことを言うことにする。
 本当は、こんな話より先に、先日参加したGAE Nightの話や、Winnyの金子さんが無罪になった話を書きたいのだけど、ココとか、ココとか、ココとか、ココとか、毎日毎日毎日毎日、MVCを語られると、何かいいたくて、もう我慢できなくなってしまった。(これはエンジニアの性なのか!?)
 中島さんのBlogのなかで最も釣られてしまうキーワードは「えせ」。これを使うということは、自分の考えだけが正しくて、他は間違いであるということを暗にいっているようなもの。多くの人はそれに反応してしまうから、感情論になって、あまりよい結論は見い出せなくなってしまっているんじゃなかろうか。中島さんの言っていることは概ね理解できるし、RESTfulな設計などは私の考えと被る部分もあって、ほぼ同意できるのだが、アーキテクチャーの議論でありがちな不毛な議論に発展しそうになっているところがちょっと悲しいところ。せっかくいいこといっているのにもったいない感じである。
 MVCモデルそのものを含めて、現時点では何が正しいかわからないわけで、むやみに「えせ」という言葉を使うべきではないと私は思うのだが・・・。
 ちなみに、私が考えているReflex設計は、ココココでもいっているように、そもそもオブジェクト設計をしないので、エセMVCモデルの筆頭みたいなものなのであるが、中島さんのいっている「核となるのはビジネスロジックを含んだModelの部分。そこをしっかりと実装し、内部構造を隠す粒度の荒いインターフェイスを定義し、外から何をされてもデータの整合性が壊れない様にすることは何よりも大切」という考えは同じである。
 エセMVCモデルでありながら、考えが同じってどういうこと?と思われると思うので、そのあたりを中心に話をまとめてみたい。
 あらかじめ言っておくと、この手の話は、本当はスルーするのが一番である。

混乱させている曖昧な言葉・・Model

 まず、曖昧な言葉の筆頭は、Modelである。Modelはビジネスロジックを指して使うこともあるが、O/Rマッパで作成したデータを扱うオブジェクトを意味することもある。後者の意味で使う人が多いので、ドメインドリブンな人から「けしからん!」とよく怒られる。
 何事も曖昧なまま強引に話を進めると酷い目にあうのが常なので、まずはModelという言葉の意味の定義が重要だ。私が好きなModelの定義は、先の記事にも書いてあるとおり、「モデリングの成果物」というもの。つまり、オブジェクトを設計していくことをモデリングいい、その成果物であるオブジェクト(=データと振る舞いをもつもの)がModelなのである。モデリングはドメイン、つまり問題領域全般にわたるので、おのずとビジネスロジックのすべてがModelに含まれることになる。なので、本来、Modelには2面性があって、ビジネスロジックを指す意味で使われることもあるし、データという意味で使っても構わないものなのである。

 また、MVCモデルはオブジェクト設計を前提にしたものであるが、多くの人はロバストネス分析をやって分析モデル(BCEモデル)を作ることを省略している。これらは脳内でやっている人がほとんどだろう。BCEモデルは、抽象クラスの「操作」と「属性」により、データはEntity、制御はControlといったように整理できる単純明快なモデルなのだが、これを設計モデルのMVCモデルに落とすときに、データであるEntityと制御であるControlの一部がModelになるため、これがモデラーのセンスを必要とする非常に難しい作業となってしまっている。
 その様子は、以下の図がわかりやすい。(参照:UMLモデリングのノウハウ、最後の秘訣)
 このとき、Modelに記述すべきビジネスロジックが少ないと、「エセMVC」とか、「ドメインモデル貧血症」とかいわれるはめになるわけだ。



 また、BCEモデルにおいては、EntityからEntityを呼ぶことはできないという制約があるが、ModelにはControlの一部が入るため、ModelからModelの呼び出しが可能になる。DDDではそれを前提に設計するため、逆にControllerの責務が曖昧になっている印象を受ける。
 SCAなどのコンポーネントアーキテクチャーではもっと進んでいて、各コンポーネントはreferenceと、serviceという口をもち、それぞれを結合できるようになっている。また、それらを内包した大きなコンポーネントを作ることができる。このアーキテクチャーにはControlerは存在しない。敢えて言うならマッシュアップサービスということになるのだろう。



混乱させている曖昧な言葉・・ビジネスロジック

 次に曖昧だと思う言葉はビジネスロジックである。ビジネスロジック=すべての処理ロジック(あるいはプログラムコード)というふうに理解すると、Controlに書くべきFlowや、Viewに書くべきJavaScriptも含まれることになる。これらは、それぞれのレイヤに存在すべきビジネスロジックである。ちなみに、Modelに書くべきビジネスロジックのことを、私はドメインロジックと呼んでいる。もちろん、ドメイン(問題領域)のオブジェクトに対して、「外から何をされてもデータの整合性が壊れない様にすること」の処理は当然記述することになる。
 FlowやJavaScriptのStep数が大きくなることがあっても、設計上は関係ないはずなのだが、ドメインロジック以外の部分が大きくなることを嫌がる傾向にあるようだ。
 MVCモデルでは、ドメインのすべてをModelに含めることが基本なので、すべてのビジネスロジックまでも、Modelの中に記述しなきゃならないのではないか、という発想をしてしまう。こんな窮屈な発想では、RESTfulで疎結合な設計に合わなくなると思うし、FlowやJavaScriptの利点を軽視してしまうことになって、マッシュアップやHTML5のような「いまどき」のアプリにも合わなくなってしまうと思う。

 前述したDDDやSCAで何がいいたかったかというと、ドメインにビジネスロジックを集約させることにこだわるのであれば、DDDやSCAぐらいの思い切った発想で設計すべきであろう。これに躊躇してしまうということは、後述のビジネスロジックが本当は各レイヤで分断されていて、それを良しとしているからではないか。ビジネスロジックをModelに集約すべきといったって、実際に存在する各レイヤのロジックを無視するわけにはいかず、結局は中途半端になってしまうのが現状。このあたりがModel集約化の話に私が素直に同意できない一番の理由である。

 ということで考えたのが、ココにも書いているような、各レイヤにビジネスロジックをもたせるという設計手法。ただし、ビジネスロジックは、揮発性※であるということが条件となる。また、インターフェース設計は重要で、基本的にCRUDの4つだけを定義して、パラメータはentityそのものとするのがミソ。(※ 揮発性とは永続化しないこと)

entity = blogic(entity);




先日作った請求書アプリを例に具体的に説明しよう。まず、各項目と金額はドメインであり、Entityを検索すれば正しく得ることができる。では、金額とともに表示する「¥」や「,」はどのレイヤのロジックで編集すべきだろうか?また、消費税額計算や合計はどうすればいいだろうか。私のおすすめは、View、もしくは、Controllerのレイヤで記述すること。これらは、上記のblogic関数を使って表現できるもので、わざわざModelでやらなくても各レイヤでやればよいと思う。それは、データの整合性はModelが保証していて、揮発性の上記のblogic関数を使う限りにおいては、整合性を壊すことは決してないからだ。
 
 実際にいくつかのプロジェクトでこれを実践してみたのだが、パフォーマンスや生産性の面で非常に有効という結果を得ることが出来た。パフォーマンスは、高速化プロジェクト その2 で述べているとおりである。リモートにあるModelではなく、ブラウザというローカル環境で実行する部分が増えるわけだから当然といえば当然である。生産性の面では、以下のように、それぞれのレイヤで分業できるという利点がある。このおかげで非常に短納期に開発できた。



ドメインサービスとしてのReflex BDB

 Reflexの新3層アーキテクチャーにおけるドメインは、EntityのCRUD操作以外の何ものでもないのだが、登録変更削除においては、「外から何をされてもデータの整合性が壊れない様にすること」がもちろん保証されなければならないし、検索においては、単なるPKによる検索だけではない点は補足すべきだろう。例えば、PK以外のKeyによる検索、大小比較、全文検索、Pagingといった検索などについては、要求仕様に応じて、それなりにドメインロジックを実装しなければならない。
 まだ公表する段階ではないのだが、Private CloudのソリューションであるReflex BDBの次のバージョンにおいて、単なるKey/Valueの検索ではなく、ドメインサービスとして機能するようなものを作っている。内部的にはBDBなのでKey/Valueではあるが、GAEのProperty Indexのような仕組みを追加することで、汎用的なKeyによる検索などができるようにするつもり。ちなみに、これはConsistent Hashと伝染プロトコルを使ったスケールアウトアーキテクチャーとなっており、Dynamoのようにそれぞれのノードを柔軟に追加削除できる。(予定)
 また、各ノードのアプリはOSGiで管理されており、ドメインに応じたユーザアプリの開発と配布が容易にできるようになっている。(かもしれない)
 ここで強調したいのは、ドメインサービスとすることで、RDBやKey/Valueといった下位のデータ層を隠蔽しているという点。重要なのは結果だけとなるので、O/Rマッピングとか諸々のめんどくさい話は全部ドメインサービスのなかに閉じ込めることができる。
 Reflex BDBは、単なるKey/Value Storageからドメインサービスへの進化をめざしていく。



Key/ValueStorageではなく、ドメインサービスを提供する
・ 単なるPKによるGET/PUTだけでなく、様々なKeyによるRESTfulなCRUD APIを提供
・ <>比較、Range、前方一致検索、全文検索、Pagingなど




(※ 2010/12現在、Reflex BDBは、Reflex Tagging ServiceのPrivate版として開発中。BDBではなくCasandraかHBASEになる予定。GAE版はこちら
2011/3、Reflex BDBは、Oracle Berkeley Edition版としてリリース予定。Tagging Serviceはデータ操作(Resource Operator)としてのI/Fが役割となる。)
2012/5 reflexworks で詳細を発表

やっぱりMVCモデルは進化する

 冒頭、この手の話は、本当はスルーするのが一番であるといったが、最後にそれについて補足する。
 私自身、長年にわたってオブジェクト設計やMVCなどを議論してきて、誰もが納得する解を見出すために、何百人月以上浪費してきた。しかし、コレ!というものは結局見つかっていない。(多くの支持を得ているMVCモデルでさえこの有様である)
 私以外にも、モデリングにコダワリをもつオブジェクト厨も多いと思う。誰もが納得する汎用的な完璧な解を見い出すために現在もなおモデリングを永遠と続けている輩もいる。しかし、完璧な正解はないと思うし、ある程度妥協して完成を見い出ださないと、永遠に時間ばかりが過ぎていく。人生なんてすぐに終わってしまうだろう。特に実際のプロジェクトでは納期が重要。設計に関しては、必要に応じて最適なアーキテクチャーを選択しながら柔軟に考えていけばよいと思う。Bugのない品質のよいものを作り上げるのが一番大事で、次に生産性とか、保守運用性とかの話がくるべき。
 Reflex設計が、そもそもオブジェクト設計をしないというのは、モデリングにかかる膨大な工数を削ることが第一の目的である。Entityだけに着目することで、モデラーの感性の入り込む余地を少なくすると同時に、誰でもサクサク設計できるようにしたい。そのための現実的な設計手法なのである。別に「エセMVC」とか、「貧血症」などと呼んでいただいて結構。でもこのおかげで、モデリングにコダワリをもつ輩も「穀潰し」じゃなくなるわけだから、利用する価値があるかもしれないだろう?

 ということで、MVCモデルは進化する、ってことでいいじゃん、と思う次第である。

<関連>
 MVCモデルは進化する
 Reflex Tagging Serviceについて話します

火曜日, 10月 13, 2009

【Google App Engine】 TaskQueueはスケールしない!? このエントリーを含むはてなブックマーク


 TaskQueueによって、何がどう改善されるかについて、Reflex iTextを使って具体的に調べてみたので報告したい。特に、パフォーマンスについて、先日の記事(ココ)と比較しながら説明する。

 やりたいことは単純で、大量のPDFをいかに短時間で生成するかである。前回のテストでは、クライアントから大量のリクエストを投げることでこれを実現しようとしたが、リトライ処理が多発してスケールしなかった。では、TaskQueueを使うとどうなるのだろうか。今回はそこにポイントを絞って調べてみた。

TaskQueue概要

 まず、TaskQueueであるが、これはWebHook型のキューシステムで、Task自体を通常のServletとして書くことができる。Googleのサンプルを見ればわかるように、QueueFactory.getDefaultQueue().add()を使って、URLとRequestParameterを与えるだけで、Worker Servletが呼ばれる仕組みになっている。このシンプルさはとても驚きである。
 ただし、WorkerのURLは、呼び出し元のServletと同じプロジェクトに配置しなければならない。(別プロジェクトに分けて、http://などをつけて実行すると、「java.lang.IllegalArgumentException: url must not contain a 'scheme' part - contains :http」というエラーが出る。)


/**
 * Task Queue worker servlet that offsets a counter by a delta.
 */
public class SimpleCounterWorker extends HttpServlet {
 public void doPost(HttpServletRequest req, HttpServletResponse resp)
  throws IOException {
  Counter.createOrIncrement(
    req.getParameter("name"),
    Integer.parseInt(req.getParameter("delta")));
 }
}



/**
 * Servlet that schedules a counter to be increased.
 */
public class SimpleCounterServlet extends HttpServlet {
 public void doPost(HttpServletRequest req, HttpServletResponse resp)
   throws IOException {

  QueueFactory.getDefaultQueue().add(
    TaskOptions.Builder
     .url("/workers/simplecounter")
     .param("name", "thecounter")
     .param("delta", "1"));

  resp.sendRedirect("/simplecounter.jsp");
 }
}


TaskQueueの制約について

 実装はとても簡単に見えるが、アプリケーション全体をうまく機能させるには、排他制御を考慮しながらMemcacheなどを使ってやりとりしなければならないなど結構大変だったりする。また、キューに追加できるタスクの総数やMemcache自体に格納できるサイズの制限などもあるので注意が必要である。

TaskQueueを使うに当たって関係すると思われる限界値は以下のとおり。

  • キューに追加できるタスクの総数が最大10万件/1日
  • Memcacheに一度に格納できるのは1MB。
  • Memcacheに登録できる総容量は10GB。
  • クライアントがダウンロードできるのは10MB。


 信頼性という意味では、特にMemcacheについては、いろいろ問題があるようである。そもそも、code.google.comに「アプリケーション側ではキャッシュの値が常に利用可能だと仮定すべきではありません。」とあるので、信頼してはいけない。
 したがって、sharding counterのように、同時に複数のTaskが参照更新するものは、消えている場合を想定して、リクエスターにcounterの初期化ロジックを入れることまで考慮しなければならないだろう。しかし、そうすると、同時に初期化するものが複数存在してしまう可能性があり、排他制御が機能しなくなる可能性もある。

<参考記事>
Task QueueはMapReduceの夢を見るか
Memcacheでスピンロックを実装してTask Queue処理結果を集約してみるテスト
Task Queue君とmemcache君、疑って正直すまんかった

TaskQueueを使った処理の流れ

 Reflex iTextを使ったパフォーマンステストでは、TaskQueueを使って以下のような処理を実行させている。

  • クライアントは1タスクあたりの処理ページ数(以下unit)と、総ページ数(以下total)を指定する。
  • dispatcherは、total / unit (余りは繰り上げ) で算出された数だけ、TaskQueueにpdfservice処理を登録する。
  • クライアントにkeyを返却して一旦終了。
  • pdfserviceはPDFを生成し、byte配列をMemcacheに保存する。(非同期)
  • クライアントはkeyをパラメータにして処理を要求する。
  • dispatcherはTaskが完了しているかチェックし、指定されたkeyの処理が全て完了していればMemcacheからPDFを取り出す。PDFマージ処理を行い、クライアントに返却する。



実行結果

今回のアプリにおいては、MemcacheにPUTできる最大のサイズ1Mb以内にするために、1Taskあたり70Pにする必要がある。(つまり、unit=70)また、1回にダウンロードできるサイズは10Mbであり、1PDFにつき700P以内にする必要がある。この処理において起動されるTaskは10本で、全てのタスクが終了するのに1分20秒かかった。

前回のアプリと比較するために、700ページを12リクエスト(合計8400ページ)処理させた場合について計測してみると、unit=70の場合(700/70*12=120タスク)で約15分かかった。

考察

 実行結果をみてもわかるように、前回の結果が約8千ページを4~5分であったのに対し、TaskQueueを使った今回の結果は約15分と、遅くて残念な結果となってしまった。前回のアプリのような大量にリクエストを投げる場合には、リクエスト数に応じてインスタンス数が増えるが、一方のTaskQueueを使った場合は、同じインスタンス内でのみTaskを起動していたのではないかと想像できる。10Taskで1分20秒で、120Taskで15分なので、ほぼリニアに処理時間が増加している計算である。マルチで実行している場合は、120Taskでも1分20秒で完了するはずだ。
 スケールしない(させない?)のは、同一プロジェクト内でしかWorkerをコールできない仕組みと何か関係があるかもしれない。

 しかしTaskQueueを使うといいこともある。大量にリクエストを投げる方法では、8千ページ前後が限界であったが、TaskQueueを使えば、時間はかかるものの、8千ページ以上のPDF生成処理を行うことができる。(上限はMemcacheの2Gbで、MAX約70万ページ)
 レスポンス速度重視であれば、TaskQueueの多用は禁物であるが、非同期処理で、いろいろな用途はあると思われる。 

<関連>
TaskQueueはスケールしない!?2
TaskQueueはスケールしない!?2

<追記>
 ・ 軽いWorkerタスクを別途用意して連続して負荷をかけることでVMは16程度起動することがわかっている。今度はServletContext+UUIDで負荷分散状況を調べてみた
 ・ WdWeaverさんの実験 スケールアウトの真実

水曜日, 9月 30, 2009

【Google App Engine】 Low level APIで前方一致検索およびPagingについて このエントリーを含むはてなブックマーク


 JDOが全然使えないことが明らかになるにつれ、最近ではLow level APIを使う方も多くなってきた。Low level APIは、シンプルで扱いやすく、パフォーマンスにも問題ないことがうけて支持されているのだろう。しかし、機能的には全然足りないので、自分たちで何とか解決しなければならない課題も多い。前方一致検索もその一つである。

Low level APIで前方一致かつPagingが難しい件

 前方一致検索は、JDOではcontent.startsWithを使えばできるようであるが、Low level APIでも、検索項目をソートすることで一応できることはできる。しかしPagingについては、2Page目以降を特定する方法が必要ななため、できないと思っていた。
 例えば、下図のように、”コーヒー”で前方一致検索した場合に、同じ商品名の”コーヒー010”が複数存在する可能性があるため、次ページにおいて、どのレコードから表示すればよいかわからない。この問題は、Low level APIでは、GREATER_THAN_OR_EQUALなどのInequality filterが、1つしか使えないという制約に起因するものである。(追記10/29:LESS_THANも使えることがわかっている。参照=>Keyとカウンタは別々に考えるといいかも
)そこで、「複雑なクエリのためのプロパティを仕込んでおく方法」を参考に、前方一致検索でかつPagingする方法を考えてみた。ただ、さらに安い順でソートしたり、全文検索などはできない。これらについては、TaskQueueを使って対応するしかないと考えている。
 また、ここでは、商品のデータ件数は無限にあるという前提で考えている。小規模なものであれば考える必要はないのかもしれないが、これまで繰り返し述べているように、クラウドの最大のメリットはスケーラビリティだと思っているので、それを阻害するような制約は排除すべきであると思う。



実装方法およびサンプル

具体的には以下のようにする。

1Page目:

キーワード(word=”コーヒー”)で検索する。Low-level Datastore APIのQueryで、商品名プロパティに対して条件をGREATER_THAN_OR_EQUAL指定。Reflex GAEのEntityConverter#convertでCondition指定をし、対象項目が指定されたwordで始まっていなければ結果に含めないようにする。(この処理はIn Memory)

2Page目以降:

キーワード(word=”コーヒー” )、最後の商品名(nextword=“コーヒー010”)、および、最後の連番(number=009)を指定する。
Low-level Datastore APIのQueryで、商品名+連番プロパティに対してnextword + number条件をGREATER_THANで指定。 EntityConverter#convertでCondition指定をし、対象項目が指定されたwordで始まっていなければ結果に含まないようにする。 (この処理はIn Memory)



前方一致でかつPagingを行うサンプル

1ページ目
2ページ目

 
© 2006-2015 Virtual Technology
当サイトではGoogle Analyticsを使ってウェブサイトのトラフィック情報を収集しています。詳しくは、プライバシーポリシーを参照してください。