2011年11月28日

Azure Tableによるデータ検索 - エンティティの結合(Join)

Windows Azure Tableに対するLINQクエリおよびREST APIでは、Joinによるエンティティの結合をサポートしていません。もちろんAzure Tableを使う時には、関連するエンティティのプロパティをコピーさせるなどしてJoinする必要がないようにデータモデルの設計を行います。

ただそれでも、仕様変更や機能追加によって当初想定していなかったデータの結合が必要になる場合もあります。そして、どうしてもデータモデルを変更したくないような場合には、Join処理が必要になってくると思います。

今までシステムを実装してきた中で行ったJoinの実装パターンを紹介します。

データモデル

以下のようにある人(Personエンティティ)とその友達に関する情報(Friendエンティティ)をOne to Manyの関係で持つといったデータ構造とします。

クラス図0

基本的な考え方

以下に3つの実装パターンを書きますがどれも基本的な考え方は同じで、一旦Join元(Person)のエンティティを使用する数だけLinq to Azure Tableによって抽出し、それに対して関連するFriendを別のクエリで引っ張ってくるといったものです。それぞれのパターンでの違いは、その関連エンティティの取得をビジネスロジック中のどのレイヤで行うのか、そして非同期に行うか・同期で行うかといった点によるバリエーションです。

実装パターン

Ajaxで対応する

まず一つ目がAjaxによって対応するパターンで、このケースではPersonエンティティ(の一覧)を元にした画面表示を行い、その後にAjaxにより非同期に関連エンティティを表示していくものです。

このパターンの利点は、画面に対するレスポンスが早いという点にあります。特にその画面においてFriend情報の重要性が高くない場合には効果的です。ただその一方、Friend情報の重要性が高い場合にはあまり利点となりません。

Linq to Objectを使う(N+1回のクエリを実行する)

次が、Linq to Objectを使って無理やりJoinさせる方法で、以下のようなクエリ式によって処理が可能です。

   var list = from e in ctx.CreateQuery<Person>("Entities").Take(3).AsEnumerable()
        select new {
         Person = e,
         Friends = ( from r in ctx.CreateQuery<Friend>("Friends")
           where r.OwnerPartitionKey == e.PartitionKey && r.OwnerRowKey == e.RowKey
           select r ).ToList()
        };

結果として取得される順序にこだわりがない場合は1行目でAsEnumerable()を呼び出している所をAsParallel()に変更して、PLINQを使ってもいいかもしれません。

この方法の利点は実装が容易という点にありますが、ただ実行されるクエリを見るとわかる通り、取得されるPersonの件数+1回分のクエリが実行されてしまい、パフォーマンス的にはイマイチです。

クエリを2つに分割して、Linq to Objectで結合させる

3つ目のパターンは上記Linq to Objectで問題になっていたクエリ回数を削減するもので、Personの一覧を取得した後にそのPersonの一覧すべてに対する関連のFriendエンティティをまとめて取得するものです。

   var list = ctx.CreateQuery<Person>("Entities").Take(3).ToList();
   var q = from r in ctx.CreateQuery<Friend>("Friends")
     where ( r.OwnerPartitionKey == list[0].PartitionKey && r.OwnerRowKey == list[0].RowKey ) 
     || ( r.OwnerPartitionKey == list[1].PartitionKey && r.OwnerRowKey == list[1].RowKey )
     || ( r.OwnerPartitionKey == list[2].PartitionKey && r.OwnerRowKey == list[2].RowKey )
     select r;

このパターンでは基本的に上記のようなクエリを動かすようにすればいいのですが、問題は取得されるPersonの件数が動的に変わるため、実際は上記のようなコードは書くことができません。そのため以下の参考URLのように、このwhere部分の条件式を動的に組み立てる必要があります。

http://d.hatena.ne.jp/coma2n/20080717/1216269202

またこのクエリでは、ORによる条件指定が長くなる傾向があり、HTTP GETリクエストの際のURIセグメント長の制限(260文字? http://buchizo.wordpress.com/2010/10/19/ms10-070-%E3%81%A8-uri%E3%82%BB%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%88%E9%95%B7/)に引っかかる可能性が高くなります。同時に、このクエリをバッチリクエストで行うといった対応も必要になりそうです。

ということで、この方法を使うと単純にLinq to Objectを使うよりはパフォーマンスも改善するはずですが、実装コストが非常に高いのが問題です。

まとめ

今回はWindows Azure Tableにおいて強引にJoin処理を行う方法について紹介しましたが、基本的にはこのような方法は使わずにJoin元のエンティティ(今回の例だとPerson)にJoin先のエンティティ(Friend)の情報をコピーしていくといった方針が望ましいと思います(One to Manyの関連が必要な場合はこちらの記事を参考にしてください)。ただ、必ずコピーしていると楽観的ロックによるエラーが発生しやすくはなるので、処理内容との兼ね合いもあります。

どうしても必要になった場合は、UI設計が許すならばAjaxで対応し、そうでなければ2番目のLinq to Objectによるものが良いのではないかと思います。

0 件のコメント:

コメントを投稿