2013年2月8日金曜日

Firebrandを使ってみる

FirebrandはJavaオブジェクトからCassandraへの接続の保持やクエリーの発行を行うためのシンプルなCassandraクライアントライブラリです。

■インストール

事前に用意しておくもの


  • JDK6
  • Maven2 (jarをダウンロードしてくる場合はいらない)
  • git (jarをダウンロードしてくる場合はいらない)

jarをダウンロードして使う


ちょっと試すだけならFirebrandが依存しているパッケージもついてくるバージョンが便利です。
CassandraおよびHectorのパッケージがすでにクラスパスに含まれていれば、Firebrandだけで結構です。
wget https://github.com/downloads/47deg/firebrand/firebrand-1.0-SNAPSHOT-jar-with-dependencies.jar
   or
wget https://github.com/downloads/47deg/firebrand/firebrand-1.0-SNAPSHOT.jar

Mavenを使ってビルドする


ソースファイルをもってくる
git clone https://github.com/47deg/firebrand.git
cd firebrand

ビルドする 


mvn clean install
     or
mvn package

■CRUDしてみる


1. Cassandra側でKeyspaceを用意しておく。


create keyspace TestKS
    with placement_strategy = 'SimpleStrategy'
    and strategy_options = {replication_factor:2}

create column family TestCF
    with column_type = 'Standard'
    and comparator = 'UTF8Type'
    and default_validation_class = 'UTF8Type'
    and key_validation_class = 'UTF8Type'

2. エンティティクラスを用意する


TestCF.java


import org.firebrandocm.dao.annotations.*;

@ColumnFamily(consistencyLevel = ConsistencyLevel.ALL)

@NamedQueries({
  @NamedQuery(name = TestCF.QUERY_ALL_TESTCFS, query = "select * from TestCF"),
  @NamedQuery(name = TestCF.QUERY_ALL_TESTCFS_WITH_NAME, query = "select * from TestCF where name = :name")
})


public class TestCF {
          private String firstProperty;

          private String nullProperty;

          public static final String QUERY_ALL_TESTCFS = "TestCF.QUERY_ALL_TESTCFS";

          public static final String QUERY_ALL_TESTCFS_WITH_NAME = "TestCF.QUERY_ALL_TESTCFS_WITH_NAME";

          @Key
          private String key;

          @Column(indexed = true)
          private String name;

          @Column(indexed = true)
          private String description;
          
          public String getName() {
                  return name;
          }
          
          public String getDescription() {
                  return description;
          }
          
          public void setName(String data) {
                  name = data;
          }
          
          public void setDescription(String data) {
                  description = data;
          }

          public String getKey() {
                  return key;
          }
          
          public void setKey(String data) {
                  key = data;
          }
}

TestCF2.java


import org.firebrandocm.dao.annotations.*;

@ColumnFamily(consistencyLevel = ConsistencyLevel.ALL)

@NamedQueries({
  @NamedQuery(name = TestCF2.QUERY_ALL_TESTCF2S, query = "select * from TestCF2"),
  @NamedQuery(name = TestCF2.QUERY_ALL_TESTCF2S_WITH_NAME, query = "select * from TestCF2 where name = :name")
})


public class TestCF2 {
          public static final String QUERY_ALL_TESTCF2S = "TestCF2.QUERY_ALL_TESTCF2S";

          public static final String QUERY_ALL_TESTCF2S_WITH_NAME = "TestCF2.QUERY_ALL_TESTCF2S_WITH_NAME";

          @Key
          private String key;

          @Column(indexed = true)
          private String name;

          @Column(indexed = true)
          private String description;
          
          public String getName() {
                  return name;
          }
          
          public String getDescription() {
                  return description;
          }
          
          public void setName(String data) {
                  name = data;
          }
          
          public void setDescription(String data) {
                  description = data;
          }

          public String getKey() {
                  return key;
          }
          
          public void setKey(String data) {
                  key = data;
          }

}

3. CRUDしてみる

Test.java

import org.firebrandocm.dao.*;
import org.firebrandocm.dao.impl.hector.HectorPersistenceFactory;
import static org.firebrandocm.dao.cql.QueryBuilder.*;
import java.util.*;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.firebrandocm.dao.cql.clauses.ConsistencyType;

