CLOVER🍀

That was when it all began.

CassandraのJavaクライアントプログラミング

とりあえず、こちらを参考に
http://wiki.apache.org/cassandra/ClientExamples
Groovyで書いてみました。

お題は、AmazonでのCassandra関連の本の情報に変えてあります。

対象のキースペースとカラムファミリーの情報は、いたって普通です。

[default@Bookshelf] describe Bookshelf;

WARNING: CQL3 tables are intentionally omitted from 'describe' output.
See https://issues.apache.org/jira/browse/CASSANDRA-4377 for details.

Keyspace: Bookshelf:
  Replication Strategy: org.apache.cassandra.locator.SimpleStrategy
  Durable Writes: true
    Options: [replication_factor:1]
  Column Families:
    ColumnFamily: Books
      Key Validation Class: org.apache.cassandra.db.marshal.UTF8Type
      Default column value validator: org.apache.cassandra.db.marshal.UTF8Type
      Columns sorted by: org.apache.cassandra.db.marshal.UTF8Type
      GC grace seconds: 864000
      Compaction min/max thresholds: 4/32
      Read repair chance: 0.1
      DC Local Read repair chance: 0.0
      Populate IO Cache on flush: false
      Replicate on write: true
      Caching: KEYS_ONLY
      Bloom Filter FP chance: default
      Built indexes: []
      Compaction Strategy: org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy
      Compression Options:
        sstable_compression: org.apache.cassandra.io.compress.SnappyCompressor

サンプルコードは、こちら。
getting_started.groovy

import java.nio.ByteBuffer

@Grab('org.apache.cassandra:cassandra-all:1.2.4')
import org.apache.cassandra.thrift.Cassandra
import org.apache.cassandra.thrift.Column
import org.apache.cassandra.thrift.ColumnOrSuperColumn
import org.apache.cassandra.thrift.ColumnParent
import org.apache.cassandra.thrift.ConsistencyLevel
import org.apache.cassandra.thrift.KeyRange
import org.apache.cassandra.thrift.KeySlice
import org.apache.cassandra.thrift.SlicePredicate
import org.apache.cassandra.thrift.SliceRange

import org.apache.thrift.transport.TTransport
import org.apache.thrift.transport.TFramedTransport
import org.apache.thrift.transport.TSocket
import org.apache.thrift.protocol.TProtocol
import org.apache.thrift.protocol.TBinaryProtocol

def transport = new TFramedTransport(new TSocket('localhost', 9160))
def protocol = new TBinaryProtocol(transport)

def client = new Cassandra.Client(protocol)

transport.open()

try {
    def keyspaceName = 'Bookshelf'
    def columnFamilyName = 'Books'

    client.set_keyspace(keyspaceName)

    def timestamp = System.currentTimeMillis()

    // 1冊目登録
    def id1 = '1'

    def isbnColumn1 = new Column(ByteBuffer.wrap('isbn'.getBytes('UTF-8')))
    isbnColumn1.setValue(ByteBuffer.wrap('978-4873115290'.getBytes('UTF-8')))
    isbnColumn1.setTimestamp(timestamp)

    def nameColumn1 = new Column(ByteBuffer.wrap('name'.getBytes('UTF-8')))
    nameColumn1.setValue(ByteBuffer.wrap('Cassandra'.getBytes('UTF-8')))
    nameColumn1.setTimestamp(timestamp)

    def priceColumn1 = new Column(ByteBuffer.wrap('price'.getBytes('UTF-8')))
    priceColumn1.setValue(ByteBuffer.wrap('3570'.getBytes('UTF-8')))
    priceColumn1.setTimestamp(timestamp)

    def columnParent = new ColumnParent(columnFamilyName)

    client.insert(ByteBuffer.wrap(id1.getBytes('UTF-8')),
                  columnParent,
                  isbnColumn1,
                  ConsistencyLevel.ALL)
    client.insert(ByteBuffer.wrap(id1.getBytes('UTF-8')),
                  columnParent,
                  nameColumn1,
                  ConsistencyLevel.ALL)
    client.insert(ByteBuffer.wrap(id1.getBytes('UTF-8')),
                  columnParent,
                  priceColumn1,
                  ConsistencyLevel.ALL)

    // 2冊目登録
    def id2 = '2'

    def isbnColumn2 = new Column(ByteBuffer.wrap('isbn'.getBytes('UTF-8')))
    isbnColumn2.setValue(ByteBuffer.wrap('978-4798128436'.getBytes('UTF-8')))
    isbnColumn2.setTimestamp(timestamp)

    def nameColumn2 = new Column(ByteBuffer.wrap('name'.getBytes('UTF-8')))
    nameColumn2.setValue(ByteBuffer.wrap('Cassandra実用システムインテグレーション'.getBytes('UTF-8')))
    nameColumn2.setTimestamp(timestamp)

    def priceColumn2 = new Column(ByteBuffer.wrap('price'.getBytes('UTF-8')))
    priceColumn2.setValue(ByteBuffer.wrap('3360'.getBytes('UTF-8')))
    priceColumn2.setTimestamp(timestamp)

    def columnParent2 = new ColumnParent(columnFamilyName)

    client.insert(ByteBuffer.wrap(id2.getBytes('UTF-8')),
                  columnParent,
                  isbnColumn2,
                  ConsistencyLevel.ALL)
    client.insert(ByteBuffer.wrap(id2.getBytes('UTF-8')),
                  columnParent,
                  nameColumn2,
                  ConsistencyLevel.ALL)
    client.insert(ByteBuffer.wrap(id2.getBytes('UTF-8')),
                  columnParent,
                  priceColumn2,
                  ConsistencyLevel.ALL)

    // カラムを、キー指定で取得
    def predicate = new SlicePredicate()
    predicate.setSlice_range(
                             new SliceRange(
                                            ByteBuffer.wrap(new byte[0]),
                                            ByteBuffer.wrap(new byte[0]),
                                            false,
                                            100))

    def columnByKey1 =
        client.get_slice(ByteBuffer.wrap(id1.getBytes()),
                         columnParent,
                         predicate,
                         ConsistencyLevel.ALL);
    println("columnByKey1 => " + columnByKey1)

    def columnByKey2 =
        client.get_slice(ByteBuffer.wrap(id2.getBytes()),
                         columnParent,
                         predicate,
                         ConsistencyLevel.ALL);
    println("columnByKey2 => " + columnByKey2)

    // 全キーを取得
    def keyRange = new KeyRange(100)
    keyRange.setStart_key(new byte[0])
    keyRange.setEnd_key(new byte[0])
    def keySlices = client.get_range_slices(columnParent, predicate, keyRange, ConsistencyLevel.ONE)
    println("size => " + keySlices.size())
    println("keySlices => " + keySlices)

    for (KeySlice ks : keySlices) {
        println("keySlice => " + new String(ks.getKey()))
    }
} finally {
    transport.close()
}

