ヒマをみつけてWeb開発
その場の思い付きを、ヒマをみつけてWebサイトにしてみるブログ

Cassandraemon 1.0.6をリリースしました

Friday, 23 December 2011 21:01 by sabro

0.8.7をリリースしたときに1.0はしばらく先になりますと書いたのですが、Cassandra1.0はThrift APIのインターフェースにほとんど変更がなかったことと、メンバーの@kojiishiさんが頑張って新機能を作ってくれたこともあって、早くもリリースすることになりました( ̄∇  ̄ )

Cassandraemon - Download: Cassandraemon 1.0.6

オブジェクトマッピングの高速化

Cassandraemonでは、オブジェクトを登録するとき、1プロパティを1カラムに格納するのですが、これまではリフレクションを使っていました。しかし、これでは遅いのでReflection.Emitで動的に変換用のアセンブリを作る方針に変更。パフォーマンスが大幅にアップしました。他にも、値型をByte配列に変換するロジックなどにも手を入れて、細かな高速化を行っています。

Serialize方法をカスタム可能に

オブジェクトをプロパティごとにシリアライズして格納する際、たとえば特定のプロパティを格納しないなどのカスタマイズが可能になりました。

// IgnoreDataMemberAttributeがついていないプロパティを取得
var properties = type.GetProperties()
     .Where(p => !p.GetCustomAttributes(
         typeof(IgnoreDataMemberAttribute), true).Any());

// Reflection.Emitを内部で使用するCompiledCassandraSerializerFactoryを使う
var factory = CompiledCassandraSerializerFactory.Default;

// 型とシリアライズするプロパティを渡してシリアライザを作る
var serializer = factory.CreateSerializer(type, properties);

// シリアライザをセット
factory.SetSerializer(type, serializer);

より詳しくは、公式のドキュメントをお読みください。

