kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

月別アーカイブ: 6月 2010

Azure Table ストレージをまっとうに使う為のExpressionVisitor


ランチにビールはかまいませんが、画像をアップするのはテロですのでやめましょう。 などと内輪ねたを最初に持ち出してみます。
 
というわけで、Azure の Table ストレージのKVS固有の制約を取っ払う方法についてです。
 
たとえば、商品マスタをAzureテーブルに乗せたとします、商品コード、商品分類、価格、その他いろいろなプロパティで検索を行いたい場合、これをどうやって実装するかです。
 
価格が1000円代のゲームソフトで機種 xbox な物ならこんなLINQ式になるはずです。
 
from prod in db.Products
  where 1000<=prod.price
      && prod.price<2000 
      && prod.type== "game"
      && prod.platform=="xbox"
  select prod
 
RowKey へのキー重畳によって以下のキーフォーマットを持つと設計すれば良いでしょう。
 
RowKey = 商品コード 8文字 + 商品価格(数字8桁) + 商品種別(英字6文字) + プラットフォーム(英字6文字)
 
これにより、以下の LINQ 式が上記LINQと同等になるわけです。
 
from prod in db.Products
  where string.Compare( prod.RowKey.SubString( 8,8 ) , "00001000", StringComparison.Ordinal ) >=0
   && string.Compare( prod.RowKey.SubString( 8,8 ) , "00002000", StringComparison.Ordinal ) <0
   && prod.RowKey.SubString( 16,6 ).Trim() == "game"
   && prod.RowKey.SubString( 22,6 ).Trim() == "xbox"
 
これがTableストレージの REST の GET での fillter 句は以下のようになるはずです。
 
$filter= (substring( RowKey, 8,8 ) ge ‘00001000’) and (substring(RowKey,8,8) lt ‘00002000’) and (trim(substring(RowKey,16,6)) eq ‘game’) and (trim(substring(RowKey,22,6)) eq ‘xbox’)
 
貴方が Azure ストレージ専用のプログラムを書いているなら要するにこういう LINQ を書き散らせば良いわけです。話は終了
キー項目を増やしたくなったとかになった場合でも64KBまでRowKeyは増やせますので、そうすれば良いだけのこと、やったね!
 
んで Azure ストレージ専用のプログラムを書いてない、他でも通るクエリを書きたい場合にどうするのかって事。
 
ここで ExpressionVisitor クラスの登場となります。
(.NET 3.5 ではクラスライブラリに標準実装は無かったんですが、MSDN上にサンプルがありました。.NET 4では標準です)
 
なんかのプロパティ(またはフィールド)にアクセスしている場所は MemberExpression があります。
 
VisitMember をオーバライドして以下のようにします。
 
protected override Expression VisitMember( MemberExpression m )
{
    MemberInfo mi = m.Member;
    var keyMap =from attr in mi.GetCustomAttributes( typeof( KeyMapAttribute ) ).Cast<KeyMapAttribute>().SingleOrDefault();
    if( keyMap==null ) return base.VisitMember( MemberExpression m );
// 本当はいろいろやるんだけど要するに以下をする
    Expression instance = Expression.MemberAccess( m.Expression, m.Expression.Type.GetMember( keyMap.PropertyName ) )
    retrun Expression.Call( Expression.Call( 
                instance,
                typeof(string).GetMethod( "SubString", new Type[] { typeof(int), typeof(int) } ),
                new Expression[] { Expression.Constant(keyMap.BeginPos), Expression.Constant(keyMap.Length) } )
        , typeof(string).GetMethod( "Trim", Type.EmptyTypes ), new Expression[] {} );
}
 
KeyMapAttribute は以下の定義
 
[AttributeTarget(Property)]
class KeyMapAttribute : Attribute
{
    public string PropertyName { get; private set; }
    public int BeginPos { get; private set; }
    public int Length { get; private set; }
 
    public KeyMapAttribute( string propertyName, int beginPos, int length )
    {
        PropertyName = propertyName;
        BeginPos = beginPos;
        Length = length;
    }
}
 
これで
[KeyMap( "RowKey", 16, 6 )]
public string type { get; set; }
 
としとけば上記のExpressionVisitor が where prod.type == "hoge" の prod.type 部を prod.RowKey.SubString( 16,6 ).Trim() に変換してくれるわけですね。
 
これで普通の LINQ 式でのwhere がキー重畳を意識して RowKey なり PartitionKey を使った格好に書き換えられますので、別のテクノロジ(たとえば LINQ to SQL)向けに書かれた LINQ 式でもかなりが通るようになるわけです。
 
