Droongaチュートリアル: 使ってみる/Groongaからの移行手順

チュートリアルのゴール

Droongaクラスタを自分で構築して、Groonga互換のサーバとして利用できるようにするための手順を学ぶこと。

前提条件

Droongaとは何か?

Droongaは分散アーキテクチャに基づくデータ処理エンジンで、「distributed-Groonga」がその名の由来です。 名前が示す通り、Droongaはいくつかの点での改善(具体的には、レプリケーションとシャーディング)を含んだGroonga互換のサーバとして動作することができます。

アーキテクチャ、設計、APIなどの点で、DroongaはGroongaと大きく異なっています。 しかしながら、Droongaを単にGroonga互換のサーバとして使う限りにおいては、Droongaのアーキテクチャ全体を理解する必要はありません。

例として、ニューヨークにあるスターバックスの店舗を検索できるデータベースシステムを作成することにします。

Droongaクラスタをセットアップする

実験用の環境を準備する

まず最初にコンピュータを用意します。 このチュートリアルは、既存のコンピュータを使ってDroongaクラスタを構築する手順について解説しています。 以下の説明は基本的には、DigitalOcean上のサーバでUbuntu 13.10 x64の仮想マシンが正しく準備されており、コンソールが利用できる状態になっている、という前提に基づいています。

注意:Droongaの依存パッケージをインストールする前に、仮想マシンのインスタンスが少なくとも2GB以上のメモリを備えていることを確認して下さい。 メモリが足りないと、ビルド時におかしなエラーに遭遇することになります。

また、有効なレプリケーションを実現するためには2台以上のコンピュータを用意する必要もあります。

Droongaの構成コンポーネントをインストールする

Groongaはバイナリのパッケージを提供しているため、環境によっては簡単にインストールできます。 (Groongaのインストール手順を参照)

しかしながら、現在の所Droongaに基づくデータベースシステムをセットアップするための簡単な方法はありません。 将来的にはより良い方法(例えばChefのクックブックなど)を用意する計画がありますが、今のところは、セットアップは手動で行う必要があります。

Droongaベースのデータベースシステムは、Droongaクラスタと呼ばれます。 Droongaクラスタは、Droongaノードと呼ばれる複数のコンピュータによって構成されます。 よって、Droongaクラスタを構築するには複数のDroongaノードをセットアップする必要があります。

192.168.0.10192.168.0.11の2つのコンピュータがあると仮定しましょう。

  1. それぞれのコンピュータで、プラットフォームごとに要求されるパッケージをインストールする。

    # apt-get update
    # apt-get -y upgrade
    # apt-get install -y ruby ruby-dev build-essential nodejs nodejs-legacy npm
    
  2. それぞれのコンピュータで、Gemパッケージ droonga-engine をインストールする。 これはDroongaシステムの主要な機能を提供する、核となるコンポーネントです。

    # gem install droonga-engine
    
  3. それぞれのコンピュータで、npmパッケージ droonga-http-server をインストールする。 これはHTTPのリクエストをDroongaネイティブのリクエストに変換するために必要な、フロントエンドとなるコンポーネントです。

    # npm install -g droonga-http-server
    
  4. それぞれのコンピュータで、Droongaノードとしての情報を保存するための設定ディレクトリを用意する。 すべてのデータベースの実体は、このディレクトリ以下に保存されます。

    # mkdir ~/droonga
    # cd ~/droonga
    
  5. いずれか1つのDroongaノードでcatalog.jsonを作成します。 このファイルはDroongaクラスタの構成を定義する物です。 データセット名を--datasetオプション、各DroongaノードのIPアドレスを--hostsオプションで、以下のように指定して下さい:

    # droonga-engine-catalog-generate --hosts=192.168.0.10,192.168.0.11 \
                                      --output=./catalog.json
    

    コンピュータが1台だけの単なる検証用の構成をセットアップする場合は、以下のようにします:

    # droonga-engine-catalog-generate --hosts=127.0.0.1 \
                                      --output=./catalog.json
    
  6. すべてのDroongaノードに、先程作成したcatalog.jsonを共有します。

    # scp ~/droonga/catalog.json 192.168.0.11:~/droonga/
    

    (もしくは、できあがったファイルをコピーする代わりに、各コンピュータ上で同じ設定のcatalog.jsonを作成しても結構です。)

