LoginSignup
2
3

More than 5 years have passed since last update.

node.js からDocker上のCassandraにアクセスする

Last updated at Posted at 2017-06-25

node.js からDocker上のCassandraにアクセスする。

KVSの一つとして、Cassandraというのがあると聞いて、node.jsからtypescriptアプリを作成し、Cassandoraにアクセスしてみる。
そのときの谷あり、谷ありの苦労を忘れないためのメモ

環境


  • Cassandra環境(3.10) : Docker on Linux(CentOS7)上に構築
  • Node環境(v8.1.2) : OSX 10.11.6 上に構築

Cassandraインストール


使っているバージョンが分からないのが嫌いなのでTAGを指定してLinux上に構築したDocker環境にインストール
時間があれば、Cassandraのクラスタ構成をチャレンジしてみよう。

$ docker pull cassandra:3.10
$ docker volume create --name main-cassandra-db
$ docker run -d --name main-cassandra -v main-cassandra-db:/var/lib/cassandra -p 9160:9160 -p 9042:9042 -m 1G cassandra:3.10

Linux上には、cqlshをインストールしていないので、Docker上のcqlshを使用してアクセス。立ち上がっていることを確認。

$ docker exec -it main-cassandra cqlsh
Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 3.10 | CQL spec 3.4.4 | Native protocol v4]
Use HELP for help.
cqlsh>

ただ、keyspaceなどをバッチ操作するのに、OSXからアクセスできた方が便利なので、OSX側にはcqlshをインストールする。
cqlshは、pythonで作られているので、pipコマンドでインストール。

$ python --version
Python 2.7.12
$ pip install cqlsh
$ cqlsh --version
cqlsh 5.0.1

ここで、最初の谷が。
OSXからLinux上のCassandraにアクセスしようとすると失敗する。(192.168.45.131はLinuxのIPアドレス)

$ cqlsh 192.168.45.131
Connection error: ('Unable to connect to any servers', {'192.168.45.131': ProtocolError("cql_version '3.3.1' is not supported by remote (w/ native protocol). Supported versions: [u'3.4.4']",)})

原因はよく分からないが、ここを参考に、回避
docker上とcqlshのバージョンは同じなのだから、ええ案配にサーバーとCQLバージョンくらいネゴってほしい。

cqlshの設定ファイルに記載することでバージョンは指定しなくてもよいようだ。ここを参考に ~/.cassandra/cqlshrc を作成する。
最初、 ~/.cqlshrc を作成していたのだが、 ~/.cassandra/cqlshrc が無いと ~/.cassandra/cqlshrc にリネイムされた。

~/.cassandra/cqlshrc
[cql]
version = 3.4.4
$ cqlsh 192.168.45.131
Connected to Test Cluster at 192.168.45.131:9042.
[cqlsh 5.0.1 | Cassandra 3.10 | CQL spec 3.4.4 | Native protocol v4]
Use HELP for help.
cqlsh>

試しに、Pythonのバージョンを3.5.2に上げてみる。が、そもそも動かない。
調べれば分かることだが、きっと Python2.7しかサポートしていないのでは?
cqlshのソースを確認したところ、きっちりPythonのバージョンをチェックしていた。

if sys.version_info[:2] != (2, 7):
    sys.exit("\nCQL Shell supports only Python 2.7\n")

Node.jsのインストール


Node.jsをインストールする。4月にNode.jsで遊んでいた頃はv6だったが、最近v8が出たようなのでv8で試してみる。

$ nodebrew install-binary v8.1.2
$ nodebrew use v8.1.2
use v8.1.2
$ node --version
v8.1.2

TypescriptでCassandraのクライアントを作るので、必要な定義ファイルを作成する。

  • package.json
package.json
{
  "name": "cassandra",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "./node_modules/.bin/tsc -w -d --sourceMap"
  }
}
  • tsconfig.json
tsconfig.json
{
  "compilerOptions": {
    /* Basic Options */
    "target": "es2015",                /* Specify ECMAScript target version. */
    "module": "commonjs",              /* Specify module code generation. */
    "declaration": true,               /* Generates corresponding '.d.ts' file. */
    "sourceMap": true,                 /* Generates corresponding '.map' file. */

    /* Strict Type-Checking Options */
    "strict": true                     /* Enable all strict type-checking options. */
  },

  "exclude": [
   "node_modules"
  ]
}

必要なパッケージをインストール

$ npm install --save-dev typescript
$ npm install --save cassandra-driver
$ npm install --save-dev @types/cassandra-driver

キースペース・テーブルの作成、および、データ投入用スクリプト


キースペース・テーブルの作成、および、データ投入スクリプト

sample.cql
create keyspace IF NOT EXISTS Sample
       with replication = {'class':'SimpleStrategy','replication_factor':1};

create table IF NOT EXISTS Sample.Cookie (
  id text primary key,     // Primary key SHA256 hash
  cookie text,             // Cookie Text
  update_time timestamp,   // Update Time
  create_time timestamp    // Create Time
) WITH comment='Cookie management table.';

begin batch

insert into Sample.Cookie ( id, create_time ) VALUES ( '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b', toTimestamp(now()) );
insert into Sample.Cookie ( id, create_time ) VALUES ( 'd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35', toTimestamp(now()) );