さて、次回のリリースは本当に先になるはず。気長にお待ちください( ̄□  ̄ ||

Tags:   , , , ,
Categories:   NoSQL
Actions:   Permalink | Comments (4) | Comment RSSRSS comment feed

Cassandraemon0.8.7をリリースしました

Saturday, 3 December 2011 20:44 by sabro

Cassandraemonの初の0.8系対応バージョンをリリースしました。

Cassandraemon - Download: Cassandraemon 0.8.7

Thrift APIが0.8.7バージョンのものを使っているからこんなバージョンになってますが、実質的にCassandra0.8に対応する初のリリースです。新機能は、New Feature - ver 0.8に書いてありますが、英語なので、簡単に日本語で説明しときます。

CQL

Cassandra0.8のウリであるCQLに対応しました。

using(var context = new CassandraContext("localhost", 9160, "Keyspace1"))
{
	// insert
	string insertCql = "insert into ColumnFamily1 (KEY, name1, name2) values ('key1', 'value1', 'value2')"; 
	CqlResult insertResult = context.ExecuteCqlQuery(insertCql);
	
	// get count
	string countCql = "select count(*) from ColumnFamily1 where KEY = 'key1'";
	CqlResult countResult = context.ExecuteCqlQuery(countCql);
	Console.WriteLine(countResult.Num);	// 2
	
	// get data
	string retrieveCql = "select * from ColumnFamily1 where KEY = 'key1'";
	CqlResult retrieveResult = context.ExecuteCqlQuery(retrieveCql);
	
	var dictionary = retrieveResult.ToFlatDictionary<string, string>();
	dictionary.ToList().ForEach(kv => Console.WriteLine(kv.Key + " = " + kv.Value));
	// name1 = value1
	// name2 = value2
}

CQLの結果は、Thriftで生成したコードで定義されているCqlResultで受け取るのですが、そのままだと扱いにくいので、Object、List、Dictionaryに変換できる拡張メソッドを用意してあります。

ToCassandraEntiry ()
ToFlatNameList<T> ()
ToFlatValueList<T> ()
ToFlatDictionary<TKey, TValue> ()
ToFlatValueKeyDictionary<TKey, TValue> ()
ToNameListDictionary<TKey, TListItem> ()
ToValueListDictionary<TKey, TListItem> ()
ToObjectList<T> ()
ToObjectDictionary<TKey, TValue> ()

Counter

Cassandra0.8の、もうひとつのウリであるCounterです。普通のColumnと同様の感覚で使えます。

// insert 
var cc1 = new CounterColumn().SetNameValue("one", 1);
var cc2 = new CounterColumn().SetNameValue("two", 2);
var cc3 = new CounterColumn().SetNameValue("three", 3);

context.InsertOnSubmit("CounterColumnFamily1", "key1", new[]{cc1, cc2, cc3});
context.SubmitChanges();


// get data
var query = from x in context.CounterColumnList	// <- attention "CounterColumnList"
	    where x.ColumnFamily == "CounterColumnFamily1" &&
		  x.Key == "key1"
	    select x.ToFlatDictionary<string, long>();

query.First()
     .ToList()
     .ForEach(kv => Console.WriteLine(kv.Key + " = " + kv.Value));
// one = 1
// three = 3
// two = 2

Eventを追加

Eventをいくつか追加しました。サーバに接続したタイミング、切断したタイミングなどで処理を実行できます。

ConnectionPool.Connected += (sender, e) => Console.WriteLine("Connected");
ConnectionPool.ConnectionFailed += (sender, e) => Console.WriteLine("ConnectionFailed");

CassandraContext.Created += (sender, e) => Console.WriteLine("Created");

using(var context = new CassandraContext("localhost", 9160, "Keyspace1"))
{
	context.Executed += (sender, e) => Console.WriteLine("Executed");
	context.Disposed += (sender, e) => Console.WriteLine("Disposed");
	
	var column = new Column().SetNameValue("name1", "value1"); 
	context.InsertOnSubmit("ColumnFamily1", "event", column);
	context.SubmitChanges();
	
	Console.WriteLine("Submitted");
}
// Connected
// Created
// Executed
// Submitted
// Disposed

新しいInsert API

Insertするとき、今まではCassandraEntityオブジェクトを作成しないといけなかったのですが、ColumnFamily、Keyを別途指定できるメソッドを用意しました。いちいちCassandraEntityオブジェクトを作らなくても、Column、SuperColumnなどを直接登録できます。

using(var context = new CassandraContext("localhost", 9160, "Keyspace1"))
{
	// old version
	context.Column.InsertOnSubmit(cassandraEntity);
	context.SuperColumn.InsertOnSubmit(cassandraEntity);
	context.ColumnList.InsertOnSubmit(cassandraEntity);
	context.SuperColumnList.InsertOnSubmit(cassandraEntity);

	// new version
	context.InsertOnSubmit( "ColumnFamily1", "key1", column );
	context.InsertOnSubmit( "ColumnFamily2", "key2", superColumn );
	context.InsertOnSubmit( "ColumnFamily3", "key3", columnList );
	context.InsertOnSubmit( "ColumnFamily4", "key4", superColumnList );
}

CassandraConnectionConfig

設定項目が増えてきたので、CassandraConnectionConfigにまとめました。CassandraConnectionConfigBuildeクラスを使って生成できます。

var builder = new CassandraConnectionConfigBuilder
{
	Hosts = new [] { "localhost" }
	Port = 9160,
	ConsistencyLevel = ConsistencyLevel.QUORUM,
	Timeout = TimeSpan.FromSeconds(100),
};
var config = new CassandraConnectionConfig(builder);
using (var ctx = new CassandraContext(config)) { ... }

設定項目の一覧です

Name Description Default Value
Hosts 接続するホスト string[] { "localhost" }
Port Thriftのポート番号 9160
Keyspace 接続するKeyspace "system"
ConsistencyLevel デフォルトのConsistencyLevel ( 実行中にいつでも再設定できます ) ConsistencyLevel.One
RetryCount 接続エラー時のリトライ数 -1 ( this mean nodecount * 2 )
IsFramed フレーム転送使用 true
Timeout 接続待ちのタイムアウト値 10 seconds
MinPoolCountPerHost プールするコネクションの最小値 2
MaxPoolCountPerHost プールするコネクションの最大値 int.MaxValue
Node Node取得の方式 empty string

ConnectionPoolの修正

コネクションプーリングが不安定だったのですが、地道にバグを取り、テストコードも追加して、安定性が向上しました。

今回のリリースでは、新たにメンバーに加わった@kojiishiさんに、コネクションプーリングを中心に色々手伝って頂きました。さて、やっと0.8系をリリースできましたが、セカンドライフ向けWebサービスの開発が忙しいので、1.0系はまだ少し先になりそうな感じです( ̄□  ̄ ||

Tags:   , , , ,
Categories:   NoSQL
Actions:   Permalink | Comments (0) | Comment RSSRSS comment feed

Cassandraemon初の0.7系リリース

Wednesday, 13 April 2011 04:34 by sabro

Cassandraemonの初の0.7系対応バージョンをリリースしました。

Cassandraemon - Download: Cassandraemon 0.7.4

バージョンが0.7.4になってますが、Thrift APIが0.7.4バージョンのものを使っているからであって、実質的にCassandra0.7に対応する初のリリースです。新機能は、New Feature - ver 0.7に書いてありますが、英語なので、簡単に日本語で説明しときます。

New Link Style

Cassandraのカラム名はByte配列であるため、Cassandraemon0.6では、比較の際、ToCassandraByte関数を呼んだり、GreaterThanOrEqual関数を呼んだりする必要がありました。0.7ではCassandraBinaryという、各種演算子をオーバーロードしたByte配列のラッパークラスを作成することで、LINQをシンプルに書けるようにしました。

// ver 0.6
from x in context.ColumnList
where x.ColumnFamily == "Product" &&
      x.Key == "key1" &&
      x.SuperColumn == "sc1".ToCassandraByte() &&
      x.Column.GreaterThanOrEqual(1)
select x;

// ver 0.7
from x in context.ColumnList
where x.ColumnFamily == "Product" &&
      x.Key == "key1" &&
      x.SuperColumn == "sc1" &&
      x.Column >= 1
select x;

Secondary Index

Cassandra0.7の目玉機能、Secondary Indexに対応しました。Where句に以下のように指定することで、事前に作られたインデックスを使って値を取得できます。

from x in context.ColumnList
where x.ColumnFamily == "Product" &&
      x.Index["ColumnName1"] == "col1" &&
      x.Index["ColumnName2"] >= 1 &&
      x.Index["ColumnName3"] < DateTime.Now &&
      x.Column.In("ColumnName1", "ColumnName2")
select x;

Live Schema Update

Live Schema Updateに対応しました。といっても、ThriftのApiをラップしたものを、CassandraContextクラスに用意しただけです・・・。

class CassandraContext
{
	public string SystemAddKeyspace(KsDef ksDef)
	public string SystemUpdateKeyspace(KsDef ksDef)
	public string SystemDropKeyspace(string keyspace)
	public string SystemAddColumnFamily(CfDef cfDef)
	public string SystemUpdateColumnFamily(CfDef cfDef)
	public string SystemDropColumnFamily(string columnFamily)
	public void Truncate(string columnFamily)
}

TTLに対応

カラムの生存時間を指定するTTLに対応しました。といっても、ウチが何かしたわけじゃなくて、ThriftAPIのColumnクラスにTTLプロパティが追加されただけです。一応、List<Column>クラスに、全てのリスト内のColumnに対してTTLをセットする拡張メソッドを追加したりはしてます。

multiget_coutに対応

Keyを複数指定して、CountColumn<TKey>メソッドを呼ぶことで、複数行のCountを一気に取ってこれます。multiget_count関数はThriftApiに用意されているんですが、なぜか今のところ公式のドキュメントに記載がないみたいです。

var query = from x in context.ColumnList
            where x.ColumnFamily == "Product" &&
                  x.Key.In("key1", "key2") &&
                  x.Column >= 1
            select x;

Dictionary<string, int> d = query.CountColumn<string>();

さて、やっと0.7系をリリースできましたが、Cassandraemonは、0.8が出るまでしばらく沈黙が続きそうな感じです( ̄□  ̄ ||

Tags:   , , , ,
Categories:   NoSQL
Actions:   Permalink | Comments (15) | Comment RSSRSS comment feed

CassandraのC#ドライバをThriftで生成する

Wednesday, 17 March 2010 22:19 by sabro

Thriftは、特殊な言語で書かれたテンプレートから、C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C# などの、コードを生成できるフレームワークです。FaceBookによって作られたらしいです。

FaceBookは、Cassandraというストレージを採用しているんですが、それにC#でアクセスするためのドライバは、Thriftを使って生成するようになってます。

Thriftをインストールには、いくつかライブラリをいれておく必要があります。Installing the required packages on CentOS 5にあるように、CentOSでは以下のコマンドでおっけーみたいですね。

sudo yum install automake libtool flex bison pkgconfig gcc-c++ boost-devel libevent-devel zlib-devel python-devel ruby-devel

※ C#のコードを生成する場合には、あとMono 1.2.4以上を入れとく必要があるみたいなんで注意です。

Thriftのソースはここからダウンロード出来ます。解凍して、お決まりのパターンでコンパイルします。./configureには色々オプションもあるみたいですが、とりあえずデフォルトでやりました。あ、今回もPaco使いました。

# ./configure
# make
# paco -D make install

インストールが終わったら、Cassandraのパッケージに入っている「cassandra.thrift」のあるディレクトリへ移動して、以下のコマンドを実行します。

# thrift --gen csharp cassandra.thrift

すると、「./gen-csharp/Apache/Cassandra」以下にコードが生成されます。あとは、これをビルドしてやるわけですが、ここにあるコードは、Thrift.dllを参照しているので、それをどっっかから持ってこないといけません。

実は、それはダウンロードしたソースの中にあります。「thrift/lib/csharp/src」の中に、コードとslnファイルがあるので、それをVisualStudioなどで開いてビルドしてやると、Thrift.dllが生成されます。

Thrift.dllを参照して、生成されたコードをビルドしてやれば、Cassandraドライバの出来上がりです( ̄∇  ̄ )

Tags:   , , , ,
Categories:   NoSQL
Actions:   Permalink | Comments (45) | Comment RSSRSS comment feed

CGIのURLエンコードと、JavascriptのURLエンコードは微妙に違う

Wednesday, 22 July 2009 07:48 by sabro

クリエモンでURLエンコード周りのバグを出してしまいました( ̄□  ̄ ||

原因はCGIとJavascriptでURLエンコードが微妙に違うことでした。WikipediaのURLエンコードのページをみると、Javascriptでは、スペースは「%20」にエンコードされるが、CGI側では普通は「+」に変換されると書いてあります。なんでそんなややこしいことになってんだろ。これって知らないの僕だけで他のWeb開発者の間では常識なのかな(_ _ ||

とりあえず、おかしくなっていた部分では、HttpUtilityクラスのUrlEncodeメソッドが使われていました。以下はPowerShellでちょっと確認してみたところ。HttpUtilityクラスは、そのままでは参照できないんでリフレクションで「System.Web.dll」を読み込んでいます。


PS C:> [System.Reflection.Assembly]::LoadWithPartialName("System.Web")

GAC    Version        Location
---    -------        --------
True   v2.0.50727     C:\Windows\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll

PS C:> [System.Web.HttpUtility]::UrlEncode("<hoge fuga>")
%3choge+fuga%3e


PowerShell覚えると、インタプリタで動作確認ができちゃって楽ちんだな~( ̄∇ ̄) で、結果を見ると確かに「+」にエンコードされていますね。

調べてみると、UriクラスのEscapeDataStringメソッドを使えばJavascript形式のエンコードが出来るらしいとのこと。どれどれ


PS C:> [Uri]::EscapeDataString("<hoge fuga>")
%3Choge%20fuga%3E


おお、たしかに「%20」にエンコードされました(^^) 結局、UrlEncodeメソッドを使用している箇所を、のきなみEscapeDataStringメソッドに置き換えて、問題は解決したのでした。

ちなみに、UriクラスにはEscapeUriStringというメソッドもあります。こちらがどんな動作をするのか気になったので、サクッと確認してみました。


PS C:> [Uri]::EscapeDataString("http://www.hogefuga.com/<hoge fuga>")
http%3A%2F%2Fwww.hogefuga.com%2F%3Choge%20fuga%3E

PS C:> [Uri]::EscapeUriString("http://www.hogefuga.com/<hoge fuga>")
http://www.hogefuga.com/%3Choge%20fuga%3E


EscapeDataStringメソッドは、URLのコロンやスラッシュもエンコードしてしまいますが、EscapeUriStringメソッドは、URLを構成するための記号はエンコードしていません。すでに、URLになっている文字列に対してエンコードしたい時はこちらを使えばいいわけですね( ̄∇  ̄ )

Tags:   , , ,
Categories:   .NET
Actions:   Permalink | Comments (48) | Comment RSSRSS comment feed