2014年06月10日

NoSQL - SQL も使えるドキュメント指向データベース CRATE 入門

NoSQL でドキュメント指向データベースといえば MongoDB が有名・話題ですが、今日は同じドキュメント指向データベースでも SQL インタフェースのサポートによる既存資産・知識とのシームレスな統合、NO DATABASE ADMINISTRATION が売り文句の CRATE を実践してみますよ!

logo-elephant

CRATE - document oriented cluster data store のセットアップ


CRATE は Java と一部のインタフェースが Python で記述されたドキュメント指向データベースで OS プラットフォームには Windows, Mac OS, Linux をサポートしています。 ソースコードは GitHub 上で公開しておりライセンスには AL-II 、有償テクニカル・サポートも販売しています。

CRATE プロジェクト・ページはこちら。 

CRATE - document oriented cluster data store


レプリケーション、シャーディング、パーティショニングなどスケールと冗長性の実現に求められる機能を一通りサポートしています。

CRATE CLUSTER-V2 overview

現時点のバージョンは 0.38.3 と発展途上ではありますが GitHub ページ を見ても分かる通り非常にアクティビティの高いプロジェクトです。

じゃあ、早速始めましょうか。 プロジェクト・ページからリンクを辿り最新のパッケージ(tar.gz)をダウンロード、適当な場所に解凍しましょう。

$ tar xvzf crate-0.38.3.tar.gz


これでインストールはおしまい(Java 7+, Python 2.7+ も必要)。

次のコマンドで crate を起動しましょう(バックグラウンドで起動するなら crate -d)。

$ ./crate-0.38.3/bin/crate

