Windows Azure Table の JSON payload

2013/11/27 に公開された最新のWindows Azure Storageでは新しくJSON Payload Format が導入されました。変更点の詳細が Windows Azure Storage Team Blog のWindows Azure Tables: Introducing JSONに出ています、興味深い内容です。

mojya mojya by Takekazu Omi, on Flickr

翻訳は追々出ると思うので、拾い読みしながら気になったことを書きます。

  • [引用]JSON Payloadは、version “2013-08-15”の一部してリリースされました。JSONは従来の AtomPub[1]フォーマットの OData payload format に比べて著しくサイズが小さくなり latency が低くなっています。また、payload size を削減するため、 insert の payload echo を Off にする方法を提供します。これらの新機能は、新しいWindows Azure Storage Client 3.0ではデフォルトの機能として働きます。

従来の HTTP request/response を見ていると無駄が目立ったので、それが削減されるのは大歓迎です。ここには、version “2013-08-15”でのplayloadの削減が書いてありますが、2013年11月(先月)ぐらいから、version “2012-02-12” の AtomPub でも余計な改行や空白を削減するなどの変更が行われています。このあたりの変更については、Windows Azure Tables の Breaking Changes 2013/11を見て下さい。

Insert の payload echo を Off にする話が出ていますが、version “2011-08-18” でサポートされたInsert Or Merge Entity (REST API)ではレスポンスのBODYが空でステータスコードは204 (No Content) を返すようになっていました、これを insert でも同じように動作するモードを付けたということのようです。Insertの時にサーバー側で付与される情報はETagとTimestampだけで、それ以外は送信した内容と同じです、レスポンスヘッダーにETagは返ってくるので、エンティティ全体が帰ってこなくても困ることはほとんど無いと思います。場合によっては、Timestamp を使う場合があるかもしれませんが、その場合は設計を見直すか、エンティティ全体を返すモードで使うかになります。

What is JSON

  • [引用]JSON(JavaScript Object Notation) は、構造化データをシリアライズするための lightweight text format です。AtomPubと同じように、 OData extends JSON formatでは、エンティティとプロパティの一般的な規則を定義します。AtomPubは異なり、OData JSON では response payload が一部がペイロードサイズを低減するために省略されます。そのため、受信側でlink, type と control dataなどのデータを再構成ための表現力が不足しています。OData には下記のような複数の JSON フォーマットがあります:

ちょっと分かりづらいです(訳が悪いのでしょうか)、この辺りの考えは、OData JSON Format Version 4.0 2 Candidate OASIS Standard 01 の 2.JSON Format Designがわかりやすく考え方を説明してくれています。それによると、実際のpayloadからwire formatの予測可能な部分を取り除いて送信できるようにするという考えでデザインされているそうです。クライアントがデータに関するメタデータを持っているというシナリオでは毎回のpayloadに全メタ情報を載せて送る(以前のAtomPubのように)のは無駄なので省略できるようにしますという話です。どこまでメタデータを持たせるかは nometadata、minimalmetadata、fullmetadata の3段階用意しています。あとそれに伴って、name/valueのペアに順序の制約を付けています。順序の話は、Hashにそのまま読み込めなくなるので少々面倒な制約です。IDLやXML Schema のようなメタ情報を定義する仕組みを持ち込まずに、受信側のクライアントのコードで実装もしくは、JSON内にメタ情報埋め込みという話にしたのは、JSONの手軽さを損なわないという点で評価できます。

nometadata、minimalmetadata、fullmetadata や、JSON payload の詳細についてはPayload Format for Table Service Operationsを見て下さい。

AtomPub vs JSON format

具体的に、AtomPubとJSONを比較してみます。JSONにすると、基本的に閉じタグが無くなるので、それだけでもかなりの削減になるのはすぐわかります。AtomPub [引用]と、JSON nometadata [引用]を見ると一目瞭然です。AtomPub [引用]の方は、元々、Atom Publishing Protocolもので、インターネット上でのコンテンツの交換を目的として設計されたものです。Table Storageでは使われない、冗長なタグが散見されます。title、id、link、author あたりとか、namespace は不要なので、それも外してしまうと結構スッキリします。試しに、XMLで余計なタグを削ったXML nometadata (参考までに作ってみました)というのを作ってみました。閉じタグだけはなんとも成らないですが、結構シンプルになります。これを見ると、XMLを選択した功罪というより AtomPub を選択した問題の方が大きかったことがわかります。OData/WCFの流れに拘りすぎてちょっと遠回りしてしまったようです。

Payloadの違いによる比較のテーブルが面白いので引用します

