Cassandraのはじめ方─手を動かしてNoSQLを体感しよう

第4回JavaでCassandraにアクセスする

前回はCassandraのデータモデルについて説明しました。今回は実際にデータ設定とJavaコードからCassandraにアクセスする基本を見ていきます。コードはEclipseで書くことを前提に進めていきます。

Cassandraの設定で最低限必要な3つの項目

Cassandraの設定はすべてconf/storage-conf.xmlに記述されます。今回は、まずはじめてみるために必要最小限な項目の設定を行いましょう。

設定が必要なのは以下の項目になります。

  • ① キースペース、カラムファミリの設定
    • ColumnType属性
    • CompareWith属性
    • CompareSubcolumnsWith属性
  • ② コミットログとデータディレクトリを設定する
  • ③ ノードを設定する

キースペース、カラムファミリを設定する

何はともあれ、キースペースとカラムファミリを指定しないとデータを格納すらできません。設定は、たとえば以下のようになります。

<Keyspaces>
 <!-- キースペースは複数定義できる -->
 <!-- キースペースKeyspace1の定義 -->
 <Keyspace Name="Keyspace1">
   <!-- カラムファミリStandard1の定義 -->
   <ColumnFamily Name="Standard1" CompareWith="BytesType"/>

   <!-- カラムファミリSuper1の定義 -->
   <ColumnFamily Name="Super1" ColumnType="Super"
         CompareWith="BytesType" CompareSubcolumnsWith="BytesType" />

Name属性は見たとおりなので割愛します。ColumnFamilyには、Name属性のほかにいくつか知っておかないといけない設定項目があります。

ColumnFamilyで設定すべき属性は、おもに以下の3つです。

ColumnType属性

前回説明したとおり、ColumnFamilyにはカラムだけが含まれるケースと、スーパーカラムだけが含まれるケースの2パターンがあります。どちらのケースかは、このColumnType属性で区別します。

上記の例では、次のように分かれます。

  • "Standard1"という名前のColumnFamily
    →カラムだけが含まれるカラムファミリ
  • "Super1"という名前のColumnFamily
    →ColumnTypeに"Super"と指定があるので、スーパーカラムだけが含まれるカラムファミリ

ColumnType属性はStandardまたはSuperのどちらかを選択できます。ColumnType属性に何も記載がないとStandardになります。

表1 ColumnType属性の種別
ColumnType設定項目説明
Standardカラムファミリ内にカラムが含まれる
Superカラムファミリ内にスーパーカラムが含まれる

CompareWith属性

次に目につく設定がCompareWithです。これは検索時のカラムのソートのための設定項目です。検索実行時にこのCompareWithで指定されたクラスを使ってカラムがソートされ、結果が返されます。

CompareWith設定には、以下の表(CompareWithの設定種別)のようなオプションが標準であります。CompareWith属性に何も指定しないとBytesTypeが適用されます。

表2 CompareWithの設定種別
CompareWith設定項目説明
BytesTypebyte[]で比較する。値の検証はしない。
UTF8TypeUTF8でも文字列比較をする。
AsciiTypebyte[]での比較に加えてUS-ASCIIでのエンコードの検証をする。
LongType64ビット長のlong値で比較する。
LexicalUUIDType128ビット長UUIDのbyte[]値を比較する。
TimeUUIDType128ビット長UUIDのタイムスタンプを比較する。

具体例を見てみましょう。以下の例ではUTF-8でカラムをソートします。

<ColumnFamily Name="utf8Order" CompareWith="UTF8Type" />

イメージとしては以下のようになります。カラム名がUTF-8順にソートされます。

SuperColumn:utf8Order
keyColumn
hogenamevaluetimestamp
1112221273285360339
aaavalue1273285360339
nameshinpei1273285360339
こんにちはさようなら1273285360339

CompareSubcolumnsWith属性

CompareSubcolumnsWith属性は、スーパーカラムの場合(ColumnType="Super")に使用します。それ以外のケースでは使えません。

スーパーカラムの場合、

  • スーパーカラムのソート
    →CompareWith
  • スーパーカラム内のカラムのソート
    →CompareSubcolumnsWith

という形で使い分けます。指定できる項目はCompareWithと同様です。

具体例を見てみましょう。

<ColumnFamily Name="time_utf8Order"
     ColumnType="Super" CompareWith="LongType"
     CompareSubcolumnsWith="UTF8Type"/>

この例ではCompareWithに"LongType"、CompareSubcolumnsWithに"UTF8Type"を指定しているので、スーパーカラムはLong値でソートされ、スーパーカラム内のカラムはUTF8でソートされます。

以下のようなイメージになります。

ColumnFamily:time_utf8Order
keySuperColumn
hogekeyColumn
1273284729294namevaluetimestamp
mogeaaa1273284729294
mugabbb1273284729294
1273284729347namevaluetimestamp
mogeccc1273284729347
mugaddd1273284729347

キースペース、カラムファミリの設定は以上です。