[X-Ray] version[1.1.1], pid[xxxx], build[${build/NA]
[X-Ray] initializing ...
[X-Ray] loaded [blob, sql, udc, crate-core, admin-ui]
[io.crate.rest.CrateRestFilter] Elasticsearch HTTP REST API not enabled
[X-Ray] initialized
[X-Ray] starting ...
[X-Ray] BlobService.doStart() io.crate.blob.BlobService
[X-Ray] bound_address {inet[/x.x.x.x:4200]}, publish_address {inet[/x.x.x.x:4200]}
[X-Ray] bound_address {inet[/x.x.x.x:4300]}, publish_address {inet[/x.x.x.x:4300]}
[X-Ray] started

ログに出力された bound_address 行を確認しておきましょう。 もし意図しないインタフェースで起動していたら crate-x.x.x/config/crate.yml を編集すればバインドするインタフェースを network.host で直接指定出来ます。

# Set both 'bind_host' and 'publish_host':
#
# network.host: 192.168.0.1
network.host: x.x.x.x

同じ手順、複数のホストで crate を起動すると動的にクラスタが構成・登録されていきます。

crate コマンドライン・インタフェースは crash。 join、トランザクション管理(commit, rollback)が出来ないのは MongoDB と同様ですが、データの操作は Crate SQL と呼ばれる SQL ライクなインタフェースを使って行います。
 
$ ./crate-0.38.3/bin/crash
+-----------------------+---------------+-----------+---------+
| server_url            | node_name     | connected | message |
+-----------------------+---------------+-----------+---------+
| http://127.0.0.1:4200 | Scarlet Witch | TRUE      | OK      |
+-----------------------+---------------+-----------+---------+
CONNECT OK
cr> help;

Documented commands (type help ):
========================================
ALTER    CRATE   DROP  INSERT   SELECT  connect  create  exit    refresh
CONNECT  CREATE  EOF   QUIT     UPDATE  copy     delete  insert  select
COPY     DELETE  EXIT  REFRESH  alter   crate    drop    quit    update

Undocumented commands:
======================
HELP  help

cr>

接続に失敗(CONNECT ERROR)するようであれば cr> connect x.x.x.x:4200; で直接アドレス指定してみましょう。

さて、適当なテーブルを作ってみましょうか。

cr> create table hoge (
... id integer primary key,
... hoge string
... );
CREATE OK (0.922 sec)

ね、SQL/RDBMS ライクで馴染みやすいでしょ。 続いて適当なデータを登録。

cr> INSERT INTO hoge (id, hoge) values (1, 'hogehoge');
INSERT OK, 1 row affected (0.164 sec)

cr> select * from hoge;
+----------+----+
| hoge     | id |
+----------+----+
| hogehoge |  1 |
+----------+----+
SELECT 1 row in set (0.189 sec)

表示されたカラム順が気になるかもしれませんね。 crate ではカラム順が無指定の場合はアルファベット・辞書順ソートで指定されたものと見なす、という暗黙のルールがあるのです。

つまり、同じテーブルへの INSERT をこう書くことも出来ます。

cr> INSERT INTO hoge values ('hogehoge', 2);

さらに crate はドキュメント指向データベース/スキーマレスですから、アプリケーション側のコードだけを修正すれば存在しないカラムを指定してもエラーにはなりません。
 
次の例では事前に定義されていない none というカラムを含む INSERT 文を実行していますがエラーにはならず動的に列が追加されています。

cr> INSERT INTO hoge (id, hoge, none) values (2, 'hogehoge', 'none');
INSERT OK, 1 row affected (0.068 sec)
cr> select * from hoge;
+----------+----+------+
| hoge     | id | none |
+----------+----+------+
| hogehoge |  1 | NULL |
| hogehoge |  2 | none |
+----------+----+------+
SELECT 2 rows in set (0.008 sec)


但し、MongoDB とは違い存在しないテーブル(コレクション)へ INSERT するのはNG。

cr> INSERT INTO foo (id, bar) values (1, 'bar');
SQLActionException[Table 'foo' unknown]

あまり自由・柔軟過ぎるのもバグに気が付きにくいので良いかも。

crate には Web インタフェースも用意されています。 次の URL をブラウザで開いてみましょう。

http://[your crate host]:4200/admin

Console メニューから Crate SQL を実行可能。

01 crate nosql admin console


認識している crate ノード及びその状態もこの Web インタフェース(CLUSTER メニュー)から確認出来ますよ。

02 admin console - clusters


GET STARTED メニューを使えば Twitter 上のツイートを crate へ一括登録してパフォーマンス・テストを始めることも可能です。


CRATE でドキュメント指向&クラスター DB 体験


例えばビデオ・コンテンツとそれに関するタグを関連付ける次のような表・関係があったとしましょうか。

old n-busted flat tables

crate のようなドキュメント指向データベースでは列指向の RDBMS とは違いデータ構造を予め決定しておく必要がありませんから、次のように1つのテーブルで表現出来ます。

cr> create table videos (
... id string primary key,
... name string,
... tags array(string),
... price object as (
... buy integer,
... rental integer
... ),
... released_at timestamp,
... INDEX tags_index using fulltext(tags)
... );
CREATE OK (0.439 sec)
cr>

tags フィールドを可変長の array 型としてタグを管理します。 価格を管理する price カラムで指定している object はネスティング(入れ子)が可能なデータ型です。

また、crate はシャーディングと呼ばれるデータ分散・並列I/O、レプリケーション(複製)によるデータの冗長化にもデフォルトで対応しており、先ほどのように特に条件を指定しない場合は次の DDL と同じ意味になります(5 シャード、1 レプリカ)。

cr> create table videos (
... id string primary key,
... name string,
... tags array(string),
... price object as (
... buy integer,
... rental integer
... ),
... released_at timestamp,
... INDEX tags_index using fulltext(tags)
... ) clustered into 5 shards with (number_of_replicas=1);
CREATE OK (0.730 sec)


さあ、データを登録しましょうか。 現在のバージョンでは crash 経由で object 型のカラムへ INSERT することは出来ないので Python とその ORM (Object-Relational Mapping) である SQLAlchemy を使った簡単なアプリケーションを作ってみます。

まずは pip を使って crate/python 環境を作ってから、

$ sudo apt-get install python-pip
$ sudo pip install crate
$ sudo pip install -U crate
$ sudo pip install SQLAlchemy


こんなコードを用意(適当・・・)。

# -*- coding: utf-8 -*-
from crate.client.sqlalchemy.types import Object, ObjectArray

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import Column
from sqlalchemy.types import String, DATETIME
from sqlalchemy.engine import create_engine
from sqlalchemy.orm.session import sessionmaker

from uuid import uuid4
from datetime import datetime
from time import sleep

Base = declarative_base()

def gen_key():
  return str(uuid4())

class Video(Base):
   __tablename__ = 'videos'
   
   CAT_ACTION    = 'Action'
   CAT_VIOLENCE  = 'Violence'
   CAT_LOVE      = 'Love'
   CAT_ADVENTURE = 'Adventure'
   CAT_HORROR    = 'Horror'
   CAT_CLASSIC   = 'Classic'
   CAT_ANIME     = 'Anime'
   CAT_FANTASY = 'Fantasy'
   
   PRICE_TYPE_BUY     = 'buy'
   PRICE_TYPE_RENTAL = 'rental'
   PRICE_TYPE_COUPON = 'coupon'
   
   id = Column(String, primary_key=True, default=gen_key)
   name = Column(String)
   tags = Column(ObjectArray)
   price = Column(Object)
   released_at = Column(DATETIME, nullable=False, default=datetime.now)

if __name__ == '__main__':
  print "starting..."
  # consists of multiple server it is recommended to connect to all of them.
  # This enables the DB-API layer to use round-robin to distribute the load
  # and skip a server if it becomes unavailable.
  #create_engine('crate://', connect_args={...,'servers': ['crate1:4200', 'crate2:4200',...]})
  engine = create_engine('crate://[Your Crate Host]:4200')
  SessionMaker = sessionmaker(bind=engine)
  session = SessionMaker()

  v = Video(name = 'ゼロ・グラビティ')
  v.tags = [Video.CAT_ACTION]
  v.price = {}
  v.price[Video.PRICE_TYPE_BUY] = 1980
  v.price[Video.PRICE_TYPE_RENTAL] = 400
  session.add(v)

  v = Video(name = 'アナと雪の女王')
  v.tags = [Video.CAT_FANTASY, Video.CAT_ANIME, Video.CAT_ADVENTURE]
  v.price = {}
  v.price[Video.PRICE_TYPE_BUY] = 3980
  v.price[Video.PRICE_TYPE_RENTAL] = 500
  session.add(v)

  v = Video(name = 'ウォーゲーム')
  v.tags = [Video.CAT_CLASSIC, Video.CAT_ACTION, Video.CAT_ADVENTURE]
  v.price = {}
  v.price[Video.PRICE_TYPE_BUY] = 500 
  v.price[Video.PRICE_TYPE_RENTAL] = 100
  session.add(v)

  v = Video(name = '13日の金曜日')
  v.tags = [Video.CAT_HORROR, Video.CAT_VIOLENCE]
  v.price = {}
  v.price[Video.PRICE_TYPE_BUY] = 500 
  v.price[Video.PRICE_TYPE_RENTAL] = 100
  v.price[Video.PRICE_TYPE_COUPON] = 0
  session.add(v)

  session.commit() # only flush (crate not support transaction)
  
  # After INSERT statements are sent to the database the newly inserted
  # rows aren’t immediately available for search because the index is only
  # updated periodically.
  # Note: Newly inserted rows can still be queried immediately if a lookup by the primary key is done.
  sleep(2)

  v = session.query(Video).first()
  print v.name

  session.close()


このコードでは主キー(id)値をアプリケーション側で生成していますが、今後は Ruby/ActiveRecord ドライバもリリース予定(現在は Python, Ruby, Java ドライバのみ)だそうで crate 側で自動生成・ジェネレーター対応してくれそうな予感(ドキュメントを読んでいると currently ... が多い)。

このアプリケーションを実行後、crash を使って videos テーブルを select してみると・・・

cr> select name,tags,price from videos;
+----------+------------------------------------+------------------------------------------+
| name     | tags                               | price                                    |
+----------+------------------------------------+------------------------------------------+
| ゼロ・グラビティ | ["Action"]                         | {"buy": 1980, "rental": 400}             |
| アナと雪の女王  | ["Fantasy", "Anime", "Adventure"]  | {"buy": 3980, "rental": 500}             |
| 13日の金曜日  | ["Horror", "Violence"]             | {"buy": 500, "coupon": 0, "rental": 100} |
| ウォーゲーム   | ["Classic", "Action", "Adventure"] | {"buy": 500, "rental": 100}              |
+----------+------------------------------------+------------------------------------------+
SELECT 4 rows in set (0.009 sec)

お、登録されていますね。 事前に定義されていなかった price > coupon も動的に追加されている。

配列を検索する場合はこうします。

cr> select name,price from videos where 'Action' = ANY(tags);
+----------+------------------------------+
| name     | price                        |
+----------+------------------------------+
| ゼロ・グラビティ | {"buy": 1980, "rental": 400} |
| ウォーゲーム   | {"buy": 500, "rental": 100}  |
+----------+------------------------------+
SELECT 2 rows in set (0.007 sec)

object 型の入れ子はこう。

cr> select name, price['coupon'] as coupon_price from videos where price['coupon'] is not null;
+---------+--------------+
| name    | coupon_price |
+---------+--------------+
| 13日の金曜日 |            0 |
+---------+--------------+
SELECT 1 row in set (0.010 sec)

timestamp の値は java.util.Formatter 書式で整形出来ます。

cr> select name,format('%1$tY-%1$tm-%1$td', released_at) as release from videos;
+----------+------------+
| name     | release    |
+----------+------------+
| ゼロ・グラビティ | 2014-06-11 |
| アナと雪の女王  | 2014-06-11 |
| 13日の金曜日  | 2014-06-11 |
| ウォーゲーム   | 2014-06-11 |
+----------+------------+
SELECT 4 rows in set (0.030 sec)

Crate SQL のより詳しい説明はこちら(https://crate.io/docs/stable/sql/index.html)をどうぞ。

CRATE 今後に注目ですよね!

MongoDBイン・アクション
Kyle Banker
オライリージャパン
売り上げランキング: 41,599

NOSQLの基礎知識 (ビッグデータを活かすデータベース技術)
本橋信也 河野達也 鶴見利章
リックテレコム
売り上げランキング: 36,655

Posted by netbuffalo at 22:27│Comments(0)TrackBack(0)プログラミング | Java


この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/netbuffalo/4799333

コメントする

名前
URL
 
  絵文字