To compare JSON and AtomPub
Format Request Header Size Request Body Size Response Header Size Response Body Size % Savings in HTTP Body Size only vs. AtomPub % Savings in total HTTP transfer vs. AtomPub
AtomPub 3,611 2,861 3,211 8,535 N/A N/A
JSON MinimalMetadata 3,462 771 3,360 2,529 71% 44%
JSON NoMetadata 3,432 771 3,330 1,805 77% 49%

AtomPub [引用]

<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="http://someaccount.table.core.windows.net/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
  <id>http://someaccount.table.core.windows.net/Customers</id>
  <title type="text">Customers</title>
  <updated>2013-12-03T06:37:21Z</updated>
  <link rel="self" title="Customers" href="Customers" />
  <entry m:etag="W/&quot;datetime'2013-12-03T06%3A37%3A20.9709094Z'&quot;">
    <id>http://someaccount.table.core.windows.net/Customers(PartitionKey='Jonathan',RowKey='Foster')</id>
    <category term="someaccount.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="Customers" href="Customers(PartitionKey='Jonathan',RowKey='Foster')" />
    <title />
    <updated>2013-12-03T06:37:21Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:PartitionKey>Jonathan</d:PartitionKey>
        <d:RowKey>Foster</d:RowKey>
        <d:Timestamp m:type="Edm.DateTime">2013-12-03T06:37:20.9709094Z</d:Timestamp>
        <d:Address>1234 SomeStreet St, Bellevue, WA 75001</d:Address>
        <d:Email>Jonathan@fourthcoffee.com</d:Email>
        <d:PhoneNumber>425-555-0101</d:PhoneNumber>
        <d:CustomerSince m:type="Edm.DateTime">2005-01-05T00:00:00Z</d:CustomerSince>
        <d:Rating m:type="Edm.Int32">3</d:Rating>
      </m:properties>
    </content>
  </entry>
  <entry m:etag="W/&quot;datetime'2013-12-03T06%3A37%3A21.1259249Z'&quot;">
    <id>http://someaccount.table.core.windows.net/Customers(PartitionKey='Lisa',RowKey='Miller')</id>
    <category term="someaccount.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="Customers" href="Customers(PartitionKey='Lisa',RowKey='Miller')" />
    <title />
    <updated>2013-12-03T06:37:21Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:PartitionKey>Lisa</d:PartitionKey>
        <d:RowKey>Miller</d:RowKey>
        <d:Timestamp m:type="Edm.DateTime">2013-12-03T06:37:21.1259249Z</d:Timestamp>
        <d:Address>4567 NiceStreet St, Seattle, WA 54332</d:Address>
        <d:Email>Lisa@northwindtraders.com</d:Email>
        <d:PhoneNumber>425-555-0101</d:PhoneNumber>
        <d:CustomerSince m:type="Edm.DateTime">2003-01-05T00:00:00Z</d:CustomerSince>
        <d:Rating m:type="Edm.Int32">2</d:Rating>
      </m:properties>
    </content>
  </entry>
  <entry m:etag="W/&quot;datetime'2013-12-03T06%3A37%3A20.7628886Z'&quot;">
    <id>http://someaccount.table.core.windows.net/Customers(PartitionKey='Walter',RowKey='Harp')</id>
    <category term="someaccount.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="Customers" href="Customers(PartitionKey='Walter',RowKey='Harp')" />
    <title />
    <updated>2013-12-03T06:37:21Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:PartitionKey>Walter</d:PartitionKey>
        <d:RowKey>Harp</d:RowKey>
        <d:Timestamp m:type="Edm.DateTime">2013-12-03T06:37:20.7628886Z</d:Timestamp>
        <d:Address>1345 Fictitious St, St Buffalo, NY 98052</d:Address>
        <d:Email>Walter@contoso.com</d:Email>
        <d:PhoneNumber>425-555-0101</d:PhoneNumber>
        <d:CustomerSince m:type="Edm.DateTime">2010-01-05T00:00:00Z</d:CustomerSince>
        <d:Rating m:type="Edm.Int32">4</d:Rating>
      </m:properties>
    </content>
  </entry>
</feed>

JSON minimalmetadata [引用]

{
    "odata.metadata":"http://someaccount.table.core.windows.net/$metadata#Customers",
    "value":[
        {
            "PartitionKey":"Jonathan",
            "RowKey":"Foster",
            "Timestamp":"2013-12-03T06:39:56.6443475Z",
            "Address":"1234 SomeStreet St, Bellevue, WA 75001",
            "Email":"Jonathan@fourthcoffee.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince@odata.type":"Edm.DateTime",
            "CustomerSince":"2005-01-05T00:00:00Z",
            "Rating":3
        },
        {
            "PartitionKey":"Lisa",
            "RowKey":"Miller",
            "Timestamp":"2013-12-03T06:39:56.7943625Z",
            "Address":"4567 NiceStreet St, Seattle, WA 54332",
            "Email":"Lisa@northwindtraders.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince@odata.type":"Edm.DateTime",
            "CustomerSince":"2003-01-05T00:00:00Z",
            "Rating":2
        },
        {
            "PartitionKey":"Walter",
            "RowKey":"Harp",
            "Timestamp":"2013-12-03T06:39:56.4743305Z",
            "Address":"1345 Fictitious St, St Buffalo, NY 98052",
            "Email":"Walter@contoso.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince@odata.type":"Edm.DateTime",
            "CustomerSince":"2010-01-05T00:00:00Z",
            "Rating":4
        }
    ]
}

