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

表情をCGMする新感覚アバター、クリエモンをバージョンアップしました

Wednesday, 11 August 2010 08:45 by sabro

クリエモンとは、表情をWebで共有できる新感覚のアバター。Web上の目や鼻のパーツを組み合わせて表情を作り、セカンドライフのアバターに適用して使います。セカンドライフの中でも高度なWeb連携を実現しているところが特長です。ちなみに、バックグラウンドはCassandra。
クリエモン

今回のバージョンアップで、具体的には以下のような機能を追加しました。

  • ブックマーク
  • タグクラウド
  • コンビネーションページ
  • フェイスレスポンス
  • ライセンスをパブリックドメインへ変更

詳しい機能解説は、こちらのブログエントリへ。
http://sabro.slmame.com/e943628.html

さらに、クリエモンのプロモーションビデオも、ニコニコ動画へアップ済み! ネタが満載で楽しめる内容になってます。

説明編


活劇編

オリジナルの表情可変アバターを作ることも可能なので、新たなアバターCGMの基盤になる可能性もないこともないかも。気になった方はぜひアクセスしてみてください( ̄∇  ̄ )

クリエモン
http://www.creamon.net/

Tags:   ,
Categories:   Second Life
Actions:   Permalink | Comments (47) | Comment RSSRSS comment feed

Cassandraでbatch_mutateにInsertとDeleteをセットするとどちらが先に実行されるか

Wednesday, 23 June 2010 03:40 by sabro

Cassandraの、batch_mutateには、map<key : string, map<column_family : string, list<Mutation>>>みたいな、引数を渡します。このMutationは、登録用のColumnリストか、削除用のDeletionオブジェクトを格納出来るようになっているので、同じカラムに対して、同時に登録と削除を指定することも出来るわけです。

では、実際に同時に指定すると、どうなるんでしょうか。削除されてから登録されるのか、登録されてから削除されるのか、実験してみました。

// *** 一部、CassandraemonのAPIを使用 *** //

var socket = new TSocket("localhost", 9160);
var protocol = new TBinaryProtocol(socket);
var client = new Cassandra.Client(protocol);

socket.Open();	

// Insert用のMutation作成
var column = new ColumnOrSuperColumn
	   {
		   Column = new Column().SetNameValueTimestamp
				    (
				    "c1",
				     "10",
				     DateTime.Now.Ticks
				    )
	   };
var insertMutation = new Mutation { Column_or_supercolumn = column };

// Delete用のMutation作成
var deletion = new Deletion
	   {
		   Timestamp = DateTime.Now.Ticks,
		   Predicate = new SlicePredicate
		   {
			   Column_names = new List<byte[]>
					   {
						   "c1".ToCassandraByte()
					   }
		   }
	   };
var deleteMutation = new Mutation { Deletion = deletion };


// Insert → Delete順のMap
var map1 = new Dictionary<string, Dictionary<string, List<Mutation>>>()
{
	{
		"k1",
		new Dictionary<string, List<Mutation>>()
		{
			{
			  "cf",
			  new List<Mutation>() { insertMutation, deleteMutation }
			}
		}
	}
};

// Delete → Insert順のMap
var map2 = new Dictionary<string, Dictionary<string, List<Mutation>>>()
{
	{
		"k1",
		new Dictionary<string, List<Mutation>>()
		{
			{
			  "cf",
			  new List<Mutation>() { deleteMutation, insertMutation }
			}
		}
	}
};


// ColumnPathを作っとく
var path = new ColumnPath { Column_family = "cf", Column = "c1".ToCassandraByte() };


// Insert → Delete順のMapを実行して、データを取得してみる
client.batch_mutate("ks1", map1, ConsistencyLevel.ONE);
try
{
	var CorS1 = client.get("ks1", "k1", path, ConsistencyLevel.ONE);
	Console.WriteLine("Found.");
}
catch(NotFoundException)
{
	Console.WriteLine("Not Found.");
}