Groovyとはいえ、変数宣言の型を書いてない以外は、極力Javaっぽい書き方にしてあります。setterとかも普通に使ってますし。
それにしても、ByteBuffer使いまくり。バイト値に近いところでプログラミングしてる感満載ですね。

依存関係の解決は、

@Grab('org.apache.cassandra:cassandra-all:1.2.4')

だけでなんとかなるみたいです。もっといろいろやらなくちゃいけないかと思いましたけど、これだけでいいのか…。

その他、Javadocが最初見つからなくて、Cassandraのバイナリディストリビューションの中に入っているのにオライリー本を読んでいて気付きました。

Cassandra

Cassandra

実行すると、SLF4Jの警告が出るものの一応動作します。

$ groovy getting_started.groovy 
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
columnByKey1 => [ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 08 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E, value:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 08 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 38 37 33 31 31 35 32 39 30, timestamp:1366554767965)), ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 08 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 38 37 33 31 31 35 32 39 30 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65, value:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 08 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 38 37 33 31 31 35 32 39 30 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 09 43 61 73 73 61 6E 64 72 61, timestamp:1366554767965)), ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 08 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 38 37 33 31 31 35 32 39 30 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 09 43 61 73 73 61 6E 64 72 61 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00..., value:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 08 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 38 37 33 31 31 35 32 39 30 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 09 43 61 73 73 61 6E 64 72 61 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00..., timestamp:1366554767965))]
columnByKey2 => [ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 09 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E, value:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 09 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36, timestamp:1366554767965)), ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 09 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65, value:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 09 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64 72 61 E5 AE 9F E7 94 A8 E3 82 B7 E3 82 B9 E3 83 86 E3 83 A0 E3 82 A4..., timestamp:1366554767965)), ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 09 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64 72 61 E5 AE 9F E7 94 A8 E3 82 B7 E3 82 B9 E3 83 86 E3 83 A0 E3 82 A4..., value:80 01 00 02 00 00 00 09 67 65 74 5F 73 6C 69 63 65 00 00 00 09 0F 00 00 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64 72 61 E5 AE 9F E7 94 A8 E3 82 B7 E3 82 B9 E3 83 86 E3 83 A0 E3 82 A4..., timestamp:1366554767965))]
size => 2
keySlices => [KeySlice(key:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32, columns:[ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E, value:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36, timestamp:1366554767965)), ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65, value:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64..., timestamp:1366554767965)), ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64..., value:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64..., timestamp:1366554767965))]), KeySlice(key:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64..., columns:[ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64..., value:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64..., timestamp:1366554767965)), ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64..., value:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64..., timestamp:1366554767965)), ColumnOrSuperColumn(column:Column(name:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64..., value:80 01 00 02 00 00 00 10 67 65 74 5F 72 61 6E 67 65 5F 73 6C 69 63 65 73 00 00 00 0A 0F 00 00 0C 00 00 00 02 0B 00 01 00 00 00 01 32 0F 00 02 0C 00 00 00 03 0C 00 01 0B 00 01 00 00 00 04 69 73 62 6E 0B 00 02 00 00 00 0E 39 37 38 2D 34 37 39 38 31 32 38 34 33 36 0A 00 03 00 00 01 3E 2D 02 F2 5D 00 00 0C 00 01 0B 00 01 00 00 00 04 6E 61 6D 65 0B 00 02 00 00 00 36 43 61 73 73 61 6E 64..., timestamp:1366554767965))])]
keySlice => 2
keySlice => 1

toStringしただけだと、取れる値がすごいことに…。

cassandra-cliでも見てみます。

[default@Bookshelf] list Books;
Using default limit of 100
Using default column limit of 100
-------------------
RowKey: 2
=> (column=isbn, value=978-4798128436, timestamp=1366554767965)
=> (column=name, value=Cassandra実用システムインテグレーション, timestamp=1366554767965)
=> (column=price, value=3360, timestamp=1366554767965)
-------------------
RowKey: 1
=> (column=isbn, value=978-4873115290, timestamp=1366554767965)
=> (column=name, value=Cassandra, timestamp=1366554767965)
=> (column=price, value=3570, timestamp=1366554767965)

2 Rows Returned.
Elapsed time: 8.45 msec(s).

ちゃんと入ってますね。

Thriftを使ったAPIはちゃんと追っかけられていないので、また今度見ます。