他にも、レプリケーション数やレプリケーションをどのように行うかなどの指定がありますが、今回は割愛します。

コミットログとデータディレクトリを設定する

次はコミットログとデータディレクトリの設定を行います。

Cassandraは、書き込み時には操作をすべてコミットログに追加で書き込んでいき、その実体(Memtableといいます)はメモリ上にカラムファミリごとに展開していく仕組みになっています。Memtableはサイズの閾値等をもっており、その閾値に達するとディスクに書き出します。この操作のことを「フラッシュ」といい、ディスクに書き出す構造のことを「SSTable」といいます。SSTableは一度書き出されるとその内容は不変で、実際の物理データとしては以下の3つがセットで書き出されます。

  • インデックス
  • ブルームフィルタ
  • データファイル

インデックスとブルームフィルタは読み込みを高速化するための仕組みです(本連載で今後分量の都合がつけば詳しくご説明します⁠⁠。

このような内部構造をもっているため、Cassandraではコミットログの書き込むディレクトリと実際のデータを各ディレクトリを明確に分離しておくのが望ましいです。

コミットログはCommitLogDirectoryで設定します。一方、SSTableを永続化する実データ部分はDataFileDirectoryで設定します。DataFileDirectoryは複数設定することが可能です。

設定例としては以下のようになります。

<CommitLogDirectory>C:\cassandra\commitlog</CommitLogDirectory>
<DataFileDirectories>
    <DataFileDirectory>C:\cassandra\data</DataFileDirectory>
</DataFileDirectories>

ノードを設定する

最後はノードの設定です。

Cassandraはマスターノードを持たないアーキテクチャのため、特定のノードをマスター指定したりする必要がありません。そのため、ノードの設定は非常に簡潔になっています。

単一ノードで動かしたい場合、Seed設定で以下のようにローカルホストを指定します。非常にシンプルですね。

  <Seeds>
      <Seed>127.0.0.1</Seed>
  </Seeds>

デフォルトの設定では、このようにローカルホストだけをノードとして追加しています。

簡単ですが、以上で必要な設定はすべて終了です。

storage-conf.xmlの全体像はどうなっているか

では、storage-conf.xmlがどのようになっているか見てみましょう。今回は分量の都合で割愛した部分もありますので、その部分は省略しました。

<Storage>
  <ClusterName>Test Cluster</ClusterName>
  ...
  <!-- キースペースの指定 -->
  <Keyspaces>
    <Keyspace Name="Keyspace1">
      <!-- カラムファミリの指定 -->
      <ColumnFamily Name="Standard1" CompareWith="BytesType"/>
      <ColumnFamily Name="Super1" ColumnType="Super" CompareWith="BytesType" CompareSubcolumnsWith="BytesType" />
      <ColumnFamily Name="utf8Order" CompareWith="UTF8Type" />
      <ColumnFamily Name="time_utf8Order" ColumnType="Super" CompareWith="LongType" CompareSubcolumnsWith="UTF8Type"/>

      ...

    </Keyspace>
  </Keyspaces>

  ...

  <!-- コミットログとデータディレクトリ指定 -->
  <CommitLogDirectory>C:\cassandra\cassandra-0.6.1\var\commitlog</CommitLogDirectory>
  <DataFileDirectories>
      <DataFileDirectory>C:\cassandra\cassandra-0.6.1\var\data</DataFileDirectory>
  </DataFileDirectories>


  <!-- ノード設定。開発用なので1ノード。 -->
  <Seeds>
      <Seed>127.0.0.1</Seed>
  </Seeds>

  ...

</Storage>

ここまでで、Cassandraの基本的な設定について説明しました。いますぐにすべてを覚えなくても大丈夫です。今後コードを書いていく中で徐々に覚えていくことができるでしょう。

開発環境を構築しよう

本連載では、開発環境としてEclipseを想定しています。まず開発環境を構築してみましょう。

以下のような手順でEclipse上にJavaプロジェクトを作成しておきます。

  • ①Eclipseを起動してJavaプロジェクトを作成します。
       プロジェクト名は「cassandra-examples」とします(名前はなんでも結構です⁠⁠。
  • ②前回動かしたCassandraのlibディレクトリ(%CASSANDRA_HOME%\lib)からjarをすべてコピーして、cassandra-examples/libに置きます。
  • ③コピーしてきたjarすべてにクラスパスを通しておきます。

Cassandraへアクセスするための基本を知ろう

では、このプロジェクトでサンプルコードを書いてみましょう。CassandraへはThriftでのRPCを介して接続する必要があります。まずは接続のためのインスタンスであるTSocketとTProtocolの実装クラスを生成します。

ソケットを事前に開けておく必要があるので、CassandraにRPCする前にTSocket.open()を呼び出しておいてください。

最後に、CassandraにアクセスするクライアントコードであるCassandra.Clientインスタンスを生成します。その際にTProtocolインスタンスを渡す必要があるので注意してください。

以下は、localhostで9160番ポートで起動しているCassandraに接続しにいく実装コードです。

    TSocket transport = new TSocket("localhost", 9160);
    TProtocol protocol = new TBinaryProtocol(transport);
    try {
        transport.open();
    } catch (TTransportException e) {
        //適宜、適切な例外を投げるなどしてください。
    }
    Cassandra.Client client = new Cassandra.Client(protocol);

これで呼び出し部分は完成です。

次は終了部分です。Thriftのために使ったソケットはフラッシュして中身をクリアした後、クローズします。

    try {
        transport.flush();
    } catch (TTransportException e) {
        //適宜、適切な例外を投げるなどしてください。
    } finally {
        transport.close();
    }

動かしてみよう

というわけで、Thrift RPCの接続と切断を行うサンプルコードを書いて動かしてみましょう。コードを実行する前に、ローカルホストでCassandraを動かしておいてください。

C:\cassandra\apache-cassandra-0.6.1>bin\cassandra.bat
Starting Cassandra Server
Listening for transport dt_socket at address: 8888
 INFO 23:21:29,072 Auto DiskAccessMode determined to be mmap
 INFO 23:21:29,961 Saved Token not found. Using 133651919147845386411055611530227208445
 INFO 23:21:29,963 Saved ClusterName not found. Using Test Cluster
 INFO 23:21:30,010 Creating new commitlog segment C:\cassandra\commitlog\CommitLog-1273501290010.log
 INFO 23:21:30,096 Starting up server gossip
 INFO 23:21:30,272 Binding thrift service to localhost/127.0.0.1:9160
 INFO 23:21:30,285 Cassandra starting up...

次回はデータ挿入のサンプルを中心にご紹介しますが、その前に、今回のThriftを使ったRPCの基本を見せるサンプルとして、1件のカラムへデータを挿入する簡単なコードを紹介しておきます。

package examples;

import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.ColumnPath;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.InvalidRequestException;
import org.apache.cassandra.thrift.TimedOutException;
import org.apache.cassandra.thrift.UnavailableException;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransportException;

/**
 * シンプルなインサート。
 * 
 */
public class SimpleInsert {

    public static final String KEYSPACE = "Keyspace1";

    public static final String COLUMN_FAMILY = "Standard1";

    public static void main(String[] args) {
        TSocket transport = new TSocket("localhost", 9160);
        TProtocol protocol = new TBinaryProtocol(transport);
        try {
            transport.open();
        } catch (TTransportException e) {
            throw new RuntimeException(e);
        }
        Cassandra.Client client = new Cassandra.Client(protocol);
        try {
            final String key = "sample1";
            final String columnName = "hoge";
            final String value = "sample_value";
            final long timestamp = System.currentTimeMillis();
            // ColumnPathは単一のカラムの位置を特定するためのクラス
            final ColumnPath columnPath = new ColumnPath(COLUMN_FAMILY);
            columnPath.setColumn(columnName.getBytes());
            // 1件カラムをインサート
            client.insert(KEYSPACE, key, columnPath, value.getBytes(),
                    timestamp, ConsistencyLevel.ONE);
        } catch (InvalidRequestException e) {
            throw new RuntimeException(e);
        } catch (UnavailableException e) {
            throw new RuntimeException(e);
        } catch (TimedOutException e) {
            throw new RuntimeException(e);
        } catch (TException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                transport.flush();
            } catch (TTransportException e) {
                throw new RuntimeException(e);
            } finally {
                transport.close();
            }
        }
        System.out.println("1件インサート完了.");
    }
}

これをEclipse上から実行してみてください。⁠1件インサート完了」と出れば、成功です。もし出ない場合、もう一度設定とサンプルコードを見直してみてください。

インサート処理の中身については心配しないでください。次回詳しく説明します。

データが投入されたかどうか確認してみよう

本当に投入されたかどうかは、Cassandraのコマンドラインツールであるcassandra-cli.batで確認できます。

以下はコマンドラインからの実行の結果です。1件データが投入されているのがおわかりいただけたでしょうか。

C:\cassandra\apache-cassandra-0.6.1>bin\cassandra-cli.bat
Starting Cassandra Client
Welcome to cassandra CLI.
cassandra> connect localhost/9160
Connected to: "Test Cluster" on localhost/9160
cassandra> get Keyspace1.Standard1['sample1']
=> (column=686f6765, value=sample_value, timestamp=1273501448025)
Returned 1 results.

これで開発環境の構築と、最も単純なクライアントコードが動くところまで来ました。ここまでで、Eclipse上のプロジェクトは次の図1のようになっているはずです。

図1 Cassandraクライアントコードのプロジェクト
図1 Cassandraクライアントコードのプロジェクト

今回は以下の2つのこと説明しました。

  • Cassandraの基本的な設定方法
  • Thriftを使ってCassandraへアクセスするための基本

次回はデータ投入を詳しく説明したあと、コマンドラインではなくコードから検索をかけてみます。お楽しみに。

おすすめ記事

記事・ニュース一覧