// Delete → Insert順のMapを実行して、データを取得してみる
client.batch_mutate("ks1", map2, ConsistencyLevel.ONE);
try
{
	var CorS1 = client.get("ks1", "k1", path, ConsistencyLevel.ONE);
	Console.WriteLine("Found.");
}
catch(NotFoundException)
{
	Console.WriteLine("Not Found.");
}

socket.Close();

/* result
Not Found.
Not Found.
*/

Mutationのリストに、登録用のMutationから突っ込んでも、削除用から突っ込んでも、結局は登録したカラムが削除されてしまっています。さらに、上記コードをFor文で囲って100回連続で実行しても、1回も登録の方が後に行われたケースはありませんでした。つまり、削除操作が必ず後で実行されるってことでしょうか。

ソースをちらっと見た感じでは特に順序制御とかしてないような感じだったんですが、削除がメチャクチャ遅いだけだったりして( ̄∇  ̄ ) たしか、Cassandraの削除はフラグを立てるだけだった気がするので、その辺の振る舞いが関係してたりもするのかも。なんにせよ、Updateの感覚でDelete→Insertとかしたいときは、操作を分ける必要があるってことですね。Cassandraemonの場合だと、DeleteOnSubmitのあと、一回SubmitChangesしてから、InsertOnSubmitみたいな手順になります、ご注意ください。

あ、あと、これはあくまで、バージョン0.6.2での結果なんで、他では変わるかもです。

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

Cassandra0.6系では、格納時にKeyがTrimされる

Friday, 18 June 2010 01:40 by sabro

Cassandraemonの新機能、全文検索を実装してたんですが、N-Gramっていう、二文字ずつ区切ってインデックスを作るアルゴリズムで作ってみたら、スペースを含むフレーズ検索がうまくいきませんでした。

で、ちょっと調べてみたら、Cassandraの0.6系では、Keyは格納前にTrimされてるようです。RowMutation.getRowMutationの一行目で確かにTrimされてますねえ。

public static RowMutation getRowMutation(String table, String key, Map<String, List<ColumnOrSuperColumn>> cfmap)
{
    RowMutation rm = new RowMutation(table, key.trim());
    for (Map.Entry<String, List<ColumnOrSuperColumn>> entry : cfmap.entrySet())
    {
        String cfName = entry.getKey();
        for (ColumnOrSuperColumn cosc : entry.getValue())
        {
            if (cosc.column == null)
            {
                assert cosc.super_column != null;
                for (org.apache.cassandra.thrift.Column column : cosc.super_column.columns)
                {
                    rm.add(new QueryPath(cfName, cosc.super_column.name, column.name), column.value, column.timestamp);
                }
            }
            else
            {
                assert cosc.super_column == null;
                rm.add(new QueryPath(cfName, null, cosc.column.name), cosc.column.value, cosc.column.timestamp);
            }
        }
    }
    return rm;
}