このように Azure テーブルの標準のTableClientはぶっちゃけていえば機能不足というか「ストレージテクノロジの基本特性が露出した状態」にありますので、まだまだアプリケーションで素のまま使うにはアレです。「式木をゴリゴリ操作するなんて怖いこと僕できない」な人にはぶっちゃけまだ手を出すには早い素材と言えるかも知れません。(=人柱希望者でない人は傍観推奨)
 
式木をゴリゴリするのが苦にならない or 苦しかろうが LINQ ですでに書かれたクエリ重要な人はこの辺りいろいろ変換をかける ExpressionVisitor をいろいろ書き散らすつもりで Azure に取り組んだほうがよろしいと思います。
まぁ、うちのばあい、(最近事例が追加で出たみたいなんですが)、CMSの中核は「LINQ クエリを実行して結果をHTMLレンダリングするテンプレートエンジン」だったりして、LINQクエリは「顧客に提供済みのプロダクト上での顧客資産」なわけで、Azureで動かすからといって「KVSだから」うんぬん言うのも無駄な話、できなければ、できるようにするしかないのであります。
広告

バッチかリアルタイムか


世間的にはHadoopみたいなバッチソリューションでの分析や集計がこれからはやるって方向なんですが、ちっと異議ありーってところ。
 
確かにKVSだとテーブルにキーは一個だけ、あってもたかが知れてる個数しかないのはご存知のとおり。
これじゃ多様な分析ニーズを満たすなんてことはぜんぜんありえナスな事。
 
でも1テーブルしか更新しないって前提であればそうだったりするだけで、分析のニーズに応じて複数テーブルをInsert/Updateすればいいじゃないって話。RDBMSのサーバがINSERT/UPDATEで物理テーブルと同時にいくつものINDEXを更新するのは周知の事実でそれをプログラムコードでやればすむって話。
 
このときにテーブルのキー構造とインデックスのキーは必ずしも一致する必要はないし、分析側のニーズで必要なINDEXがあるならそれを別々のINDEXテーブルに持てば良いわけで、これでwhereで使えるキーが制約されるとか、そんなのお構いナッシング。Hadoopとかに代表されるMapReduceを使うとかの以前に DAL からビジネスレイヤよりの場所で見れば複数インデックスが付いたテーブルに見え、DAL以降では複数のKVSテーブルに分散格納された構造を作るのは決して不可能なこっちゃなく。
 
フロントの負荷?インスタンス増やして分散しれ!なんのためのクラウドじゃって話だったりするし。
 
確かにこれまでのBIは OLTP からバッチ的にETLしてキューブ作ってという手順踏んできたしそれが効率の良い方法っていうか、必要とされる処理効率に対しての解がそうだっただけで、それを延々とやるわけにはいかんだろーし、「データが見えるまでのラグタイムが発生する」事をいつまで顧客が納得して受け入れ続けるか、リアルタイム更新での分析データを競合が出せると言ったらどうなるか。
 
KVSではキーに制約があって…、既存の OLAP の流れで バッチによるETLで… そこでMapReduceですよHadoopですよって、流れは普通なんだがあからさまに普通ですねー感もしないでもなく。
とりあえずKVSはどの層に属するもんなんだという点で考えれば「トラック番号、セクター番号」でIOするディスクより何ぼかマシ程度の物理層なんじゃねーかとしてみる方向もないわけじゃないっすよと。

System.Web.Management と Azure


ちっとハマッタお話
 
web.config で system.web/helthMonitoring 以下をいじるときで Azure WebRole を使っているときは構成がらみが結構センシティブなんで要注意。
All Events を Trace に振るとかすると、 Development Fabric でWebRoleがぜんぜん起動しなくなったりするから超危険。
 
この辺、Azure はなにやらむちゃくちゃカスタムしている模様なんですが、どういうカスタムしてるか全然情報がなくてこまりまくりんぐ。
 
んで、「唐突に何をって」思う人に向けて話しの展開
 
 
でも言及されてるんだけど、「仮想マシンがクラッシュするとログデータが吹き飛ぶ」(事がある)んですよ。
んで、うちはお金に絡むログデータもあったりしたり、吹き飛ぶことを容認できないケースバイケース要素が非常に多いわけでございます。
なので、絶対吹き飛ばないようにする為に標準の AzureのDiagnosticsMonitorでのログ取りだけでは不十分だったりするわけです。
 
日本におけるAzureの神の一人である酒井さんの本では Queue の使用方法の解説でログを流すサンプルアプリケーションを書いてらっしゃるわけですが、この例はあくまで Queueを使う事を目的としたサンプルなんで「ログを書くことがメイン」なアプリなわけでありますが、Queueはログの出し先としては非常に有用であります。
 
