Hatena::ブログ(Diary)

Taste of Tech Topics このページをアンテナに追加 RSSフィード

Acroquest Technology株式会社の若手エンジニアが書く技術ブログ

2011-12-12

[]Cassandra1.0性能検証。読み込み性能400%向上ってほんまかいな?!(前半)

こんばんは、ishida(@kojiisd)です。

実は私、Java Advent Calendarの12/12にエントリーしていまして、今回はその一環でも書いています。
Java Advent Caldendarが何なのかについては、
こちらを参照してください。

さて、最近諸事情からCassandraを触ることになったのですが、こんな記事を見つけました。

NoSQLのCassandraが1.0に。読み込み性能は400%向上」-Publickey-

0.7からの性能が大幅アップしているのであれば、試してみたい・・・!
ていうかほんまかいな?!

というわけで、Cassandraの性能検証0.7 vs 1.0読み込み性能は本当に400%も向上しているのか?」を実施します。
※とはいえ、インストール作業等も備忘録として載せるので、今回は前半戦として
 Clientから一括データ登録処理の作成までを載せます。実際の性能評価はぜひ近いうち公開する後半をお楽しみに。

後半も書きました。こちらをご覧ください。

前提[対象の確認]


まず今回の実験を行うに当たり、それぞれ試したスペックを載せておきます。
性能評価のために、まずはXenServerをインストール、以下のスペックのマシンを用意しました。

CPUCore i3の2コアを割り当て
メモリ2GB
HDD160GB
OSUbuntu desktop 11.10 64bit
Cassandra Version0.7.10 / 1.0.5

ちなみに、、、Client側は大丈夫なんだろうなぁ?!と
後から言われないために、今回のClientスペックはこちら。

CPUCore i5 2.83GHz
メモリ12GB
HDDSSD 128GB
OSWindows 7 Pro

これだけそろえればまずClient側で問題が出ることはないでしょう。

Cassandra1.0のインストール

意外とCassandra1.0のインストールが載っていなかったので、
こちらに載せておきます。

今回はUbuntu上でapt-getをすることでインストールする
方法を選択しました。

まずUbuntu本体をアップデートします。

sudo apt-get update
sudo apt-get upgrade

次に、/etc/apt/source.listの最終行に以下の記述を加えます。

deb http://www.apache.org/dist/cassandra/debian 10x main
deb-src http://www.apache.org/dist/cassandra/debian 10x main

再度apt-get updateを実施すると、PUBLIC_KEYがない、とERRORを返されるので、
以下の操作を行ってください。

gpg --keyserver pgp.mit.edu --recv-keys XXXXXXX
gpg --export --armor XXXXXXX | sudo apt-key add -
(XXXXXXXはapt-get update時に出力されるエラー内に表示されるPUBLIC_KEY)

これでapt-get update後、Cassandraをinstallすれば完了です。

sudo apt-get update
sudo apt-get install cassandra

ちなみにこのやり方だとServiceとして登録されますので、Cassandraの起動/終了はserviceコマンドを使って行います。

sudo service cassandra start|restart|stop

サンプルプログラムの作成

Cassandraの準備ができたらデータをClientから投入するプログラムの作成です。
Cassandra0.7のサンプルはいくらでも手に入るので省略します。
1.0バージョンのコードは、ThriftExampleというThriftのWikiにありました。
(Cassandra本体のページにないのが何とも。。。これ探すの苦労しました。)

さて、とはいえこのままでは1件の投入しかできません。これを改造する必要があります。
batch_mutationというClientクラスのメソッドを使うのですが、若干組み方がやっかいなので、
全体を含めたソースを以下に載せます。(javadocが書かれていないなど、メンテ不足で汚いですが^^;)、
まぁ、サンプルということで(汗)参考にしてください。

public class CClient
{
    /** サーバアドレス(適宜変えてください) */
    private static final String SERVER_ADDR = "192.168.1.155";

    /** デフォルトポート番号 */
    private static final int DEFAULT_PORT = 9160;

    /** データ数(行) */
    private static final int DATA_ROW_NUM = 1000;

    /** 一つのカラムファミリあたりのカラム数 */
    private static final int DATA_COL_NUM = 100;

    /** キースペース */
    private static final String KEYSPACE_NAME = "Samplespace1";

    /** カラムファミリー */
    private static final String COLUMN_PARENT_NAME = "Standard1";