つまり、Cassandra0.6系では、N-Gramで、スペースを含んだフレーズ検索は、実装不可能ってことのようです( ̄□  ̄ || 一応、インデックスをSuperColumnで作れば出来るけど、それだと偏りがえらいことなりそうだから、現実的じゃないですしねえ。

ただし、Cassandra0.7系では、Keyがバイト配列になるので、Trimは実行されません。なんで、たぶんスペースを含んでも検索できるんじゃないかなと思います。下記0.7のソースでは引数のkeyがバイト配列なんで、Trimが呼ばれてないことが確認できますね( ̄∇  ̄ )

public static RowMutation getRowMutation(String table, byte[] key, Map<String, List<ColumnOrSuperColumn>> cfmap)
{
    RowMutation rm = new RowMutation(table, key);
    for (Map.Entry<String, List<ColumnOrSuperColumn>> entry : cfmap.entrySet())
    {
        String cfName = entry.getKey();
        for (ColumnOrSuperColumn cosc : entry.getValue())
        {
            if (cosc.column == null)
            {
                assert cosc.super_column != null;
                for (org.apache.cassandra.thrift.Column column : cosc.super_column.columns)
                {
                    rm.add(new QueryPath(cfName, cosc.super_column.name, column.name), column.value, unthriftifyClock(column.clock), column.ttl);
                }
            }
            else
            {
                assert cosc.super_column == null;
                rm.add(new QueryPath(cfName, null, cosc.column.name), cosc.column.value, unthriftifyClock(cosc.column.clock), cosc.column.ttl);
            }
        }
    }
    return rm;
}

そういうわけなんで、全文検索機能のリリースは0.7系が出るまでは、おあずけということになりそうです。

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

Cassandraemon 0.6.0をリリースしました( ̄∇  ̄ )

Saturday, 12 June 2010 11:17 by sabro

自分のプロジェクトへ組み込んで、地味に検証を続けていたCassandraemonですが、先程、バージョン0.6.0をリリースしました。一応ベータ版扱いにしてありますが、バグはだいたい潰し終わったと思います。

Cassandraemon 0.6.0

ドキュメントも頑張って英語で書いてみました。日本語版のドキュメントは今のところありませんが、リクエストがあったらどこかに作ります。

リリースノートにも書いてありますが、Cassandraemonで出来ることの一覧を挙げておきます。

  • LINQクエリでCassandraデータの取得が可能
  • Cassandraへの、登録、削除操作もサポート
  • 使い易い拡張メソッドがたくさん
  • 変態的なConvertメソッドで、Cassandra ByteデータからObjectへ楽々変換
  • TimeUUIDサポート
  • Javaとの相互運用性を考慮して、Big Endian形式でデータ登録

現状の.NETクライアントの中では結構イイ線いってるような気がします。っていうか、LINQで動くCassandraクライアントでまともなやつがないですからねえ。

Cassandraemonのバージョンは、本家Cassandraと連動しています。0.6.0を試すときは、Cassandraも0.6系を使ってください。Cassandraはまだまだ変化が激しいので、多分0.7になったら動かないと思います。Cassandraコミュニティの活発さを見ると、0.7もすぐにリリースされそうなので、はやくも追従していく必要がありそうです( ̄□  ̄ ||

それとは別に、Cassandraemon独自の機能追加も考えています。具体的に上げると

  • コネクションプーリング、ロードバランシング
  • リフレクションによるマッピングを、高速マッパーライブラリで速度改善
  • 全文検索機能

などです。

結構異質なのが、全文検索機能ですが、うちのプロジェクト「クリエモン」では、今までToritonnを使った全文検索を実装していたので、Cassandraへ移行するに当たって、そこを何とかしないといけないわけです。Lucandraとかもあるけど、複数のライブラリを使いまわすのもメンドくさそうですし、開発することにしました。さぶろーは全文検索とかほとんど知らないので、仕組みとしては簡単な物になると思います。

とりあえず報告はこんなところです。それでは、充実したカサンドライフ(セカンドライフみたいだ・・・)をお過しください( ̄∇  ̄ )

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

Javaと.NETのエンディアンの違いでCassandraの互換性崩壊( ̄□  ̄ ||

Friday, 4 June 2010 02:23 by sabro

昨日の件ですが、Javaと.NETでは、数値をバイト配列にした際の順序はやはり逆になるようです。バイト配列の格納方式はエンディアンといって、先頭から素直に並べるビッグエンディアンと、逆順に並べるリトルエンディアンがあります。

Javaの場合は常にビッグエンディアンとなり、.NETの場合は、CPUによって並べ方が変わるようです。x86系のCPUでは、リトルエンディアンになるみたいですね。.NETでは、BitConverterクラスに、IsLittleEndianというフィールドがあるので、それを見て、自前でバイト配列を並べ替えることで対処は出来るみたいです。

で、Cassandraemonで数値を格納するときにどうするか決めないといけないわけです。少なくともLongTypeのカラム名に格納する際は、ソートできるようにビッグエンディアンへの変換が必要ですね。他の場合はどうするか・・・。Thrift側でやってくれたらよかったんだが。

1、すべての数値を格納前にビッグエンディアンへ変換する

とりあえず、数値をToCassandraByte関数などでバイトへ変換するときは、常にビッグエンディアンにしてしまうというやり方がまず考えられます。このやり方だと、カラム名だけでなくカラムの値もビッグエンディアンになります。この方式のいいところは、Javaと相互運用が可能な点です。常にビッグエンディアンが格納されるので、Javaからも問題なく読みだすことができるはず。ただ、完全ではなく、数値を自前で野良バイト変換してCassandraへ格納した場合に、整合性が取れなくなることはありますね。

2、LongTypeのカラム名に格納するときだけビッグエンディアンへ変換

Cassandraemon側で、LongTypeのカラム名登録の場合だけ、自動でバイト配列を逆順にするやり方です。.NETから見た場合の相互運用性が高くなります。他の.NETクライアント、例えばhectorsharpなどから、カラム名以外は違和感なく値の取得ができると思います(そういえば、hectorsharpでは、この問題どうしてんだろ)。

3、自動的な変換はやらない

Cassandraemon側では一切変更を行わず、リトルエンディアン変換関数とかを用意しておき、LongTypeカラム名登録時は各開発者に自分で変換を行ってもらうやり方です。面倒ではありますが、どのカラムにどのエンディアンで格納されているかを開発者側が管理出来ると言うメリットがあります。ただ、値取得時に、このカラムはどのエンディアンかを意識して変換する必要もあるので、大人数での開発などでは混乱も起こるかもしれません。

以上、やり方考えてみましたが、どれにしようかなあ( ̄□  ̄ ) 個人的には1か3を考えてますが、みなさん希望とかありますでしょうか( ̄∇  ̄ )

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

Cassandraでカラム名に数値を格納するときBytesType指定は可能か?

Thursday, 3 June 2010 10:24 by sabro

追記: 以下の文章では、結局BytesTypeではうまく取得出来ないから、LongTypeを使いましょうということが書いてありますが、LongTypeでもうまく取得出来ないようです。Cassandraemonのバグの可能性もあるので、詳しく調べ中です。

追追記: 試しにJassandraで、.NETからLongTypeに登録した値を取ってみたら、なぜか「72057594037927936」みたいな値が取れました。これを16進数に変換してみると、なんと「100000000000000」。もしかして、1で登録したやつが逆になってる・・・( ̄□  ̄ || とてつもなくイヤな予感がします。

追追追記: .NETでの登録時に、Reverse関数でバイト配列を逆順にしてやると、なんか想定通りの動きをしました・・・。あれ、ほんとにJavaと.NETって、数値から作ったバイト配列が逆順になるってことでしょうか( ̄□  ̄ || 詳しい方カモン!

追追追追記: 調べてみました。→ 次のエントリへ

Cassandraでは、カラム名の比較方法として、以下の6つが選べます。(本家サイトより引用)

  • BytesType: Simple sort by byte value. No validation is performed.
  • AsciiType: Like BytesType, but validates that the input can be parsed as US-ASCII.
  • UTF8Type: A string encoded as UTF8
  • LongType: A 64bit long
  • LexicalUUIDType: A 128bit UUID, compared lexically (by byte value)
  • TimeUUIDType: a 128bit version 1 UUID, compared by timestamp

カラム名に数値を入れたいときは、普通はLongTypeを使うのですが、今日、LongTypeのカラムに4バイトのint型を突っ込んだら、8バイトじゃないとダメよってエラーがでました( ̄□  ̄ || まあ、普通に対処するなら、バイト変換する前にLong型にキャストしてやればいいんですが、数値を格納する際に毎回キャストするのも面倒なので、比較タイプをBytesTypeにすることでキャストしなくてもいい具合に動いてくれないかどうか調べてみました。

BytesTypeの動作は、「org.apache.cassandra.db.marshal.BytesTypeクラス」で規定されてるようです。

public class BytesType extends AbstractType
{
    public int compare(byte[] o1, byte[] o2)
    {
        return FBUtilities.compareByteArrays(o1, o2);
    }

    public String getString(byte[] bytes)
    {
        return FBUtilities.bytesToHex(bytes);
    }
}

FBUtilitiesクラスっていうのを使ってるみたいですね。「org.apache.cassandra.utils.FBUtilitiesクラス」のようです。

public static int compareByteArrays(byte[] bytes1, byte[] bytes2){
    if(null == bytes1){
        if(null == bytes2) return 0;
        else return -1;
    }
    if(null == bytes2) return 1;

    int minLength = Math.min(bytes1.length, bytes2.length);
    for(int i = 0; i < minLength; i++)
    {
        if(bytes1[i] == bytes2[i])
            continue;
        // compare non-equal bytes as unsigned
        return (bytes1[i] & 0xFF) < (bytes2[i] & 0xFF) ? -1 : 1;
    }
    if(bytes1.length == bytes2.length) return 0;
    else return (bytes1.length < bytes2.length)? -1 : 1;
}

むう、先頭から順番に、どちらかの配列の終わりに達するまで比較して、それでも同じだったら、配列長の長い方が勝ちとなるようです。

次に、.NETで、BitConverterを使って、バイト配列に変換したとき、どんな感じに格納されるのか知らなかったので、実験してみました。

static void Main(string[] args)
{
	uint u1 = 10;
	uint u2 = 257;
	long l1 = 10;
	long l2 = 257;

	var bu1 = BitConverter.GetBytes(u1);
	var bu2 = BitConverter.GetBytes(u2);
	var bl1 = BitConverter.GetBytes(l1);
	var bl2 = BitConverter.GetBytes(l2);

	Console.WriteLine(bu1[0] + " " + bu1[1] + " " + bu1[2] + " " + bu1[3]);
	Console.WriteLine(bu2[0] + " " + bu2[1] + " " + bu2[2] + " " + bu2[3]);
	Console.WriteLine(bl1[0] + " " + bl1[1] + " " + bl1[2] + " " + bl1[3] + " " + bl1[4] + " " + bl1[5] + " " + bl1[6] + " " + bl1[7]);
	Console.WriteLine(bl2[0] + " " + bl2[1] + " " + bl2[2] + " " + bl2[3] + " " + bl2[4] + " " + bl2[5] + " " + bl2[6] + " " + bl2[7]);
}


/*
結果
10 0 0 0
1 1 0 0
10 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0
*/

どうやら、バイト配列の先頭から格納されていくようです。しかし、256単位で繰り上がるので、10を変換した場合と、257を変換した場合では、バイト配列の1つめは、前者の方がおおきくなっていますね。これだと、多分、BytesTypeの比較では上手くいかない気がします。

ためしに実験。設定ファイルを以下のように定義。

<ColumnFamily Name="BytesType" CompareWith="BytesType" />

以下がテストコード。Cassandraemonを使っています。分かりにくいかもしれないですが、通常のThriftインターフェースを使うと、とんでもなく長くなりそうだったので・・・。

public static void Main (string[] args)
{
	using(var context = new CassandraContext("localhost", 9160, "Test"))
	{
		// 1, 10, 257 の、3カラムを生成
		var list = new List<Column>();
		list.Add(1, "")
			.Add(10, "")
			.Add(257, "");
			
		// 登録用エンティティ作成
		var entity = new CassandraEntity<List<Column>>()
				.SetColumnFamily("BytesType")
				.SetKey("hoge")
				.SetData(list);
				
		// 登録
		context.ColumnList.InsertOnSubmit(entity);
		context.SubmitChanges();
		
		// 1以上のデータを取得
		var q1 = from x in context.ColumnList
				 where x.ColumnFamily == "BytesType" &&
					   x.Key == "hoge" &&
					   x.Column.GreaterThanOrEqual(1)
				 select x;
				 
		Console.Write("条件 1以上 ");
		foreach(var e in q1)
		{
			foreach(var column in e.Data)
			{
				Console.Write(column.Name.ToInt32() + " ");
			}
			Console.WriteLine();
		}
		
		// 9以上のデータを取得
		var q2 = from x in context.ColumnList
				 where x.ColumnFamily == "BytesType" &&
					   x.Key == "hoge" &&
					   x.Column.GreaterThanOrEqual(9)
				 select x;
				 
		Console.Write("条件 9以上 ");
		foreach(var e in q2)
		{
			foreach(var column in e.Data)
			{
				Console.Write(column.Name.ToInt32() + " ");
			}
			Console.WriteLine();
		}
	}
}


/*
条件 1以上 1 257 10 
条件 9以上 10 
*/

9以上の値を取得しようとしてるのに、257が取れてこないですね。検証は正しかったようです。

というわけで、数値タイプをカラム名にするときは、素直にLongTypeを使いましょうということですね( ̄∇  ̄ ) で、格納するときは毎回ちゃんとlongにキャストするか、そもそもModelクラスのフィールドには基本的に、long型を使うようにするのがいいと思います(あくまで.NETの場合)。

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

Zookeeperメモ

Sunday, 16 May 2010 18:17 by sabro

Zookeeperが、けっこう独自ルール多いので、忘れないようにメモ。英語のドキュメント読んで理解してるから、間違ってる可能性あり。ちなみに、.NETドライバでの場合です。

  • Zookeeperクラスのインスタンスは、何度も使いまわすことが可能
  • Zookeeperクラスのコンストラクタに渡す、セッションタイムアウト値は、あくまでも一回の通信におけるタイムアウト値であって、Zookeeperインスタンスをタイムアウト値以上の時間保持しても、インスタンスが使えなくなることはない
  • Zookeeperクラスのコンストラクタに渡す、セッションタイムアウト値は、設定ファイルのtickTime値の2倍以上、20倍以下でないといけない
  • Zookeeperクラスのコンストラクタに渡すWatcherはデフォルトのウォッチャーであり、接続状態が変わる度にイベントが起こる、Zookeeperインスタンス生成時は、Connectionが非接続から接続に変わるので、いきなりイベント発生するので注意
  • Zookeeper.Exists、Zookeeper.GetData、Zookeeper.GetChildrenに渡すWatcherは観測対象のノードに変化があったときにイベント発生、イベントは1回しか起きない
  • 接続文字列には、複数のホストをカンマ区切りで指定可能、その場合は再接続のたびに適当に接続先を選んでくれる、またホストに接続出来なかった場合、別のホストへつないでくれる
  • クライアントアプリから接続後、クラスタを再起動しても、Zookeeperインスタンスを作りなおすことなく再接続できた、うまく再接続する仕組みになってるっぽい
  • Zookeeper.Deleteメソッドに渡すデータのバージョンは、-1を渡してやると、どんなバージョンでも削除してくれる、Zookeeper.SetDataのバージョンも同様
  • CreateModeで、PersistentSequentialや、EphemeralSequentialを指定すると、Node名の末尾に連番をつけてくれるが、これはNode名ごとではなく、ツリー全体で1つの連番を使う、"node1","node2"と作ったあとに"tmp3"を作ると、次は"node4"になる、普通アプリには複数の連番IDが必要なので、この機能で連番を作るのは厳しい

注意点も多いけど、再接続とかを、うまいことやってくれるのは、いい感じですね( ̄∇  ̄ )

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

Zookeeperを.NETから使ってみる

Thursday, 13 May 2010 15:11 by sabro

Cassandraemonの正式版はもう少しお待ちを。実際にうちのサービスに組み込んでみて、安定して動くことを確認してからリリースします。

で、今組み込み作業中なんですが、CassandraだとMySQLでやってた連番生成が出来ないことに気づきました。Diggは、ZooKeeperを使ってやってるみたいです。TwitterはMySQLなのかな。

Zookeeperは、分散システムを協調動作させるために、Hadoopチームによって作られたライブラリです。データ構造は、ファイルシステムのようなツリー構造になっていて、それぞれのノードに対して、アトミックで順序保証されたアクセスが可能とのこと。

でも、Zookeeperは今のところ正式なものとしては、Java、C言語しかインターフェースが用意されていません。一応Http Restでの呼び出しでZookeeperにアクセスできる実験的機能があるみたいですが、これはWatcherがJavascriptからしか使えないとか制限があるみたいですね。

これは諦めるしかないかなと思ってたんですが、調べてみると本家ZookeeperをForkして、.NETドライバを作ってる方がいました!

http://github.com/ewhauser/zookeeper

最終更新日が5月4日。まだ作ってる最中かもしれないですね。本家にマージされたりしないのかな( ̄∇  ̄ )

Gitでソース取ってきて、MonoDevelopで開いたところ、いくつかソースが足りません。どうやら、Antでソースを作る必要があるみたいなので、トップディレクトリでAnt実行。すると、無事ソースコードが作られてビルド成功しました(^^)

それでは、せっかくなんでC#で接続してみます。Zookeeperのインスタンスを作るには、IWatcherインターフェースを実装したクラスを自作しないといけないみたいなんで、適当にでっちあげます。(追記:Watcherが不要なら、Nullを渡してもいいみたいです)

public class DefaultWatcher : IWatcher
{
	public void Process (WatchedEvent @event)
	{
		Console.WriteLine(@event.Path);
		Console.WriteLine(@event.Type);
		Console.WriteLine(@event.State);
	}
}

最初は、ZNodeを作る例です。Zookeeperのコンストラクタには、接続ホスト名&ポート、セッションタイムアウト、でっちあげたWatcherを渡します。Createメソッドでは、ノードパス、格納データ、接続ACL、CreateModeを渡してます。接続ACLはいくつか定数で用意されてたので、そのまま使いました( ̄∇  ̄ )

using(var zookeeper = new ZooKeeper("RemoteLinuxServer:2181", 
                                    new TimeSpan(0,1,0), 
                                    new DefaultWatcher()))
{
	var response = zookeeper.Create("/node1", 
	                                Encoding.UTF8.GetBytes("data1"), 
	                                Ids.OPEN_ACL_UNSAFE,
	                                CreateMode.Persistent);
	                                

	Console.WriteLine("Create : " + response);
}

// ### result ###
// Create : /node1

接続先のLinuxサーバ側で確認してみると、ルートディレクトリに「node1」が作られていました。今度は、Linuxサーバ側で、node2を作ってみます。

[root@RemoteLinuxServer bin]# ./zkCli.sh -server 127.0.0.1:2181
[zk: 127.0.0.1:2181(CONNECTED) 1] ls /
[node1, zookeeper]
[zk: 127.0.0.1:2181(CONNECTED) 2] create /node2 data2
Created /node2

再び、C#に戻って、GetChildrenメソッドにパスと、Watchフラグを渡して実行。Linuxサーバ側で追加したnode2が取れてます。

using(var zookeeper = new ZooKeeper("RemoteLinuxServer:2181", 
                                    new TimeSpan(0,1,0), 
                                    new DefaultWatcher()))
{
	var responses = zookeeper.GetChildren("/", false);

	foreach(var res in responses)
	{
		Console.WriteLine("GetChildren : " + res);
	}
}

// ### result ###
// GetChildren : node2
// GetChildren : node1
// GetChildren : zookeeper

うーん、グッジョブと言わざるをえない。とりあえず、もうちょっといじってみたいと思います( ̄∇  ̄ )

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

DateTime.Ticksの精度は100ナノ秒ではない

Thursday, 6 May 2010 07:41 by sabro

DateTime.Ticksプロパティの説明には

「このプロパティの値は、0001 年 1 月 1 日午前 00:00:00 (DateTime.MinValue を表します) 以降の経過時間 (100 ナノ秒単位) を表します。」

って書いてあります。でも、これは桁数がそれだけ用意してありますよっていうだけで、実際は、動作OSによって精度が異なるみたいです。

ちょっとうちのVistaで実験

static void Main(string[] args)
{
	for (int i = 0; i < 100; i++)
	{
		Console.WriteLine(DateTime.Now.Ticks);
	}
}

こんなかんじで実行したところ、結果は以下のような感じでした。

634086940404282076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404312076
634086940404322076
634086940404322076
634086940404322076
634086940404322076
634086940404322076
634086940404322076
634086940404322076
634086940404322076
634086940404322076
:
:

下4桁は固定で、1ミリ秒単位で増加するようです。うっかりMSDNの内容をうのみにしないように気をつけないといけませんね。

こっから蛇足。

なんで、こんな実験したかっていうと、作成中のCassandra LINQプロバイダ、Cassandraemonで、TimeUUIDをキーにする必要があったからなんです。TimeUUIDは、現在の100ナノ秒単位のタイムスタンプから生成されるIDのようなもので、かなりの確率で複数マシン間で一意になることになってます。

ところが、これをDateTime.Ticksから生成すると、1ミリ秒単位なので結構な確率で同じものが生成されてしまうんですね。これでは、テーブルのキーとして使うには厳しいわけです。

うーん、内部でミリ秒以下は適当に生成するとかで凌ぐしかないのかなあ。なんかいいアイデアないでしょうか( ̄∇  ̄ )

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

CassandraのLINQProvider作ったよ( ̄∇  ̄ )

Friday, 23 April 2010 21:03 by sabro

今をときめくCassandraの、LINQプロバイダを作ってみました( ̄∇  ̄ )

Cassandraemon in CodePlex
Cassandraemon

こんな感じで、データを取ってこれます。

public class Product
{
	public int ID { get; set; }
	public string Name { get; set; }
}

	
using(var context = new CassandraContext("localhost", 9160, "KeySpace1"))
{
	var products = from x in context.ColumnList
		       where x.Key == "1" &&
			     x.ColumnFamily == "Product"
		       select x.ToObject<product>();
				   
	foreach(var p in products)
	{
		Console.WriteLine(p.ID.ToString());
	}
}

一応、更新系もサポートしています。より詳しい解説はドキュメントページを見てください。

まだとりあえずクエリ、更新処理が動いたことを確認できただけの段階です。全く使い込んでないのでバグもあるかも。ある程度使ってみて安定してきたら、ちゃんと正式版用意すると思います。

今後の開発方針ですが、さぶろーはセカンドライフ系サービスの開発が忙しいため、Cassandraemonに関しては、積極的なコミットはしない予定です。本家のバージョンアップで追加されたAPIくらいには対応したいですけどね。

ソースコード管理はMercurialなので、機能追加されねーって場合は、自分でForkして作ってみるのも一興かもしれません。

ちなみに、プロジェクト名の由来は、セカンドライフで絶賛稼働中の、拙作、萌え系アバターサービス「クリエモン」からです。

クリエモン

今までクリエモンのバックグラウンドではMySQLを使っていたのですが、メンテナンスが大変なのと、NoSQL使ってみたかったという理由から、Cassandraに切り替えることにしました。クリエモンで使うCassandraドライバなので、Cassandraemonというわけです。某国民的アニメとは全く関係ないのでご注意ください( ̄∇  ̄ )

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