apply batch;

Cassandraへのデータ投入


先に作成したスクリプトを使用して、Docker上に構築したCassandraに対して、キースペース、テーブル、データ投入を行う。

$ cqlsh 192.168.45.131 < sample.cql

-f オプションでスクリプトを指定すると、データ投入スクリプトに書いたコメント「// Cookie Text」やcommentプロパティに日本語を使用すると以下のエラーメッセージが出る。
どうやら create tableから最後のコロンまでの間に日本語が存在すると駄目なようだ。

sample.cql:11:'ascii' codec can't encode characters in position 134-137: ordinal not in range(128)
sample.cql:26:InvalidRequest: Error from server: code=2200 [Invalid query] message="unconfigured table cookie"

しかし、docker中のcqlshを使用するとエラーが起きることなくテーブルの作成できる。Pythonのバージョンの違い??
ではないようだ、OSXに2.7.9をインストールしてみたが、エラーは変わらなかった。

$ docker exec -it main-cassandra bash
root@32e0a9c14cc7:/# cqlsh -f /tmp/sample.cql
root@32e0a9c14cc7:/# python --version
Python 2.7.9

node.jsからTypescriptアプリを使用してCassandraへアクセスする。


cassandra-driver には、ここにあるように、Promiseスタイルか、コールバック関数スタイルのどちらかで利用する必要がある。ここは時代に合わせてPromiseスタイルで行くことにした。
しかし、npmに登録されている @types/cassandra-driver は、コールバック関数スタイルしか方定義されていない。そのため、Promiseスタイルで記述すると以下のようにエラーとなる。

$ npm start
> tsc -w -d --sourceMap
src/client.ts(17,44): error TS2339: Property 'then' does not exist on type 'void'.
src/client.ts(24,52): error TS2339: Property 'then' does not exist on type 'void'.

githubに、Promiseスタイルの方定義をしたファイルが存在するので、上書きすることでPromiseスタイルのtypescriptソースがコンパイルできるようになる。

$ wget https://raw.githubusercontent.com/aliem/DefinitelyTyped/feature/cassandra-driver-promises/cassandra-driver/index.d.ts \
         -O node_modules/@types/cassandra-driver/index.d.ts
  • client.ts Cassandraにアクセスするクライアントモジュール
client.ts
// このソースをコンパイルするには、npm --save-dev @types/cassandra-driver を実行した後、
// node_modules/@types/cassandra-driver/index.d.ts を以下のファイルと置き換える必要がある。
// master には、callback形式の型は登録されているが、Promise形式の型が登録されていないため、コンパイルエラーとなる。
// なぜか、masterにマージされていない。おそらく、lintエラー取るためにかなり手を入れているようだから、その影響かもしれない。
// wget https://raw.githubusercontent.com/aliem/DefinitelyTyped/feature/cassandra-driver-promises/cassandra-driver/index.d.ts \
//         -O node_modules/@types/cassandra-driver/index.d.ts
//
import {types, Client, QueryOptions} from 'cassandra-driver';

export class client extends Client {
    constructor(hosts: string) {
        super({ contactPoints: [ hosts ] });
    }

    public select(id: string): Promise<any> {
        const query: string = "select * from Sample.Cookie where id = ?";
        return this.execute(query, [ id ]).then((result: types.ResultSet) => {
            return (result.rows);
        });
    }

    public update(id: string, cookie: string ): Promise<any> {
        const query: string = "update Sample.Cookie set cookie = ?, update_time = toTimestamp(now()) where id = ? IF EXISTS";
        return this.execute(query, [ cookie, id ]).then((result: types.ResultSet) => {
            return (result);
        });
    }
}
  • main.ts クライアントモジュールを使用して、SELECTとUPDATEを実行
main.ts
//
import {client} from './client';

var c: client;

new Promise((resolve) => {
    c = new client(process.argv[2]);
    resolve();
}).then(async () => {
    const result = await c.select('6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b');
    console.log(result);
}).then(async () => {
    const result = await c.update('6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b', 'cookie string');
    if (result.rows && result.rows[0]['[applied]']) {
        console.log('更新成功');
    } else {
        console.log('更新失敗');
    }
    console.log(result);
}).then(async () => {
    const result = await c.select('6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b');
    console.log(result);
}).then(async () => {
    await c.shutdown();
}, async (err) => {
    console.log(err);
    await c.shutdown();
});

実行結果


$ node src/main.js 192.168.45.131
[ Row {
    id: '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b',
    cookie: 'cookie string',
    create_time: 2017-06-25T11:32:43.529Z,
    update_time: 2017-06-25T11:41:40.859Z } ]
更新成功
ResultSet {
  info: 
   { queriedHost: '192.168.45.131:9042',
     triedHosts: {},
     achievedConsistency: 10,
     traceId: undefined,
     warnings: undefined,
     customPayload: undefined },
  rows: [ Row { '[applied]': true } ],
  rowLength: 1,
  columns: [ { name: '[applied]', type: [Object] } ],
  pageState: null,
  nextPage: undefined }
[ Row {
    id: '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b',
    cookie: 'cookie string',
    create_time: 2017-06-25T11:32:43.529Z,
    update_time: 2017-06-26T10:37:42.001Z } ]
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3