まぁ、詳しくは system.web/helthMonitoring の config リファレンスなんかを見てもらうとしても、 System.Web.Management 配下は「イベントの重要度によるフィルタリングが設定でできる」、「本当に重要なものは即座に処理できる」、「必要であればイベントをどっかへ投げる方法が用意できる or else 用意されている」といいことづくめでございます。
 
要するにログをとりたくなるかも!!って要素は WebEvent 吐いとけ、これ推奨。
んで、重要なログを本当に重要に扱うには WebEventProvider を実装するといいですよ。
んで、何が重要とかはお客やシステムの使われ方によって違うんで、config で構成できる system.web/helthMonitoring は非常に良いわけでございます。
 
仕事で ASP.NET 開発やるならマジ知っとけな名前空間は System.Web.Management 配下って事で AzureQueueWebEventProvider を書いてみたりした今日この頃でありまする。
 
前回の記事も含めてむにゃむにゃProvider しか書いてない今日この頃なんであったりしまする。
 

にゃんたら Provider で簡単拡張アプリケーション


Azureのテーブルストレージに対応したMembershipやらRoleやらのProvider を書いていて、「別に標準外のプロバイダーを作るのは簡単じゃないか」と気づいたのでメモ
 
ProviderBase の派生クラスとして、自分のほしいAPIセットを持つクラスを作る
 
public abstract class PageTitleProvider : ProviderBase
{
    public override void Initialize( string name, NameValueCollection config )
    {
        base.Initialize( name, config );
        // 追加の初期化処理が必要なら実装する
    }
    public abstract string GetPageTitle( Uri pageUrl );
}
 
ConfigurationSection 派生を作って web.config で定義できるようにする。プロパティを ProviderSettingsCollection を返すとして実装するのがポイント
 
public class PageTitleProviderConfig : ConfigurationSection
{
    [ConfigurationProperty( "titleProviders", IsDefaultCollection=false )]
    public ProviderSettingsCollection TitleProviders { get { return (ProviderSettingsCollection) this["titleProvider"]; } }
}
 
HttpModule を作って Init で ConfigurationSection をとってきて、Providerを初期化して回る。System.Configuration.TypeNameConverter で文字列をTypeに変換するのがポイント Type.GetType(string) だとモジュールのあるアセンブリ内か、mscorlib からしか引けないので注意
 
ProviderSettings には Type のほかに Name および config に使えるParameters があるんで、コードは至極簡単。
 
var configSection = (PageTitleProviderConfig) ConfigurationManager.GetConfig( "pageTitles" );
var tnameConverter = new TypeNameConverter();
var providerCollection = new ProviderCollection();
foreach( ProviderSettings settings in configSection.TitleProviders )
{
    var providerType = (Type) tnameConverter.ConvertFrom( settings.Type );
    var provider = providerType.GetConstructor( Type.EmptyTypes).Invoke(null) as PageTitleProvider
    if( provider ==null ) throw new ConfigurationException("ちがうもん混ぜんな");
    provider.Initialize( settings.Name, settings.Parameters );
    pvoviderCollection.Add( provider );
}
_providerCollection = providerCollection;
 
んで、HttpModule からProvider をもらう方法を用意するわけだが、ConfigSectionに DefaultProvider プロパティとか設けとけばそれのNameでとるだけだったりする
 
public static PageTitleProvider TitleProvider
{
    get { return _providerCollection[_defaultProviderName] as PageTitleProvider; }
}
 
これで、よくあるProvider として web.config (app.config)で設定すればいくらでも使えるプロバイダができあがり、HttpModule も構成ファイルで使う事を指定するのを忘れずに。
 
<pageTitleProviders>
    <add name="pageTitle" type="AzureTablePageTitleProvider, Assembly.Name.Here" />
</pageTitleProviders>
 
ってやればきっと Azureのテーブルからページタイトルを取ってきてくれるんだけど、どっから、どうやってとかはプロバイダが中で考えればよろし、アプリケーションは ModuleからProviderを呼ぶだけ。モジュールから取ってきて呼ぶまでラップしちゃえばProviderの存在自体もアプリケーションは忘れてよろし。
 
アプリケーションの主要処理からストレージの心配とか、むにゃむにゃの心配とか全部消せる(責任はProviderが取るという前提であれば)んでやりたいことに専念したコードに集中できる用になるわけですな。
 
自分たちの商売に関するところだと、カード周りの決済ゲートウェイの仕様とかはあっちゃこっちゃでばらばら、物流連携のフォーマットもバラバラ、ポイントシステムなんて各社各様にもほどがあるの世界なんだけど、これをProvider にすると、config でコレとアレとドレをアプリケーションに使わせるって指定すればそれで動くようになるわけですな。
 
てなわけで、Provider書こうよProvider!!