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

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 (0) | 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 (0) | 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 (1) | 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 (0) | 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 (0) | 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 (0) | 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 (0) | Comment RSSRSS comment feed

列指向データベース、CassandraとHBaseを比較

Thursday, 25 March 2010 16:52 by sabro

Cassandraが気になっていたんですが、同じ列指向データベースのHBaseはどうなんだろうと思って、簡単に比較してみました。

データの一貫性
NoSQL系は、そもそもRDBMSに比べて一貫性がゆるめに設計されている感じがあります。実際、Cassandraは、行ロックがなくトランザクションが使えません。複数のクライアントからの更新が被った場合は、タイムスタンプを見て最新のものを現在の値に設定するようになっています。結果整合性っていうみたいですね( ̄∇  ̄ ) ただ、まったく一貫性に関するオプションがないわけではなく、クエリ発行時にConsistencyLevelを指定することで、全てのレプリケーションノードに書込まれたことを保証したりは出来るみたいです。一方、HBaseの方は行ロック可能で、トランザクションを使うことができます。ただ制約も多いみたいで、いわゆる複数テーブルにまたがったようなのは無理ぽいみたいでした(自信なし)。まあしかしデータ一貫性では、HBaseの方に軍配が上がるようです。
運用の容易さ
Cassandraは運用の容易さを非常に重視してます。いわゆるマスターノードはなく、すべてのクラスタが同列に扱われていて、単一故障点がありません。他のノードの情報はGossipプロトコルをつかってP2P的な感じで伝搬するみたいです。それと比較してHBaseは、マスターノードがあります。マスターノードの2重化は出来るみたいなので、単一故障点はないですが、運用の難度はあがりますね。また、HBaseは、HadoopやZookeeperなどのコンポーネントに依存しているので、そちらのサーバも立てる必要があり、サーバ構成は複雑になります。運用容易さは基本的にはCassandraの勝ちですね。ただ、Cassandraにもテーブルスキーマ変更時に再起動が必要という欠点もあったりするみたいです。
ツール・ドキュメント
Cassandraは、ツールやドキュメントの整備はまだまだといった感じを受けます。一方、HBaseはそもそも、HDFSという柔軟なファイルシステムの上で動いているので、ストレージ製品を別途用意するがないですし、MapReduceとも楽に連携できます(Cassandraも最新のベータ版ではHadoopがあるみたいです)。このあたりの整備はHBaseに一日の長がありますが、Cassandraはコミュニティが活発なので、これからどんどん整備されていくんじゃないでしょうか。
データ構造・API
データ構造に関しては、Cassandra、HBaseはかなり似ています。まあ、同じ列指向データベースなんで当たり前なんですけどね。双方ともGoogleのBigTableをモデルにしているみたいです。データの取得、更新を行なうAPIはどちらもThriftに対応しているので、色んな言語のドライバを生成できます。生成されたドライバは、ネストの深いHashMapがあったりと、結構クセが強いですが、高機能なドライバも有志が作ってますんで、自分にあったものを探しましょう。

ざっと見て来ましたが、Cassandraはパフォーマンスの良さ、運用の容易さなどを重視していて、HBaseの方は、スケーリング性能を保ちつつのデータ一貫性、ツール整備など含めた完成度の高さを目指してるように感じました。

乱暴に結論を出すなら、Cassandraは、Webなどの比較的一貫性を求められない用途に、HBaseはスケーリングした上である程度一貫性が求められる場合や、MapReduceのデータ解析力を生かした、データウェアハウスなどの用途に使うかんじでしょうか( ̄∇  ̄ )

Tags:   ,
Categories:   NoSQL
Actions:   Permalink | Comments (0) | 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 (0) | Comment RSSRSS comment feed

オブジェクトデータベースdb4oをちょっと調べてみた

Monday, 15 March 2010 14:44 by sabro

クリエモンでは、.NETの、DataReader系を使用した独自の永続化層を作って、MySQLへアクセスしてたんですが、ちょっと他のプロジェクトで使い回せるようなカタチになってなかったり、NoSQL系に興味が出てきたりしたので、別のやり方を模索しています。

FaceBook、Twitterなどの大手が採用しているCassandraと、Monoでも問題なく使えるオブジェクトデータベースdb4oが気になっていたので、今回はdb4o(ver7.4)を軽く調べてみました。

特徴

  • オブジェクト自体を検索条件として使える
  • より詳細なクエリも発行出来る構文もある
  • Linqでデータを取ってくることも可能
  • インデックスも使える
  • トランザクションもある
  • サーバ側も基本的には自分でコードを書く必要あり
  • レプリケーション可能、2台まで?
  • レプリケーションは、双方向も可能
  • クラスターは実装中ぽい
  • MySQL的なデュアルライセンス

オブジェクトデータベースということで、カラムとフィールドのマッピングが不要だったり、クエリもSQL不要だったりで、コードの記述量はかなり少なくて済む印象。Javaと.NETのみをターゲットにしていて、LINQにも対応するなど、かなり.NETと親和性は高めに感じました。

ただ、組み込み用途を主に意識しているようで、Webでの使用は若干の不安もありそう。レプリケーションは可能で、コードから設定するんだけど、2台までしか指定できなくなってます。ただ、2台のレプリケーションの設定をいくつも書けば、もっとたくさんでのレプリケーションも可能なのかも(未調査)。双方向のレプリケーションが可能なのはいい感じ。要するにマルチマスタで、コンフリクト時は、自分でイベントハンドラを書いて解消する仕組みになってます。クラスターっていうのもあるみたいだけど、まだ実験段階かな。

速度面は、PolePosition benchmarkっていうのを見たら、かなりパフォーマンスがいいってことだったけど、実際、Wikipediaの日本語記事データを70万件くらいインサートしてみたら、MySQLの方が速かったです。オブジェクトのネストが浅かったからですかね。 1項目インサートするごとに、ネットワークを繋ぎ直していたのが遅くなっていた原因でした。一度使ったコネクションを使い舞わすようにしたところ、驚くほどInsertがパフォーマンスアップしました。体感ですが、MySQLの数倍速いと思います。

総合的にみて、db4oはコードが簡潔に書けて非常に使いやすいですが、Webで大規模に使うには、まだちょっと不安が残るかなという感じをうけました。組み込み系を作るときは、ぜひ使ってみたいプロダクトですね。クリエモンで使うかどうかは、Cassandraの方も評価してみて、最終的な判断をしようと思います( ̄∇  ̄ )

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