class FBTest {
        public static void main(String args[]) {
              // 使用するカラムファミリーのエンティティクラス
              List<Class<?>> classList = Arrays.asList(TestCF.class, TestCF2.class);

              // Cassandraノード
              String[] nodes = {"192.168.1.122"};

              try {

                // HectorのFactoryのやり方で接続設定する
                PersistenceFactory persistenceFactory = new HectorPersistenceFactory.Builder()
                  .defaultConsistencyLevel(ConsistencyLevel.ONE)
                  .clusterName("Test Cluster")
                  .defaultKeySpace("TestKS")
                  .contactNodes(nodes)
                  .thriftPort(9160)
                  .entities(classList)
                  .build();

                  /* build()した時に、先ほど指定したエンティティクラスに対応するカラムファミリーに接続される or 無ければ勝手にcreate column familyされる。今回はTestCF2というカラムファミリーはあらかじめ作成して置かなかったため、ここでCassandra側にTestCF2が作られる。 */



                /***************************/
                /*     create, update      */
                /***************************/

                // insertColumns
                persistenceFactory.insertColumns("TestCF", "000001", new HashMap<String, Object>(){{
                  put("name", "Keith");
                  put("description", "This is Keith's data.");
                }});
                TestCF setter1 = persistenceFactory.get(TestCF.class, "000001");
                System.out.println(setter1.getDescription()); // This is Keith's data.

                // getResultList CQLベタ書き
                persistenceFactory.getResultList(TestCF.class, Query.get("INSERT INTO TestCF (KEY, 'name', 'description') VALUES ('000002', 'Thom', 'This is the data of Thom.') USING CONSISTENCY ONE AND TTL 86400;"));
                TestCF setter2 = persistenceFactory.get(TestCF.class, "000002");
                System.out.println(setter2.getDescription()); // This is the data of Thom.

                // getResultList Query Builder
                persistenceFactory.getResultList(TestCF.class, Query.get(
                  insert(
                    columnFamily("TestCF"),
                    into("KEY", "name", "description"),
                    values("000003", "Stevie", "This is the Stevie data."),
                    writeOptions(
                      consistency(ConsistencyType.ONE),
                      ttl(86400)
                    )
                  )
                ));
                TestCF setter3 = persistenceFactory.get(TestCF.class, "000003");
                System.out.println(setter3.getDescription()); // This is the Stevie data.
                
                
                /**************************/
                /*          read          */
                /**************************/

                /**** rowkey指定 ****/
                TestCF getter1 = persistenceFactory.get(TestCF.class, "000001");
                System.out.println(getter1.getKey()); // 000001

                /**** 全部取得 ****/
                // getResultList CQLベタ書き
                List<TestCF> getter2 = persistenceFactory.getResultList(TestCF.class, Query.get("select * from TestCF"));
                System.out.println(getter2.get(0).getKey()); // 000001

                // getResultList Query Builder
                List<TestCF> getter3 = persistenceFactory.getResultList(TestCF.class, Query.get(select(allColumns(), from(TestCF.class))));
                System.out.println(getter3.get(0).getKey()); // 000001

                // getResultList TestCF内でCQLを定義し使用
                List<TestCF> getter4 = persistenceFactory.getResultList(TestCF.class, Query.get(TestCF.QUERY_ALL_TESTCFS));
                System.out.println(getter4.get(0).getKey()); // 000001


                /**** カラム指定 Secondary Index 指定有りのときのみ可 ****/
                // getResultList CQLベタ書き
                List<TestCF> getter5 = persistenceFactory.getResultList(TestCF.class, Query.get("select * from TestCF where name = 'Keith'"));
                System.out.println(getter5.get(0).getName()); // Keith

                // getResultList Query Builder
                List<TestCF> getter6 = persistenceFactory.getResultList(TestCF.class, Query.get(select(allColumns(), from(TestCF.class), where(eq("name", "Keith")))));
                System.out.println(getter6.get(0).getName()); // Keith


                // getResultList TestCF内でCQLを定義し使用
                List<TestCF> getter7 = persistenceFactory.getResultList(TestCF.class, Query.get(TestCF.QUERY_ALL_TESTCFS_WITH_NAME, new HashMap<String, Object>(){{
                  put("name", "Keith");
                }}));
                System.out.println(getter7.get(0).getName()); // Keith



                /**************************/
                /*         delete         */
                /**************************/
                TestCF del = persistenceFactory.get(TestCF.class, "000001");
                persistenceFactory.remove(del); // persistenceFactory.remove(del, del2, del3...); も可
                System.out.println(del); // []

              } catch(Exception e) {
                System.out.println(e);
              }
        }

}