上記の手順により、DroongaクラスタのためのすべてのDroongaノードの準備が完了しました。 次の段階に進みましょう。

DroongaクラスタをHTTP経由で使用する

各Droongaノードの上でのサービスの開始と停止

GroongaをHTTPサーバとして使う場合は、以下のように -d オプションを指定するだけでサーバを起動できます:

# groonga -p 10041 -d --protocol http /tmp/databases/db

一方、DroongaクラスタをHTTP経由で使うためには、各Droongaノードにおいて複数のサービスを起動する必要があります。

サービスを起動するには、各Droongaノードで以下のようにコマンドを実行します:

# cd ~/droonga
# host=192.168.0.10
# DROONGA_BASE_DIR=$PWD
# droonga-engine --host=$host \
                 --log-file=$DROONGA_BASE_DIR/droonga-engine.log \
                 --daemon \
                 --pid-file=$DROONGA_BASE_DIR/droonga-engine.pid
# env NODE_ENV=production \
    droonga-http-server --port=10041 \
                        --receive-host-name=$host \
                        --droonga-engine-host-name=$host \
                        --cache-size=-1 \
                        --daemon \
                        --pid-file=$DROONGA_BASE_DIR/droonga-http-server.pid

いくつかのオプションにおいて、そのDroongaノード自身のホスト名を指定する必要がある事に注意して下さい。 この情報は、クラスタ内の他のDroongaノードとの通信のために使われます。 よって、別のDroongaノード上では以下のように別のホスト名を指定する事になります:

# cd ~/droonga
# host=192.168.0.11
# droonga-engine --host=$host \
...

このコマンドにより、2つのノードはクラスタを形成し、互いの生死を監視するようになります。もしクラスタ内のどれか1つのノードが機能を停止し、他のノードがまだ機能し続けていた場合には、残ったノードがDroongaクラスタとして動作し続けます。そのため、そのような事態が起こっても秘密裏に、機能停止したノードを復旧したりクラスタに復帰させたりすることができます。

クラスタが動作している事を、system.status コマンドを使って確認してみましょう。 コマンドはHTTP経由で実行できます:

# curl "http://192.168.0.10:10041/droonga/system/status"
{
  "nodes": {
    "192.168.0.10:10031/droonga": {
      "live": true
    },
    "192.168.0.11:10031/droonga": {
      "live": true
    }
  }
}

この結果は、2つのノードが正常に動作している事を示しています。 Droongaはクラスタで動作するので、他のエンドポイントも同じ結果を返します。

# curl "http://192.168.0.11:10041/droonga/system/status"
{
  "nodes": {
    "192.168.0.10:10031/droonga": {
      "live": true
    },
    "192.168.0.11:10031/droonga": {
      "live": true
    }
  }
}

サービスを停止するには、以下のコマンドを各Droongaノード上で実行します:

# kill $(cat $DROONGA_BASE_DIR/droonga-engine.pid)
# kill $(cat $DROONGA_BASE_DIR/droonga-http-server.pid)

テーブル、カラム、インデックスの作成

以上の手順で、Groonga HTTPサーバ互換のサービスとして動作するDroongaクラスタができました。

リクエストの送信方法はGroongaサーバの場合と全く同じです。 新しいテーブル Store を作るには、table_create コマンドにあたるGETリクエストを送信して下さい:

# endpoint="http://192.168.0.10:10041"
# curl "${endpoint}/d/table_create?name=Store&flags=TABLE_PAT_KEY&key_type=ShortText"
[[0,1401358896.360356,0.0035653114318847656],true]

リクエストの送信先として、Droongaノード中でdroonga-http-serverが動作しているDroongaノードのどれか1つを指定する必要がある事に注意して下さい。 言い換えると、接続先(エンドポイント)としてはクラスタ中のどのノードでも好きな物を使う事ができます。 すべてのリクエストは、クラスタ中の適切なノードに配送されます。

次は、column_create コマンドを使って Store テーブルに namelocation という新しいカラムを作ります:

# curl "${endpoint}/d/column_create?table=Store&name=name&flags=COLUMN_SCALAR&type=ShortText"
[[0,1401358348.6541538,0.0004096031188964844],true]
# curl "${endpoint}/d/column_create?table=Store&name=location&flags=COLUMN_SCALAR&type=WGS84GeoPoint"
[[0,1401358359.084659,0.002511262893676758],true],true]

