CLOVER🍀

That was when it all began.

NoSQL Aerospikeで遊ぶ

ちょっと前々から気になっていた、NoSQL、Aerospikeで遊んでみました。

Aerospike
http://www.aerospike.com/

日本語の参考情報は、こちらに。

Aerospikeを採用した話
http://tech.im-dmp.net/archives/421

Aerospikeを採用した話2
http://tech.im-dmp.net/archives/1551

高速なNoSQLらしく、今月のWEB+DB PRESSにも載っていましたね。SSDに対しての最適化があるらしいですが、メモリでも普通にディスクも使えるようです。

OSSになったあたりから、ちょこちょこと気になっていたものです。

ま、さわりだけですけど、遊んでみました。クラスタ構成もできるようなのですが、今回は単一Nodeとします。

インストール

こちらのドキュメントを見ながら、インストール。

Install on Ubuntu
ww.aerospike.com/docs/operations/install/linux/ubuntu/

その他のOSは、こちらからリンクをたどってみてください。

Install Aerospike
http://www.aerospike.com/docs/operations/install/

で、自分の場合はUbuntuなので、以下のコマンドでインストール。

$ wget -O aerospike.tgz 'http://aerospike.com/download/server/latest/artifact/ubuntu12'
$ tar -xvf aerospike.tgz
$ cd aerospike-server-community-*-ubuntu12*
$ sudo ./asinstall

このコマンドでは、Aerospike ServerとAerospike Toolsが入るようです。

起動。

$ sudo service aerospike start

Javaからアクセスしてみる

Aerospike Serverが起動したので、Javaからアクセスしてみます。

Introduction
http://www.aerospike.com/docs/client/java/

プログラムを書くのに記述した、Maven依存関係は以下のとおり。

        <dependency>
            <groupId>com.aerospike</groupId>
            <artifactId>aerospike-client</artifactId>
            <version>3.1.3</version>
        </dependency>
        
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.1.0</version>
            <scope>test</scope>
        </dependency>

JUnitとAssertJはテスト用ですね。

で、先ほどのIntroductionに習ってまずはこんなコードを書いてみました。
src/test/java/org/littlewings/aerospike/AerospikeClientTest.java

package org.littlewings.aerospike;

import java.util.stream.IntStream;

import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Record;
import com.aerospike.client.policy.Policy;
import com.aerospike.client.policy.QueryPolicy;
import com.aerospike.client.policy.WritePolicy;
import com.aerospike.client.query.Filter;
import com.aerospike.client.query.RecordSet;
import com.aerospike.client.query.Statement;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class AerospikeClientTest {
    @Test
    public void testAerospikeClientGettingStarted() {
        try (AerospikeClient client = new AerospikeClient("localhost", 3000)) {
            // String namespace = "my-namespace";  // デフォルトで、「test」というnamespaceが用意してある
            String namespace = "my-namespace";
            String setName = "my-set";

            Key key1 = new Key(namespace, setName, "key1");
            Bin bin1 = new Bin("m1", "value1");
            Bin bin2 = new Bin("m2", "value2");

            WritePolicy writePolicy = new WritePolicy();

            client.put(writePolicy, key1, bin1, bin2);

            Policy policy = new Policy();

            Record record = client.get(policy, key1);

            assertThat(record.getString("m1"))
                    .isEqualTo("value1");
            assertThat(record.getString("m2"))
                    .isEqualTo("value2");

            assertThat(client.get(policy, new Key("test", "my-set", "key2")))
                    .isNull();
        }
    }
}

Aerospikeへの接続ポートは3000で、さらにデータ構造にアクセスするにはnamespaceとsetを指定する必要があるようです。データベースとテーブルに近いもの…かな…。

Data Model
http://www.aerospike.com/docs/architecture/data-model.html

管理単位としては、namespace、set、recordという感じらしいです。

また、データの保存先はnamespace単位で指定するようです。

で、プログラムを実行してみます。

com.aerospike.client.AerospikeException: Error Code 20: Namespace not found
	at com.aerospike.client.command.WriteCommand.parseResult(WriteCommand.java:72)
	at com.aerospike.client.command.SyncCommand.execute(SyncCommand.java:56)
	at com.aerospike.client.AerospikeClient.put(AerospikeClient.java:310)
	at org.littlewings.aerospike.AerospikeClientTest.testAerospikeClientGettingStarted(AerospikeClientTest.java:33)