2013年1月8日火曜日

Cassandra1.2のデフォルトパーテショナーに関して

 1.2では1.1と比べ幾つか仕様変更や追加があります

その中でパーテショナーの設定ではデフォルトパーテショナーが変更され、RandomPartitionerから新たに追加されたMurmur3Partitionerに変更になりました

Murmur3Partitionerに関してはハッシュ値をRandomPartitionerより早く取得できシノニムも少なく抑えられるハッシュ関数で、事実上RandomPartitionerと同じ立ち位置になります

しかし、トークンのレンジ範囲はMurmur3PartitionerとRandomPartitionerでは違いがあります
・RandomPartitionerのレンジ範囲
                     0 ~ 2^127
( 0 ~ 170141183460469231731687303715884105728 )

・Murmur3Partitionerのレンジ範囲
                 -2^63 ~ 2^63 - 1
( -9223372036854775808 ~ 9223372036854775807 )

Murmur3Partitionerに対応したノードのトークン値を均等にする求め方はpythonを使い
python -c 'print [((2**64 / リングのノード数) * i) - 2**63 for i in range(リングのノード数)]'
で求め、複数のリングが存在する場合はリングごとに1ずつ値をずらします

例)3ノードのリングが2つある場合
python -c 'print [((2**64 / 3) * i) - 2**63 for i in range(3)]
[-9223372036854775808L, -3074457345618258603L, 3074457345618258602L]

なので

一つ目のリングのノード3つは
-9223372036854775808
-3074457345618258603
3074457345618258602

二つ目のリングのノード3つは一つずらし
-9223372036854775807
-3074457345618258602
3074457345618258603

また、1.2に移行する際などデフォルトパーテショナーが変更されているので注意が必要です(必ず使用していたパーテショナーに合わせてください)

2012年12月6日木曜日

virtual node について


virtual nodeについて(Cassandra勉強会第24回のスライド)

http://www.slideshare.net/seki_intheforest/virtual-node


virtual nodeはバージョン1.2.0 から実装されますが
Strategy関係で問題がある為最低でも1.2.1以降からと思っておいたほうがよい

動作として単純に一つのノードに割り当てるトークンをランダムに複数持つようにする機能で
複数のノードをランダムで複数のトークンを持たせる事で比較的負荷を均等にする事ができ
レンジが多く細分化されるのでパーテショナーによってデータの偏りがあまり起こらない

また、一つのノードに複数のトークンがある際、同じノードのトークンが連続する事があるが
レプリケーションが一つのノードに二重に取られる事は無い

virtual nodeの良い点悪い点


virtual nodeの良い点
  • ランダムの値のトークンに任せるのでトークンの値のmoveの作業をしなくて良い
  • ノードがダウンした際リング内の隣のノードが複数になるので負荷が集中しない
  • ノード追加した際の他のノードの受け渡しも全体のノードで行うようになるので早い
  • ノードの負荷はレンジの領域の多さではなく、トークンの数の多さで考えられるようになる
  • パーテショナーの偏りに対応できる
virtual nodeの悪い点
  • トークンの値を任意に変更できない
  • 起動後にトークンの数を変更できない
  • virtual nodeのトークンを全体的に変更する際はノードのdecommisionをしてから再度追加する事になる
  • トークンを大量に増やさなければ比較的に分散されない
  • 大量のトークンでの nodetool ring の見栄えがすごい

virtual nodeは総合的に見て、よりメンテナンスフリーに近づく形にはなっている

virtual node をメンテナンスする上での追加ツール


virtual nodeのトークンの値を変更する為のツールも1.2から登場する
  • shuffle
現在あるトークンの値を変えずにそのトークンを別のノードに移してシャッフルする
  • バランスツール
現在未実装、恐らく1.2.1から登場する

virtual node の使用の仕方

cassandra.yaml で設定を行いcassandraを起動するとトークンが作られる