インデックスも作成しましょう。

# curl "${endpoint}/d/table_create?name=Term&flags=TABLE_PAT_KEY&key_type=ShortText&default_tokenizer=TokenBigram&normalizer=NormalizerAuto"
[[0,1401358475.7229664,0.002419710159301758],true]
# curl "${endpoint}/d/column_create?table=Term&name=store_name&flags=COLUMN_INDEX|WITH_POSITION&type=Store&source=name"
[[0,1401358494.1656318,0.006799221038818359],true]
# curl "${endpoint}/d/table_create?name=Location&flags=TABLE_PAT_KEY&key_type=WGS84GeoPoint"
[[0,1401358505.708896,0.0016951560974121094],true]
# curl "${endpoint}/d/column_create?table=Location&name=store&flags=COLUMN_INDEX&type=Store&source=location"
[[0,1401358519.6187897,0.024788379669189453],true]

注意: テーブルが完全にできあがるまでは、table_listcolumn_list といったコマンドを実行しないでください。テーブルができあがる前にこれらのコマンドを実行してしまうと、インデックスが破損した状態になってしまいます。これはバージョン1.1.1での既知の不具合で、将来のバージョンで修正される予定です。

さて、テーブルを正しく作成できました。 table_list コマンドを使って、作成されたテーブルの情報を見てみましょう:

# curl "${endpoint}/d/table_list"
[[0,1401358908.9126804,0.001600027084350586],[[["id","UInt32"],["name","ShortText"],["path","ShortText"],["flags","ShortText"],["domain","ShortText"],["range","ShortText"],["default_tokenizer","ShortText"],["normalizer","ShortText"]],[256,"Store","/home/vagrant/droonga/000/db.0000100","TABLE_PAT_KEY|PERSISTENT","ShortText",null,null,null]]]

Droongaはクラスタで動作するので、他のエンドポイントも同じ結果を返します。

# curl "http://192.168.0.11:10041/d/table_list"
[[0,1401358908.9126804,0.001600027084350586],[[["id","UInt32"],["name","ShortText"],["path","ShortText"],["flags","ShortText"],["domain","ShortText"],["range","ShortText"],["default_tokenizer","ShortText"],["normalizer","ShortText"]],[256,"Store","/home/vagrant/droonga/000/db.0000100","TABLE_PAT_KEY|PERSISTENT","ShortText",null,null,null]]]

テーブルへのデータの読み込み

それでは、Store テーブルにデータを読み込みましょう。 まず、データを stores.json という名前のJSON形式のファイルとして用意します。

stores.json:

[
["_key","name","location"],
["store0","1st Avenue & 75th St. - New York NY  (W)","40.770262,-73.954798"],
["store1","76th & Second - New York NY  (W)","40.771056,-73.956757"],
["store2","2nd Ave. & 9th Street - New York NY","40.729445,-73.987471"],
["store3","15th & Third - New York NY  (W)","40.733946,-73.9867"],
["store4","41st and Broadway - New York NY  (W)","40.755111,-73.986225"],
["store5","84th & Third Ave - New York NY  (W)","40.777485,-73.954979"],
["store6","150 E. 42nd Street - New York NY  (W)","40.750784,-73.975582"],
["store7","West 43rd and Broadway - New York NY  (W)","40.756197,-73.985624"],
["store8","Macy's 35th Street Balcony - New York NY","40.750703,-73.989787"],
["store9","Macy's 6th Floor - Herald Square - New York NY  (W)","40.750703,-73.989787"],
["store10","Herald Square- Macy's - New York NY","40.750703,-73.989787"],
["store11","Macy's 5th Floor - Herald Square - New York NY  (W)","40.750703,-73.989787"],
["store12","80th & York - New York NY  (W)","40.772204,-73.949862"],
["store13","Columbus @ 67th - New York NY  (W)","40.774009,-73.981472"],
["store14","45th & Broadway - New York NY  (W)","40.75766,-73.985719"],
["store15","Marriott Marquis - Lobby - New York NY","40.759123,-73.984927"],
["store16","Second @ 81st - New York NY  (W)","40.77466,-73.954447"],
["store17","52nd & Seventh - New York NY  (W)","40.761829,-73.981141"],
["store18","1585 Broadway (47th) - New York NY  (W)","40.759806,-73.985066"],
["store19","85th & First - New York NY  (W)","40.776101,-73.949971"],
["store20","92nd & 3rd - New York NY  (W)","40.782606,-73.951235"],
["store21","165 Broadway - 1 Liberty - New York NY  (W)","40.709727,-74.011395"],
["store22","1656 Broadway - New York NY  (W)","40.762434,-73.983364"],
["store23","54th & Broadway - New York NY  (W)","40.764275,-73.982361"],
["store24","Limited Brands-NYC - New York NY","40.765219,-73.982025"],
["store25","19th & 8th - New York NY  (W)","40.743218,-74.000605"],
["store26","60th & Broadway-II - New York NY  (W)","40.769196,-73.982576"],
["store27","63rd & Broadway - New York NY  (W)","40.771376,-73.982709"],
["store28","195 Broadway - New York NY  (W)","40.710703,-74.009485"],
["store29","2 Broadway - New York NY  (W)","40.704538,-74.01324"],
["store30","2 Columbus Ave. - New York NY  (W)","40.769262,-73.984764"],
["store31","NY Plaza - New York NY  (W)","40.702802,-74.012784"],
["store32","36th and Madison - New York NY  (W)","40.748917,-73.982683"],
["store33","125th St. btwn Adam Clayton & FDB - New York NY","40.808952,-73.948229"],
["store34","70th & Broadway - New York NY  (W)","40.777463,-73.982237"],
["store35","2138 Broadway - New York NY  (W)","40.781078,-73.981167"],
["store36","118th & Frederick Douglas Blvd. - New York NY  (W)","40.806176,-73.954109"],
["store37","42nd & Second - New York NY  (W)","40.750069,-73.973393"],
["store38","Broadway @ 81st - New York NY  (W)","40.784972,-73.978987"],
["store39","Fashion Inst of Technology - New York NY","40.746948,-73.994557"]
]

データが準備できたら、load コマンドのPOSTリクエストとして送信します:

# curl --data "@stores.json" "${endpoint}/d/load?table=Store"
[[0,1401358564.909,0.158],[40]]

これで、JSONファイル中のすべてのデータが正しく読み込まれます。

テーブル中のデータを取り出す

以上で、すべてのデータが準備できました。

試しに、select コマンドを使って最初の10レコードを取り出してみましょう:

# curl "${endpoint}/d/select?table=Store&output_columns=name&limit=10"
[[0,1401362059.7437818,0.00004935264587402344],[[[40],[["name","ShortText"]],["1st Avenue & 75th St. - New York NY  (W)"],["76th & Second - New York NY  (W)"],["Herald Square- Macy's - New York NY"],["Macy's 5th Floor - Herald Square - New York NY  (W)"],["80th & York - New York NY  (W)"],["Columbus @ 67th - New York NY  (W)"],["45th & Broadway - New York NY  (W)"],["Marriott Marquis - Lobby - New York NY"],["Second @ 81st - New York NY  (W)"],["52nd & Seventh - New York NY  (W)"]]]]

もちろん、query オプションを使って検索条件を指定する事もできます:

# curl "${endpoint}/d/select?table=Store&query=Columbus&match_columns=name&output_columns=name&limit=10"
[[0,1398670157.661574,0.0012705326080322266],[[[2],[["_key","ShortText"]],["Columbus @ 67th - New York NY  (W)"],["2 Columbus Ave. - New York NY  (W)"]]]]
# curl "${endpoint}/d/select?table=Store&filter=name@'Ave'&output_columns=name&limit=10"
[[0,1398670586.193325,0.0003848075866699219],[[[3],[["_key","ShortText"]],["2nd Ave. & 9th Street - New York NY"],["84th & Third Ave - New York NY  (W)"],["2 Columbus Ave. - New York NY  (W)"]]]]

まとめ

このチュートリアルでは、Ubuntu Linuxのコンピュータを使ってDroongaクラスタを構築しました。 また、Groongaサーバ互換のシステムとしてデータを読み込ませたり取り出したりすることにも成功しました。

現在の所、DroongaはGroonga互換のコマンドのうちいくつかの限定的な機能にのみ対応しています。 詳細はコマンドリファレンスを参照して下さい。

続いて、Droongaクラスタのデータをバックアップしたり復元したりする手順を学びましょう。