Windows Azure Tips from Prospex

システム開発 × インフラ構築・運用 × グラフィックデザイン = プロスペックス

Expression を使って動的 OrElse

clock February 19, 2012 15:47 by author Ito

なますて。

とあるAzureプロジェクトで、パフォーマンスが超悪いものがあるので、それを何とかしている今日この頃です。 

 

さて、Azureでトレンディーなもののひとつに、Windows Azure Table があります。
こいつは、自動でスケールアウトされたり、データを大量に詰め込んでもほぼ定数時間で取得出来たり(PartitionKey, RowKey を指定したとき)と、
使いこなせれば非常に強力なものだと思います。

しかし、使い方を間違ったら…マジやばいです。

 

今回のプロジェクトでも、Azure Table を使用していますが、その使用している部分が非常に遅い…

具体的に何が遅いと言うと

・RDBMSのように、親テーブルがあり、詳細情報用に子テーブルがある。
・親テーブル項目をキーとして、子テーブルを Azure Table から一件ずつ取得している。 

実際には、親テーブルは Sql Azure にいて、詳細情報は Azure Table にいるという変則なのですが…

この、Azure Table から一件ずつ取得が超遅いのです。

速度を調べてみると、Azure Table から一件取得するのにかかる時間は大体50ms。
それを約300件分取得しようとするので…15秒!
(現在は苦肉の策で20件ずつのページング処理化してあります)

 

そんなの、一回で全部取得すればいいじゃないか

 

そこで考えたのが、取得するエンティティのPartitionKeyとRowKeyをOrElseでつなげる。

しかし、取得したいエンティティの件数は動的に変化するので、Expressionクラスを用いて動的にWhereメソッドに渡す式を作ってみます。

 

Expression exp = null;
ParameterExpression paramExp = Expression.Parameter(typeof(HogeEntity), "e");

// Expression : e.PartitionKey == pkey
string pkey = string.Format("prospex-hoge-{0:0000000000}", tenantID);
Expression pKeyExp = Expression.Property(paramExp, "PartitionKey");
Expression pkeyEqualExp = Expression.Equal(pKeyExp, Expression.Constant(pkey));

foreach ( var item in items) {
    // Expression : e.RowKey.CompareTo(rkey) >= 0
    string rkey = string.Format(
        "{0:0000000000}-{1:0000000000}-{2:0000000000}-{3:0000000000}-{4:000}-",
        item.TenantID,
        item.BoxID,
        item.ThreadID,
        item.MessageID,
        item.Language);
    Expression rKeyExp = Expression.Property(paramExp, "RowKey");
    System.Reflection.MethodInfo compareToType = typeof(string).GetMethod("CompareTo",new Type[] { typeof(string) });
    Expression rKeyCompareToExp = Expression.Call(rKeyExp, compareToType, Expression.Constant(where_RKey));
    Expression rkeyGteExp = Expression.GreaterThanOrEqual(rKeyCompareToExp, Expression.Constant(0));

    // OrElse結合
    if ( exp == null ) {
        exp = rkeyGteExp;
    } else {
        exp = Expression.OrElse(exp, rkeyGteExp);
    }
}
 
exp = Expression.AndAlso(pkeyEqualExp, exp);
 
TableServiceContext context = _tableClient.GetDataServiceContext();
IQueryable<HogeEntity> entitys = context.CreateQuery<HogeEntity>("HogeEntity");
var q = from t in entities
        select t;
q = q.Where(Expression.Lambda<Func<HogeEntity, bool>>(exp, paramExp));

 

そして実行…したら例外でました。内容は、

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">

  <code>InvalidInput</code>
  <message xml:lang="en-US">0:One of the request inputs is not valid.
RequestId:d54d290e-bc01-4fa7-9c77-30bde58fde6c
Time:2012-02-19T07:49:39.6028980Z</message>
</error> 

理由はたぶん、URLが長くなりすぎたと思われ。
(TableServiceContext? は、REST API のラッパーであるため、最終的には Azure Table のURLにリクエストを投げているだけ)

 

そこで、とあるサイトで「バッチリクエスト(エンティティグループトランザクション)」で送る必要があるようなことが書かれていたため、試してみた。

 

foreach ( QueryOperationResponse res in context.ExecuteBatch((DataServiceQuery<MessageBodyEntity>)q) ) {
    foreach ( HogeEntity entity in res) {
        data = entity;
    }
}


これで実行…だが結果は同じであった。


とりあえず調べてみて分かった Azure Table の制限。

・長いURLはつかえない。(ブチザッキさんに詳しく書かれている)
・バッチリクエスト(ExecuteBatch)では、$filter は使えない。(上の方法だと、最終的に$filterを使うURLになる)


だめじゃん…


Azure Table はちゃんと設計しないとハマります。




TableServiceEntity脱却と、Entity Framework