エラーになりました。

今回、「my-namespace」というnamespaceを指定したのですが、先にnamespaceは作成しておく必要があるようです。

            // String namespace = "my-namespace";  // デフォルトで、「test」というnamespaceが用意してある
            String namespace = "my-namespace";

Aerospikeはデフォルトで「test」というnamespaceが用意されており(あと、「bar」もあるみたい…)、こちらならすぐに使えますがここはnamespaceを追加してみます。

Namespace Configuration
https://www.aerospike.com/docs/operations/configure/namespace/

Add Namespace: Adding New Namespace
https://discuss.aerospike.com/t/add-namespace-adding-new-namespace/680

以下のファイルを編集します。

$ sudo vim /etc/aerospike/aerospike.conf

ここでは、単純に「test」namespaceの内容をコピーして作成しました。データの保存先は、メモリになります。

namespace my-namespace {
        replication-factor 2
        memory-size 4G
        default-ttl 30d # 30 days, use 0 to never expire/evict.

        storage-engine memory
}

この内容を、「/etc/aerospike/aerospike.conf」ファイルの1番したに追記しています。

編集したら、Aerospike Serverを再起動します。

$ sudo service aerospike restart

この状態でプログラムを実行すると、今度はうまく動くようになります。

Queryを実行してみる

続いて、今度はQueryを実行してみます。

Query Records
http://www.aerospike.com/docs/client/java/usage/query/query.html

こんなプログラムを書いてみます。

    @Test
    public void testQuery() {
        try (AerospikeClient client = new AerospikeClient("localhost", 3000)) {
            String namespace = "my-namespace";
            String setName = "query-set";

            WritePolicy writePolicy = new WritePolicy();
            IntStream
                    .rangeClosed(0, 100)
                    .forEach(i ->
                            client.put(
                                    writePolicy,
                                    new Key(namespace, setName, "key" + i),
                                    new Bin("member", "value" + i)));

            Statement statement = new Statement();
            statement.setNamespace(namespace);
            statement.setSetName(setName);
            statement.prepare(true);
            statement.setFilters(
                    Filter.equal("member", "value1")
                    );

            QueryPolicy queryPolicy = new QueryPolicy();
            try (RecordSet recordSet = client.query(queryPolicy, statement)) {
                while (recordSet.next()) {
                    Key key = recordSet.getKey();
                    Record record = recordSet.getRecord();

                    assertThat(record.getString("member"))
                            .isEqualTo("value1");
                }
            }
        }
    }

実行。

com.aerospike.client.AerospikeException: Error Code 201: Index not found
	at com.aerospike.client.command.MultiCommand.parseGroup(MultiCommand.java:96)
	at com.aerospike.client.command.MultiCommand.parseResult(MultiCommand.java:71)
	at com.aerospike.client.command.SyncCommand.execute(SyncCommand.java:56)
	at com.aerospike.client.query.QueryExecutor$QueryThread.run(QueryExecutor.java:137)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

エラーになりました。どうやら、インデックスが必要なようです。

ちなみに、全件スキャンならインデックスはいらないらしく、以下の部分を削除すると動作するようになります。

            statement.setFilters(
                    Filter.equal("member", "value1")
                    );

では、インデックスを作ってみます。

aql – Index Management
http://www.aerospike.com/docs/tools/aql/index_management.html

今回、「member」というものに対してQueryを投げようとしているので、

            statement.setFilters(
                    Filter.equal("member", "value1")
                    );

ここに対してインデックスを貼ることにします。

aqlを起動。

$ aql
Aerospike Query
Copyright 2013 Aerospike. All rights reserved.

aql> 

インデックス作成。

aql> CREATE INDEX my-index ON my-namespace.query-set (member) STRING
OK, 1 index added.

できました。

ここまですると、先ほどのQueryを投げるプログラムを動作させることができます。

Dockerイメージ

Docker HubにAerospike ServerのDockerイメージがあるので、こちらでも簡単に遊べます。

docker run -tid --name aerospike -p 3000:3000 -p 3001:3001 -p 3002:3002 -p 3003:3003 aerospike/aerospike-server

Dockerfileの雰囲気からすると、Aerospike ServerのみでAerospike Toolsは入ってなさそうな感じです。

https://registry.hub.docker.com/u/aerospike/aerospike-server/

この場合、「test」などの用意されているnamespaceしか使えない気がしますが、簡単に試すならこちらでもよいと思います。