2011年11月15日

OData のための効率的なフォーマット

OData のブログに気になるエントリがありましたので、 ちょっと日本語に訳しておきます。

  • "dense" にしっくりくる日本語を思いつかなかったので、そこはそのまま英語表記。

日本語訳

そろそろ OData におけるもっと効率的なフォーマットが必要になってきており、 このスレッド (Why XML) で直近はやり取りされている。 このトピックについてもっと突っ込んで調査し、問題を文章として表現することで、 考えられる可能性への足場づくりにしたいと考えた。

OData 用の新しいフォーマットの導入を議論すると、非常に多くのことを考えさせられる。 過去にもこのフォーラムで議論してきたように、閉じたシステムに特化したフォーマットは良かった。 しかし、たくさんのエコシステムにサポートを望むフォーマットについて話すときは別だ。 エコシステムを分断化 (fragment) しないことを保証する必要があり、特定のクライアントや サーバーを蚊帳の外に追い出してはならない。

OData は今や膨大なサーバファームでも携帯電話でも使われている。 前者はデータシリアライズにおける CPU サイクルに厳しく、 後者はデータサイズや CPU 効率化、それからバッテリー消費を注意深く管理する必要がある。 よって、どこそこの部分に注力する、と言うのがフェアだろう。

解決すべき問題とは?

フォーマットとパフォーマンスについて議論すると、お決まりの質問がやってくる。 「サイズとスピードのどちらを最適化するの?」 おもしろいことに、本当に多くの場合に、サーバ側はスループットを最適化したがり、 そうしたサーバとやり取りするクライアントはバッテリーの寿命を最大化することなら何でも最適化したがる。 そして誰もがサイズのために帯域幅を最適化する。

ここから分かるのは、バランスのとれた選択肢を模索しなくてはならない、という点であり、 相互運用を認めながら、さらなる改善の機会は常に開かれている。 このもとに、OData のための「効率的」なフォーマットを作成するというゴールを緩く定義し、 更なる効率さが更なるコンパクトさや生成の早さを意味するかどうかを問題定義に埋め込まないようにする。

何を変更できる?

シリアライゼーションの準備ができていれば、データの獲得とそれを運搬するプロセスを一定だと考えると、 シリアライゼーションのコストは CPU 時間に支配される傾向にある。 文字列でないデータを文字列に変換する (テキストベースのフォーマットを使う場合) すべての文字列を正しい文字エンコーディングにエンコードし、すべてのレスポンスを一緒に縫い合わせる。 サイズだけに注目するなら出力を圧縮するだろうが、それでは CPU を二倍も使ってしまう。 一度目は最初のドキュメントを生成するときで、二度目はそれを圧縮するときだ。

そこで、始めるときから記述量を減らし、何を書き出すかを決めるのに大量の時間を費やさないようにしよう。 このことは比較的明らかな候補を見つけられるし、余分なものを排除することにつながる。

その一方で、変えたくないこともたくさんある。 帯域外の知識なしに通信できるようになるので、OData のリクエストとレスポンスを自己内法的にすることは大事である。 OData はサーバがなんでもエンコードできるように URL を非常によく使う。 たとえば、エンティティの分散や異なるホストにまたがった関係性、継続的なエンコーディング、 CDN におけるメディアの場所といったものが挙げられる。

読み書きすべきこと、すべきでないこと

OData のペイロードを見てみると、すぐに冗長な部分を勘ぐるだろう。 しかし何回か試行するために、多くのデータを持たせるようにしておく (正確な科学的調査ではなく、大雑把なものだと考えてほしい)。 参考として Northwind データベースを OData サービスとして見せるサンプルサービスから、3つの場合を扱った。

これらは、本当に小さなデータセット、やや大きく広範なデータセット、それから大きいが狭い範囲のデータセットである。

Atom と JSON のサイズを比較すると、JSON は Atom の半分から3分の1になる。 このことの大部分は、JSON の方が冗長もしくは不要なコンテント (閉じタグ、空の必須要素、など) が少ないからだ。 値の型アノテーションの欠如のような、このうちのいくつかは実際に忠実さを損なうけれでも。

JSON のペイロードをもう少し見ていくと、メタデータとプロパティ名がコンテンツの40%近くになっていて、 システムが生成した URL も40%弱ある。 純粋なデータはコンテンツの20%前後でしかない。 データを視覚的に見たい人も多いだろうから図にしよう。

これについてはかなりの変動性がある。 URLが20-25%近くになるフィードや、メタデータとプロパティ名が65%にもなるようなものも見てきた。 どちらにせよ、これらの両方に言及する確固たる言い分がある。

アプローチ

実際の接続フォーマットと、冗長さを減らして記述量を少なくする ("write less") 戦略を分離しておこうと思う。 まずは後者に着目していこう。

上述のデータから、構造 (プロパティ名、エントリのメタデータ) と URL の両方をエンコーディングするのが大変な作業なのは明らかだ。 シンプルな戦略は二つの構造をドキュメントに導入することだろう。

  • 構造化テンプレート ("structural templates"): 含まれるものの構成を記述する。 レコードや入れ子になったレコード、それからメタデータのレコードなどである。 テンプレートは一度記述されれば良く、実際のデータをそれらを参照するだけとなるので、 行レコードにおけるプロパティ名の繰り返しを回避できる。
  • テキストテンプレート ("textual templates"): ドキュメント全体を通して何度も繰り返すテキストパターンを記録しておく。 URL などは真っ先に思いつく好例といえる。 (例えば、このようなテンプレートを想像できるだろう "http://services.odata.org/Northwind/Northwind.svc/Customers('{0}')"。 それぞれの行の値は "{0}" を置換するだけでよい。)