clock February 22, 2011 21:43 by author Sekine

物事は横断的に考えていこうよ っと!

Azure ストレージテーブルでデータ設計を考える。

カテゴリは、LINQです、ちょっと脱線してますが。



そもそも、データの入れ物は、単にプロパティ定義があれば良く、

Table Storage を利用するからと言って、

Microsoft.WindowsAzure.StorageClient.TableServiceEntity なんかに依存したくない。


ハイ。以下の通り。
(サンプルデータのpubs.publishersを想定)

※ System.Data.Services.Client の参照が必要
using System;
using System.Data.Services.Common;

namespace SampleStorageStore.TableEntitys
{
    [DataServiceEntity]
    public class PublisherEntity
    {
        public string PartitionKey { get; set; }
        public string RowKey { get; set; }
        public DateTime Timestamp { get; private set; }

        public string Pub_id { get; set; }
        public string Pub_name { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
    }
}

クラスのアトリビュートに、DataServiceEntity を付ければ良いわけです。

Azure Table で使うには、PartitionKey, RowKey, Timestamp を定義する必要があります。


コンテキスト側のメソッドは、次のような感じ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

using SampleStorageStore.TableEntitys;

namespace SampleControlImpl
{
    public class PublishersControl
    {
        public void AddPublisher(
            string pub_id, string pub_name, string city, string state, string country)
        {
            var account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");

            CloudTableClient client = account.CreateCloudTableClient();
            client.CreateTableIfNotExist("Publishers");

            TableServiceContext context = client.GetDataServiceContext();

            PublisherEntity publisherEntity = new PublisherEntity
            {
                PartitionKey = "Sample",
                RowKey = "000000",
                Pub_id = pub_id,
                Pub_name = pub_name,
                City = city,
                State = state,
                Country = country
            };

            context.AddObject("Publishers", publisherEntity);
            context.SaveChanges();
        }
    }

}

TableServiceEntity を使った場合と全く同じです。

PublishersControl control = new PublishersControl();
control.AddPublisher("0736", "New Moon Books", "Boston", "MA", "USA");
こんな感じで挿入できます。


さて、TableServiceEntity のしがらみは無くなりました。

じゃ、どうするかと言うと、

個人的には、アスペクトの組入れに、ContextBoundObject を継承させたいのですが、

Entity Framework との融合と、ExpressionVisitorの走査手助けが欲しい・・


Entity Framework を想定すると、ObjectContext の継承と思いますが、

Azure 利用では、ExpressionVisitor が、どうしても欲しいので、

結果的には、つまり、Azure利用での継承元となるのは、ExpressionVisitorとなります。


Entity Framework の想定も外せないところなので、

ここで、POCOの登場であります。

→メモメモ POCO
http://msdn.microsoft.com/ja-jp/library/dd456853.aspx

POJO じゃなくて、POCOです、ポコ!

段々、複雑になってきた感に押されそう・・・・

リレーションには、不得意とされているAzure Tableですが、

Rowkey での自動昇順で、単的リレーションは出来る! (はず)



その前に、POCOを少し勉強しないといけないのでしょうね・・・

pubs のAzureサンプル的なものを、次回かその次回か・・にでも報告しますね。


関根:



ExpressionVisitorクラス、VisitMember

clock February 14, 2011 23:01 by author Sekine

Windows Azure Storage テーブルでデータ設計をする。

とにもかくにも、既存業務のデータストアをストレージテーブルに移そうとしてますが、

楽観的同時実行制御が、TimeStampで実行できるというとこから、

まぁ、トランザクションの問題は、書込順序と読取順序を組み合わせれば、

なんとかなりそうなんですね。


で、実際にやってみると、PartionKey、RowKeyをどのように割り当てるか?

という処に、行きつくわけですが、

キーとなる文字を複合的に連結させて一意性を確保するように

まぁ、Storage Table なら当然な作業となるのですが、

string1.Format(...) + string2.format(...) ....っとして書き込み

取得も同じように、文字+文字+ ... と延々とこんな事書いてる自分に

段々と嫌気がさしてきました。



別方面でLinq絡みを調べてたところ、

ExpressionVisitorクラスなるものを見つけ、

クラスのプロパティを走査できる機能があるという。

リフレクションとは違う視点で操作する事も分かり、

うまい使い方をすると、メンバープロパティにアトリビュートを与え、

文字文字文字...のような文字列が作れる機能となる。

アスペクト指向の組み入れで同じような事もできるかと思うが、

割と簡単に実装できそうな予感がして、

Azure Tableに限らず、いろいろな方面でも試したい。



ExpressionVisitorは、

System.Linq.Expressions 名前空間

VisitMember をオーバーライドする。

っと、メモ

.NET Framework 4 からの機能です。

実際の使い方は、次回に。。ペコ


関根:



Sign In