(1.2.0 beta1 の cassandra.yaml)
# This defines the number of tokens randomly assigned to this node on the ring
# The more tokens, relative to other nodes, the larger the proportion of data
# that this node will store. You probably want all nodes to have the same number
# of tokens assuming they have equal hardware capability.
#
# If you leave this unspecified, Cassandra will use the default of 1 token for legacy compatibility,
# and will use the initial_token as described below.
#
# Specifying initial_token will override this setting.
#
# If you already have a cluster with 1 token per node, and wish to migrate to 
# multiple tokens per node, see http://wiki.apache.org/cassandra/Operations
# num_tokens: 256
num_tokensをコメントアウトして持ちたいトークンの数を指定する(デフォルトだと256が入っている)
また、initial_tokenの設定は無効になる

2012年12月5日水曜日

Leveled Compactionについて

Leveled Compactionについて(Cassandra勉強会第23回のスライド)

http://www.slideshare.net/seki_intheforest/leveled-compaction
 

Leveled Compactionを選ぶ上でよい場合とそうではない場合

よい場合
  • 読み込みのレスポンスを早くしたい
  • 書き込みより読み込みが多い
  • 一定のデータが頻度に更新され、使用される
  • 頻度にRowデータの削除などを行う
そうではない場合
  • I/Oのリソースが厳しい
  • 頻繁に大量の書き込みを行う
  • 一度きりの書き込みが多い
構造上SSTableを細分化するのでレスポンスや読み込みが早くなり
細かいSSTableのコンパクションも頻度に起こるので削除データもデータから消えやすいが
同時に細かいコンパクションが多発するので頻繁に大量の書き込みをしてI/Oのリソースを常に使う場合は
I/Oのリソースが足りなくなる可能性がありお勧めできない
 

2012年11月9日金曜日

astyanaxその1

astyanaxを使ってみる


astyanaxは主にNetflixが開発を行っている Cassandra Java Client でcassandraへの読み書きはもちろん、主に自動的にリングやノードの状態を見て負荷を平均になるようにコントロールできる新しいクライアントです



astyanaxのインストール


事前に準備しておくもの
  • maven2
  • git
  • JDK6
まずgitからastyanaxを取得してビルドします
git clone https://github.com/Netflix/astyanax.git

mvnを使用してローカルリポジトリにおきたい場合は取得したastyanaxのディレクトリ上で
mvn clean install

jarファイルだけほしい場合は
mvn package

astyanaxの依存関係のライブラリを取得したい場合は以下のコマンドでtarget/dependency に出力される
mvn dependency:copy-dependencies

またgradlewを使用してビルドしたい場合は
gradlew build

astyanaxで適当にCassandraを使用してみる


Cassandra側でKeyspaceとColumn Familyを作成しておく
create keyspace ks
    with placement_strategy = 'SimpleStrategy'
    and strategy_options = {replication_factor:2}

create column family cf
    with column_type = 'Standard'
    and comparator = 'UTF8Type'
    and default_validation_class = 'UTF8Type'
    and key_validation_class = 'UTF8Type'

以下のような単体で動かせるようなソースコードを作成(ソース名ConnectionTest.java)
import org.apache.log4j.PropertyConfigurator;

import com.netflix.astyanax.*;
import com.netflix.astyanax.AstyanaxContext.Builder;
import com.netflix.astyanax.impl.AstyanaxConfigurationImpl;
import com.netflix.astyanax.thrift.ThriftFamilyFactory;
import com.netflix.astyanax.model.ColumnFamily;
import com.netflix.astyanax.model.ColumnList;
import com.netflix.astyanax.model.Column;
import com.netflix.astyanax.model.Row;
import com.netflix.astyanax.model.Rows;
import com.netflix.astyanax.serializers.StringSerializer;
import com.netflix.astyanax.connectionpool.OperationResult;
import com.netflix.astyanax.connectionpool.exceptions.ConnectionException;
import com.netflix.astyanax.connectionpool.NodeDiscoveryType;
import com.netflix.astyanax.connectionpool.impl.CountingConnectionPoolMonitor;
import com.netflix.astyanax.connectionpool.impl.FixedRetryBackoffStrategy;
import com.netflix.astyanax.connectionpool.impl.SmaLatencyScoreStrategyImpl;
import com.netflix.astyanax.connectionpool.impl.BadHostDetectorImpl;
import com.netflix.astyanax.connectionpool.impl.ConnectionPoolType;
import com.netflix.astyanax.connectionpool.impl.ConnectionPoolConfigurationImpl;

public class ConnectionTest
{
    
