CROSS2014の次世代Webセッションを見て来た。
セッションの前半で議論されていたプロトコル編はしっかりとした方向性が示されていたが、後半のアーキテクチャー編は現状の混沌とした話が多くて、方向性というか新しいビジョンを示すまではいけなかった印象だった。
それは、サーバのアーキテクチャーが成熟していることも理由の一つなのかもしれない。
しかし、アーキテクチャーこそ方向性を示すのが重要だろうと思うので、個人的に考えていることをまとめることにした。
Webスケールを実現する技術とリアルタイムWebの方向性
リアルタイムWebというわけではないが、密結合なプロトコルはことごとくインターネットで玉砕されてきた歴史がある。
古くはCORBA IIOP、DCOMの失敗。それからSOAPの失敗。
(ちなみに、IIOPのIはInternetで、当初は大規模なインターネットスケールで分散させようとしたことがうかがえる)
これらは何でうまくいかなかったのか。
それは、簡単に言ってしまえば、単一のマシンから複数のマシン、そして、インターネットへと拡張していく際に失うもの、つまり整合性や信頼性の問題を考慮してなかったからだと思う。(詳しくは補足を参照)
IIOPにしてもSOAPにしてもライブラリのAPIのような形で提供して、それをインターネットを跨いで同期的に実行することを想定していた。
これらは整合性や信頼性の問題を解決することなしに、単一マシンのアーキテクチャーをそのままインターネットの信頼できないマルチ・マシン上に拡張してしまったのだ。
(かつてIBMでCORBAのプロジェクトに多少からんでいた私は、インターネット上では通用しないことを嫌というほど味わった。早く気づくことができていたら(特にIBMは)莫大な投資をせずに済んだのに。)
ところで、リアルタイムWebの方向性はどうなんだろうか。
将来においても、既存のWebアーキテクチャの延長線上にあるものなのか。それとも、別の違う方向を進むのか。 また、何かリアルタイムWeb技術が急速に進んで、既存のWebアーキテクチャーがそれに置き換わるというような話になるのか。
クライアントとサーバが密に結合した形になってしまうと前述したようなトラウマが蘇ってくる。
プロトコルの高速化によりある程度の適用範囲は広がるとは思うが、それでも局所的にしか解決できないだろう。 逆の言い方をするとリアルタイムWebを追求していくとNWやプロトコルの高速化でしか対応する術はないように思える。
一方、既存のWebアーキテクチャというのは、http(s)とかプロトコルの話だけでなく、RESTなどを含むアーキテクチャスタイルの話が中心になる。
これは、permalink(URI)で示すリソースを操作する概念であり、基本的にステートレスである。 あらためて説明するまでもないが、RESTの「リソース」概念は次のようなものである。
・インターネットのすべてのものがリソースである。
・すべてのリソースは、URIによってアドレス可能である。
・リソースに対する操作は、HTTPの基本的なオペレーション、Get, Put, Post, Delete によって定義される
普通のWebサーバは基本的にはスタティックなホームページの配信を行い、動的なWebにしてもサーバで都度生成したものを返しているだけだ。
Webスケールといった言葉があるように、これはスケーラビリティに優れており、また、ネットワーク耐性にも優れている。
RESTは要はそのWebのアーキテクチャースタイルを壊さないようにWebサービスに応用したものである。
また、キャッシュも重要な技術要素の一つである。これは、「誰か他の人のリソースを利用し、あるいは、他の誰かが、我々のリソースを利用することを認める」ような世界である。
Webスケールを実現できているのは、ブラウザキャッシュ、PROXY、あるいはCDNといったキャッシュ技術があるからといっても過言ではない。
他方で、ワンセグTVがフルセグTVより数秒のディレイがあっても気にする人はいないように、そもそも本当にリアルタイムである必要があるかという話がある。
別のいい方をすれば、非同期的な更新で、eventuallyであっても十分ではないかということ。 例えば、一般的なWebアプリのユースケースを考えてみると、そこまでオリジナルにこだわる必要はなく、コピーの若干古い情報だとしても実用上問題はないケースも多い。むしろ、完璧に同じタイミングじゃないとまずいケースの方が少ないのではないか?
これは妥協しろと言っているのに等しく、低レイテンシーを求めるリアルタイムWebとは相容れない考え方かもしれない。
なので、個人的には、既存WebとリアルタイムWebは別の方向性をとり、リアルタイムWebが既存Webに置き換えられていくこともないだろうと考えている。
全体的な方向性としては、リアルタイムWebはそれはそれで盛り上がりつつも、既存Webのアーキテクチャー上をベースとして今後も進んでいくのではないだろうか。
データのキャッシュ化を実現するためには
一つの解として、HTMLなどがWebサーバやブラウザでキャッシュされるのと同様に、データにおいてもキャッシュさせるという考え方がある。
そもそもデータは動的であり揮発性の高いものであるが、生成する際にバージョニングして静的に扱えるようにすることで、HTMLなどの静的コンテンツと同様、キャッシュを効かせることができればスケーラビリティは高くなる。
バージョニングとは、例えば、データはレコード単位にURI+リビジョン番号を振ることでユニークであることを保証し、それが一致している限り同じデータとみなすような仕組みである。更新されるとURIは変化しないがリビジョン番号はカウントアップされる。
正のデータはサーバ上にあって常に誰かによって更新されている。他のシステムはそのコピーを持ち、正のデータが更新されるとEventuallyに更新されることを前提に考える。ここでは遅延を許容することが肝要である。
また、同じデータが複数のクライアントやサーバに散在している可能性も考慮し、更新する際は何回更新されても同じリビジョンであれば同じ結果になるようにする(べき等性を考慮する)
ただし、データの整合性についてはシビアに考えなければいけない。これには、更新の際に元データのリビジョン番号が異なれば他者による更新があったということでエラーとするような楽観的排他の仕組み(Snapshot Isolation)で対応できる。
これにより、整合性の問題をWebのアーキテクチャースタイルを壊さないで解決できるようになる。
去年だったか、permalinkの重要性は薄れて短縮リンクで十分という議論もあったが、整合性まで考えるとやはりpermalinkは重要であるように思う。短縮リンクはpermalinkのaliasという位置づけであろう。
サーバ負荷軽減のためには通信しないこと
注意しなければならないのは、既に同じリビジョンのデータを持っているにもかかわらず、何度もサーバにリクエストを投げてしまうようなことである。これではキャッシュしている意味がない。
CROSSセッションの議論でも、シングルページアプリケーションのデメリットとして、サーバへの通信回数が増えて負荷が大きくなるという指摘がなされていた。
「long pollなどNativeと同じで不必要な通信が多く発生するリスクがありしばしば作り方が問題になる。 初期化のタイミングがなくなる反面、サーバへの通信回数が増える。 最悪、ステータスコードを見ないでリトライするようなクライアントコードが書かれるリスクも考慮しないといけない」
シングルページだとパフォーマンス管理が難しくなったという意見であるが、そもそもサーバサイドのレンダリングを引きずられていると高速化は難しく、たとえデータバインディングに特化できたとしても、効率よくしないと今度はそれがパフォーマンスに悪影響を与えてしまう。
ステートレス性というか、キャッシュを利用してなるべく通信を発生させないようにする。また、発生しても必要最低限のデータに絞ることが大事である。
究極の形態がオフライン耐性をもったアプリであり、Offline Firstのような概念や方向性をもって作っていけば、おのずとNW効率のよいものが出来上がるのではないかと思う。
クライアント・サーバ間のデータバインディング
クライアント・サーバ間のデータバインディングは、具体的にはAngularJSに代表されるようなMVVMに加えてサーバとのデータバインド機能を追加するようなイメージとなる(図)
これまでの、クライアントからのリクエストによってサーバ上で動的にコンテンツが作られるスタイルから、基本的にクライアントとサーバが同じデータを参照しており、どちらかが更新したタイミングでお互いに通知されて同期されるような、クライアントとサーバとの双方向バインディングのスタイルに変化していくと思う。(リアクティブプログラミングという)
これからの時代、このようなクライアント・サーバ間のリアクティブプログラミングなどが求められると思うのだが、一歩間違えれば密結合の世界になって、スケールしなくなるのも懸念される。
同期的な密結合ではスケールしないことは述べてきた通りであり、それは、XML/SOAPでもREST/JSONでも同じことだといえる。
実際、WebサービスのAPIは今ではREST/JSONが主流であるが密結合になってしまって残念なものも多く見られる。
クライアント・サーバ間のデータバインディングでは、図にあるように、XHRでリクエストを受けつけるが、レスポンスについてはWebSocketで非同期更新する。
バージョニングされたデータが非同期更新されるのがポイントであり、WebSocketであっても同期的な密結合に陥ることはないのが特長である。
あと、バリデーションの問題(サーバとクライアントでどうバリデーションするか)といった新たな課題もあるが、長くなるのでここでは触れない。
通信のデータ形式、それから、BaaS
クライアント・サーバ間のデータバインディングでは、通信のデータ形式はXMLでもJSONでもMessagePackでも何でもいい。 むしろ同じように扱えるべきだと考える。
データとは本質的には普遍的なものだから。
よく見かける、XML vs JSONとか、SOAP vs RESTなどの議論はたいへん不毛である。
データ表現を変えたところでアーキテクチャーが密結合であれば意味がない。
ちなみに、弊社のReflexWorksは様々なデータ表現に対応している。
(先日、既存のReflexWorksシステムにおいて、XMLからMessagepack+Deflateに変更したところ処理速度が4倍(1034msから250ms)、データ量が2%(892,799bytesから21,255bytes)になった。なんと98%削減できたことになる。業務ロジックに変更はない。)
現在、AngularJSのクライアントとReflexWorksサーバ間との非同期データバインディングが可能なBaaS、vte.cx engine(ぶいてっくす えんじん)を開発中である。
ソフトスキーマと項目単位に付与できるACLやグループ機能などのSNS機能もある。
乞うご期待。
(補足:http://stream.itmedia.co.jp/enterprise/pdf/pdf_16_1235461490.pdf より)
・整合性の問題
マルチ・プロセスからマルチ・マシンへ拡張するとき、我々は、状態を失う。「システム」のグローバルな状態というのは、虚構である。興味深い分散システムには、整合的な状態というものは存在しない。(Lamport:http://research. microsoft.com/users/lamport/pubs/pubs.html)分散OSのプロジェクトは、グローバルな状態を導入しようとしたが、大々的に失敗した。
しかし、障害の局所化(何かまずいことが起きても、システムの部分は生きのこる)を手に入れた。
・信頼性の問題
(インターネットの)信頼できないマルチ・マシンたちに拡張するとき、誰を信ずることが出来るか分からない難しい状況の中で、我々は信頼を失う。しかし、我々はスケール(webスケール、インターネットスケール)」を手に入れた。