JSON nometadata [引用]

{
    "value":[
	{
            "PartitionKey":"Jonathan",
            "RowKey":"Foster",
            "Timestamp":"2013-12-03T06:45:00.7254269Z",
            "Address":"1234 SomeStreet St, Bellevue, WA 75001",
            "Email":"Jonathan@fourthcoffee.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince":"2005-01-05T00:00:00Z",
            "Rating":3
	},
	{
            "PartitionKey":"Lisa",
            "RowKey":"Miller",
            "Timestamp":"2013-12-03T06:45:00.8834427Z",
            "Address":"4567 NiceStreet St, Seattle, WA 54332",
            "Email":"Lisa@northwindtraders.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince":"2003-01-05T00:00:00Z",
            "Rating":2
	},
	{
            "PartitionKey":"Walter",
            "RowKey":"Harp",
            "Timestamp":"2013-12-03T06:45:00.5384082Z",
            "Address":"1345 Fictitious St, St Buffalo, NY 98052",
            "Email":"Walter@contoso.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince":"2010-01-05T00:00:00Z",
            "Rating":4
	}
    ]
}

XML nometadata (参考までに作ってみました)

<?xml version="1.0" ?>
<Feed>
  <Properties>
    <PartitionKey>Jonathan</PartitionKey>
    <RowKey>Foster</RowKey>
    <Timestamp>2013-12-03T06:39:56.6443475Z</Timestamp>
    <Address>1234 SomeStreet St, Bellevue, WA 75001</Address>
    <Email>Jonathan@fourthcoffee.com</Email>
    <PhoneNumber>425-555-0101</PhoneNumber>
    <CustomerSince>2005-01-05T00:00:00Z</CustomerSince>
    <Rating>3</Rating>
  </Properties>
  <Properties>
    <PartitionKey>Lisa</PartitionKey>
    <RowKey>Miller</RowKey>
    <Timestamp>2013-12-03T06:39:56.7943625Z</Timestamp>
    <Address>4567 NiceStreet St, Seattle, WA 54332</Address>
    <Email>Lisa@northwindtraders.com</Email>
    <PhoneNumber>425-555-0101</PhoneNumber>
    <CustomerSince>2003-01-05T00:00:00Z</CustomerSince>
    <Rating>2</Rating>
  </Properties>
  <Properties>
    <PartitionKey>Walter</PartitionKey>
    <RowKey>Harp</RowKey>
    <Timestamp>2013-12-03T06:39:56.4743305Z</Timestamp>
    <Address>1345 Fictitious St, St Buffalo, NY 98052</Address>
    <Email>Walter@contoso.com</Email>
    <PhoneNumber>425-555-0101</PhoneNumber>
    <CustomerSince>2010-01-05T00:00:00Z</CustomerSince>
    <Rating>4</Rating>
  </Properties>
</Feed>

最後に

書き始めたら案外知らないことが多く。てっきり、OData V3 の JSON[2]Content-Type: application/json;odata=verbosejson light (content-type : application/json; odata=light)が使われているのかと思ったら、いつの間にかOData Version 4.0 JSON[3]なんてものがあって、そっちが使われていたことに気が付いたり。

Windows Azure Storage Client 3.0で使っている、ODataLib 5.6.0から OData V4をサポートしているらしい[4]ということが分かったりということで手間取りました。

じゃあ、ODataLib 5.6.0 のコードちょっと見ておくかと思ったら、odata.codeplex.com の ODataLib は2011年から放置状態になっていて(これは知ってました)、http://www.symbolsource.org/Public/Metadata/NuGet/Project/Microsoft.Data.Services/5.6.0からコードが確認できるってことに気が付いたりで手間取りました。

今回は前半しか紹介できていないので、そのうち続きを書きたいと思います。

Resources

[1]AtomPub
[2]OData V3 JSON Verbose Format
[3]OData JSON Format Version 4.0 Candidate OASIS Standard 01 19 November 2013
[4]OData V4 のstackが、ODataLib として提供されることがコメントに書いてあるWCF Data Services 5.6.0 Release