    public static void main(String[] argv) throws ConnectionException{
        
        //適当にlog4jの設定を引数で渡してやる
        String filePath = argv[0];
        PropertyConfigurator.configure(filePath);
        
        //コネクション設定と開始
        AstyanaxContext<Keyspace> context = new AstyanaxContext.Builder()
            .forCluster("{クラスタ名}")
            .forKeyspace("ks")
            .withAstyanaxConfiguration(new AstyanaxConfigurationImpl()
                //.setDiscoveryType(NodeDiscoveryType.NONE) ノードの情報を意識しない場合の設定
                .setDiscoveryType(NodeDiscoveryType.RING_DESCRIBE) // リング内のノードを意識したアクセスの仕方に設定
                .setConnectionPoolType(ConnectionPoolType.TOKEN_AWARE) // ノードのトークンを意識したアクセスの仕方に設定
            )
            .withConnectionPoolConfiguration(new ConnectionPoolConfigurationImpl("MyConnectionPool")
                .setPort(9160) // 使用ポート
                .setMaxConnsPerHost(1) // 限界のコネクション数
                .setSeeds("{ホスト}:9160") //Seedsのアドレス
            )
            .withConnectionPoolMonitor(new CountingConnectionPoolMonitor())
            .buildKeyspace(ThriftFamilyFactory.getInstance());
        
        context.start();
        Keyspace keyspace = context.getEntity();
        
        //使用するカラムファミリの定義
        ColumnFamily<String, String> CF_TEST =
          new ColumnFamily<String, String>(
            "cf",              // カラムファミリ名
            StringSerializer.get(),  //キーのシリアライザ
            StringSerializer.get());   //カラムのシリアライザ
        
        //データの書込み
        MutationBatch m = keyspace.prepareMutationBatch();

        m.withRow(CF_TEST, "0001")
          .putColumn("value1", "hoge", null)
          .putColumn("value2", "hogehoge", null);
          
        m.withRow(CF_TEST, "0002")
          .putColumn("value1", "huga", null)
          .putColumn("value2", "hugahuga", null);
        
        try {
          OperationResult<Void> result = m.execute();
        } catch (ConnectionException e) {
        }
        
        //データの取得
        OperationResult<Rows<String, String>> result =
          keyspace.prepareQuery(CF_TEST)
                  .getKeySlice("0001", "0002")
                  .execute();
        
        //取得したデータの表示
        for (Row<String, String> row : result.getResult()) {
            System.out.println(row.getKey());
            for (Column<String> column : row.getColumns()) {
                System.out.println(column.getName() + " " + column.getStringValue());
            }
        }
    
    }
}

ビルドして実行(lib内に各jar、第一引数に適当なlog4jの設定ファイルで実行)
$ javac -cp ./lib/*:./ ConnectionTest.java
$ java -cp ./lib/*:./ ConnectionTest ./log4j.properties
0002
value1 huga
value2 hugahuga
0001
value1 hoge
value2 hogehoge

2012年10月31日水曜日

Cassandraインストールドキュメント

Cassandra推奨ハードウェア

Cassandra インストール

Cassandra のインストール(RHEL・CentOS編)

Cassandra のインストール(Debian・Ubuntu編)

Cassandra のインストール(Generic編)

その他サーバーの推奨設定

ユーザーリソースの限界値の設定
Disable Swap の挙動の変更
時間の同期

Cassandraの基本設定

基本的な設定項目
各ノードのリング内の位置設定
JVM のメモリ使用量設定

Cassandra 推奨ハードウェア構成

Cassandra推奨ハードウェア構成

■ノード構成
  • 動作確認環境用最小構成 3 台以上
  • プロダクション用最小構成 6 台以上推奨(9 台以上が望ましい)
  • Unix、Linux、Windows サーバーの稼働する環境

■HWスペック
  • メモリ8GB以上
  • HDD500GB以上(SATA可)
  • Commitlog 用とデータディスク用の 2 台のディスクを推奨
  • データディスク用としてSSDの使用は、尚、望ましい
  • Gigabit Ethernet推奨

  • AWS・EC2上では m1.large 以上必須

JWT(Jason Webトークン)を理解するJWT(Jason Webトークン)を理解する

JWT ( Jason Web トークン)を理解する   JSON Web Token はオープンスタンダードです。これは、任意の 2 つの機関 ( ユーザー、サーバー)間で情報を転送するために使用されます。 JWT では、ユーザーデー...