必要とされるときにデータと一緒にインライン化できるので、 本当に必要とされるテンプレートだけが特定のドキュメントに入ることになる。

実際にやり取りするフォーマット

接続フォーマットを実際に選択するときはたくさんの観点から検討する。

  • テキストかバイナリか?
  • どのようなクライアントが処理できるようにすべきか?
  • 低レベルの転送フォーマットを選び、テンプレートスキームに適用すべきか、 もしくは、冗長性を取り除くメカニズムを持っている既存のフォーマットを使うべきか?

私が検討したいくつかのことを挙げよう。

  • EXI: W3C が推奨するコンパクトなバイナリ XML フォーマットだ。 これの良い点は、単なる XML であり、Atom のシリアライザーを使えることだ。 圧縮効率も満足のいくレベルである。 それでも、すべてのことを高レベルのレイヤーで実現した方が良いのではないかと心配になる (CPU 使用率に関してきっちり調べていない) し、すべてのクライアントがダイレクトに使用できる わけでもない。実装するのも一仕事だ。
  • 「低レベル」のバイナリフォーマット: BSON、Avro、それから Protocol Buffers について 調べてみた。様々な OData のイディオムを受け渡す部分の上にフォーマットを定義する必要があるため、 「低レベル」と呼ぶことにする。多くの場合に冗長性を減らしたいなら、これを取り扱わなければならないだろう。 (公平を期すなら Avro は構造の面でも自己言及の側面を既に扱っているが)
  • JSON: いつだったか WCF チームの人も言及していたように、これは直感的な考えだ。 構造的でテキストのテンプレートを使って "dense JSON" エンコーディングを定義できる。 ドキュメントは一貫して JSON ドキュメントであり、どのような環境でも通常の JSON パーサーを使って処理できる。 よくできたパーサーなら dense フォーマットから最終的な出力までをダイレクトに生成できるだろうし、 普通のパーサーであってもシンプルな JSON から JSON への変換を適用できる。 これは実際のシナリオで期待する類で、プロパティ名が繰り返し出現するオブジェクトとして JSON を返す。 このアプローチはサイズに対してはそこまで最適化された結果ではない。 しかし、ほどほどな効率性を持っており、極めて優れた相互接続性を持つ。

バイナリフォーマットと JSON については、圧縮することを脇に置いておいたことに注意してほしい。 OData は HTTP 上に構築されるので、クライアントとサーバはコンテンツのエンコーディングを介していつでも圧縮について協議できる。 これにより、サーバはスループットとサイズに関して選択できるし、クライアントはサイズと CPU 使用率でどちらの最適化が好ましいかを提案できる。 (ポータブル機器の場合はバッテリーの寿命に反映されるかもしれない)

OData 用の dense JSON エンコーディング

これらの中では、私は JSON に興味を持っている。 テンプレートに詰め込むのがとても暗号的になるかもしれないが、フォーマットをテキストにしておく考え方が好きだ。 その他すべてのクライアントに加えて、ブラウザーの中で動作するという事実も本当に好ましい。

ここまでで十分に長文になってしまった。 これが興味深い方向性だと考える人たちがいれば、JSON エンコーディングの実際の定義は他の議論に移したい。 ということで、その動機付けのために一例を示させて欲しい。

上述の Netflix の例では「タイトル (Title)」型の行データがたくさんある。 典型的には、個別の行データに対して完全な JSON オブジェクトを使うだろう。 すべてが array 型でまとめられ、__metadata オブジェクトを伴ってそれぞれが「自分へのリンク (self links)」などを持っている。 dense バージョンでは、 "control" と "metadata" それから "data" に対して、それぞれ "c" や "m" あるいは "d" オブジェクトなどを持つ。 それぞれのオブジェクトはトップレベルの array 型でオブジェクトを表現し、 その値はテンプレートが定義される順番に一致しており、 新しいテンプレートを導入するメタデータオブジェクトか、テンプレートのある部分の特定の値を表現するデータ値のどちらか。 制御オブジェクトはたいてい最初か最後にあり、件数や array または singleton などを示す。 次のような感じである。

(そこまで正確さを意識しておらず、単なる手描きの図だと考えて欲しい)

ふたつの構造化されたテンプレート (JSON スキーマっぽいもの) とテキストテンプレートに注意して欲しい。 単一行データにとってはいささか大きくなってしまうが、大きすぎるほどでもないと思う。 行データが増大するにつれて、圧縮密度が高いオブジェクトになっていくだろう。 属性名がなく、解凍するときに順番が固定されている。 このようなフォーマットだと、"100NarrowCustomers" の例では元の JSON に対して3分の1くらいのサイズになる。 クライアントとサーバーをつなぐデータとして小さいだけでなく、テキストの3分の1はまったく処理する必要がないのである。

次のステップ?

複数のフォーマットについてもっと良く評価する必要がある。 個人的には dense JSON フォーマットの考え方が良いと思うが、トレードオフの作業として検証が必要になる。 もう少しよく考えてみてから、また連絡するよ。 dense JSON でもう一段階踏み込んだことを挑戦してみて、どんな感じになるかを見ていこうと思う。

ここまで読んでくれたなら、新しいフォーマットを作ることに本当に興味があるに違いない。 フィードバックを歓迎する。

ありがとう。 -pablo

終わりに

"An efficient format for OData" というエントリを日本語にしました。 OData 自体がどうなっていくかは未知数ですが、 データ構造の設計についてまとまった記事は少ないと思いますので、 クライアント/サーバ間の通信に関して議論する取っ掛かりにはなるでしょう。

JSON のキー名を個別のデータに含めない、という考え方は JavaScript の本 (High Perfomance JavaScript) でも述べられていますので、 知っているに超したことはないのかな、と。

コメントを投稿