    public static void main(String[] args) throws TException,
	    InvalidRequestException, UnavailableException,
	    UnsupportedEncodingException, NotFoundException, TimedOutException
    {
	TTransport tr = new TFramedTransport(new TSocket(SERVER_ADDR,
		DEFAULT_PORT));
	TProtocol proto = new TBinaryProtocol(tr);
	Cassandra.Client client = new Cassandra.Client(proto);
	tr.open();

	long timestamp = System.currentTimeMillis();
	client.set_keyspace(KEYSPACE_NAME);

	// KeyとカラムファミリのMapのMapを作成
	Map<ByteBuffer, Map<String, List<Mutation>>> rowsMap = new HashMap<ByteBuffer, Map<String, List<Mutation>>>();

	// DATA_ROW_NUM分の行数のデータを作成
	for (int index = 1; index <= DATA_ROW_NUM; index++)
	{
	    // 登録した値からMutationオブジェクトのリストを作成
	    // 100のColumnを作成し、Mutationリストに詰める。
	    List<Mutation> mutationList = createSampleMutationList(
		    DATA_COL_NUM, String.valueOf(index), timestamp);

	    // カラムファミリとMutationのリストのMapを作成
	    Map<String, List<Mutation>> columnFamilyMap = new HashMap<String, List<Mutation>>();
	    columnFamilyMap.put(COLUMN_PARENT_NAME, mutationList);

	    rowsMap.put(toByteBuffer(String.valueOf(index)), columnFamilyMap);
	}

	// 一括登録
	long beforeBatchTime = System.currentTimeMillis();
	client.batch_mutate(rowsMap, ConsistencyLevel.ONE);
	long afterBatchTime = System.currentTimeMillis();

	System.out.println("登録に " + (afterBatchTime - beforeBatchTime)
		+ " ミリ秒かかりました。");

	// 一要素取り出してみる。
	ColumnPath path = new ColumnPath("Standard1");

	// read single column
	path.setColumn(toByteBuffer("Key30"));

	beforeBatchTime = System.currentTimeMillis();
	System.out.println("\n"
		+ client.get(toByteBuffer("1000"), path, ConsistencyLevel.ONE)
		+ "\n");
	afterBatchTime = System.currentTimeMillis();

	System.out.println("検索に " + (afterBatchTime - beforeBatchTime)
		+ " ミリ秒かかりました。");

	tr.close();
    }

    /**
     * バイトコードへの変換
     * 
     * @param value
     * @return
     * @throws UnsupportedEncodingException
     */
    public static ByteBuffer toByteBuffer(String value)
	    throws UnsupportedEncodingException
    {
	return ByteBuffer.wrap(value.getBytes("UTF-8"));
    }

    /**
     * toStringの実装
     * 
     * @param buffer
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String toString(ByteBuffer buffer)
	    throws UnsupportedEncodingException
    {
	byte[] bytes = new byte[buffer.remaining()];
	buffer.get(bytes);
	return new String(bytes, "UTF-8");
    }

    private static Mutation toMutation(final String name, final String value,
	    final long timestamp) throws UnsupportedEncodingException
    {
	Mutation mutation = new Mutation();
	ColumnOrSuperColumn columnOrSuperColumn = new ColumnOrSuperColumn();
	Column column = new Column(toByteBuffer(name));
	column.setValue(toByteBuffer(value));
	column.setTimestamp(timestamp);
	columnOrSuperColumn.setColumn(column);
	mutation.setColumn_or_supercolumn(columnOrSuperColumn);
	return mutation;
    }

    private static List<Mutation> createSampleMutationList(int endNum,
	    String valuePrefix, long timestamp)
	    throws UnsupportedEncodingException
    {
	Map<String, String> keyValueMap = new LinkedHashMap<String, String>();
	List<Mutation> mutationList = new ArrayList<Mutation>();

	for (int index = 1; index <= endNum; index++)
	{
	    String key = "Key" + index;
	    String value = "Value" + index + valuePrefix;

	    keyValueMap.put(key, value);
	}

	for (Entry<String, String> entry : keyValueMap.entrySet())
	{
	    mutationList.add(toMutation(entry.getKey(), entry.getValue(),
		    timestamp));
	}

	return mutationList;
    }
}

自分でも整理のために行やら何やら、RDBの言葉を持ち出しています(^^;あまり気にしないでください。
このDATA_ROW_NUM(行)とDATA_COL_NUM(列)を変化させ、性能評価を行おうと思います。

実行、、、の前の事前準備(外部アクセス許可)

さて、上記プログラムを実行してみましょう。と、その前に。今回はClientからアクセスするため、
上記状態ではCassandraに外部からアクセスできず「connection refused」が発生します。

Cassandra側にClient側からのアクセス用の穴を空けてあげる必要があります。
/etc/cassandra/cassandra.yamlの以下の記述を変更します。

#listen_address: localhost
listen_address: 192.168.1.0
(アドレスは適宜変更してください。)
~~~~~~~
#rpc_address: localhost
rpc_address: 0.0.0.0

今回の変更がベストなのかは未検証ですが、これでClient側からつながるはずです。

実行

いよいよ実行です。

登録に 2399 ミリ秒かかりました。

ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 03 67 65 74 00 00 00 03 0C 00 00 0C 00 01 0B 00 01 00 00 00 05 4B 65 79 33 30, value:80 01 00 02 00 00 00 03 67 65 74 00 00 00 03 0C 00 00 0C 00 01 0B 00 01 00 00 00 05 4B 65 79 33 30 0B 00 02 00 00 00 0B 56 61 6C 75 65 33 30 31 30 30 30, timestamp:1323648382260))

検索に 72 ミリ秒かかりました。

おぉ!実行されて結果が出ましたね。

これを0.7用のCassandraプログラムとしても組み替えて、0.7用の仮想イメージを用意して、
性能評価に移りたいと思います。

では、前半としてはここまでで。

[]Twitter Stormをインストールしてみます!(その3

こんばんは。kimukimuです。

前回に引き続き、Twitter Stormのインストールを続けます。

4.Stormインストールの前提ソフトウェアインストール(その3


Java 6

言うまでもなくJavaの実行環境ですね。
Stormはjava/clojureというJVM言語で開発されているため、動作に必要です。

実は既にインストールされていたりします(汗
これはCentOSに限ったことではなくて、
そもそもJZMQをインストールする時点でJDKが必要になるからなんですね。

一応、バージョンを確認してみますが、下記のようにJava6系がインストールされています。

> java -version
java version "1.6.0_17"
OpenJDK Runtime Environment (IcedTea6 1.7.9) (rhel-1.36.b17.el6_0-x86_64)
OpenJDK 64-Bit Server VM (build 14.0-b16, mixed mode)

Python 2.6.6

これも今更解説不要ですね。
Pythonオブジェクト指向スクリプト言語のひとつで、広く広まっています。

Javaと同じく既にインストールされている可能性があるため、
まずはバージョンを確認します。

> python
Python 2.6.5 (r265:79063, Jun 25 2011, 08:36:25)
[GCC 4.4.4 20100726 (Red Hat 4.4.4-13)] on linux2
Type "help", "copyright", "credits" or "license" for more information.

2.6.5で、2.6.6とは微妙に違います・・・
念のため、インストールだけはしておくことにします。

> cd /usr/local/download
> wget http://www.python.org/ftp/python/2.6.6/Python-2.6.6.tgz ★ダウンロード★
> tar zxvf Python-2.6.6.tgz
> cd Python-2.6.6
> vi Modules/Setup

一部設定を変更する必要があるため、
下記のように『Modules/Setup』を一部修正します。

# Andrew Kuchling's zlib module.
# This require zlib 1.1.3 (or later).
# See http://www.gzip.org/zlib/
zlib zlibmodule.c -I$(prefix)/include -L$(exec_prefix)/lib -lz
#★↑zlib で始まる行のコメントアウトを外す↑★

# Interface to the Expat XML parser

設定が変更し終わったら、後はconfigureコマンドで設定を取り込み、
makeコマンドでインストールです。

> ./configure --with-threads --enable-shared
> make
> make install
> ls -l /usr/local/lib ★ライブラリの更新確認★

unzip

おなじみ、unzipコマンドです。
こちらも元々インストールされていました。

> unzip
UnZip 6.00 of 20 April 2009, by Info-ZIP.  Maintained by C. Spieler.  Send
bug reports using http://www.info-zip.org/zip-bug.html; see README for details.

そのため、これでTwitter Stormの前提ソフトウェアインストール完了です。

5.Storm本体のインストール


では、Storm本体をインストールします。
Storm GitHubからリリースファイルをダウンロードします。

> wget https://github.com/downloads/nathanmarz/storm/storm-0.6.0.zip
> unzip storm-0.6.0.zip
> chmod +x storm-0.6.0/bin/storm
> mv storm-0.6.0 ../
> cd ../
> ln -s storm-0.6.0 storm

実はこれで終わりだったりします^^;
後は、『/usr/local/storm/bin』ディレクトリにパスを通しておきましょう。
stormコマンド実行時に下記のエラーメッセージが表示されればOKです。

> storm
Commands:
        classpath, drpc, jar, kill, localconfvalue, nimbus, remoteconfvalue, repl, shell, supervisor, ui

6.次はどうすればいいの?


これでStormインストール出来た!
・・・と思いましたが、実はこれだけでは何もできません(汗
Hadoop等と同じく、Stormはプラットフォームであり、
『その上で動くアプリケーション』が必要なんですね。

次回はStorm上でリアルタイム処理を動かすためにそもそも何が必要か、
を説明します。