Groongaユーザ向けの、はじめてのDroonga - 2014-07-11 - ククログ

ククログ

株式会社クリアコード > ククログ > Groongaユーザ向けの、はじめてのDroonga

Groongaユーザ向けの、はじめてのDroonga

昨年11月29日に開催したイベント「全文検索エンジンGroongaを囲む夕べ 4」 において、Groonga族の新たな一員としてDroongaが加わった事をお知らせしました。その後現在に至るまでにリリースを着実に重ね、現在はバージョン1.0.4となっているのですが、その間に方針や開発の優先度が変わってきている事についてのきちんとしたアナウンスができておらず、今改めてDroongaの事を知ろうとした時に「で、結局これって何なのよ?」ということが分かりにくくなってしまっています。

この記事は、そんなDroongaの現時点での情報を一旦整理して、特にGroongaを実運用されている方にとって「Droongaって何なの? どう便利になるの?」ということが一目で分かるようにする物です。Droongaの現状まとめとしてご参照下さい。

GroongaとDroongaの違い

Droongaは端的に言うと、「Distributedな(分散型の)Groonga」ということになります。詳しくご説明しましょう。

Groongaは全文検索エンジンであり、カラム指向のデータベースでもあります。アプリケーション組み込みのライブラリやHTTPサーバとして動作して、ユーザのリクエストに応じてデータの読み書きと検索を処理することができます。

簡単に利用し始められるのが魅力のGroongaですが、大規模なサービスのバックエンドとしてGroongaを安定運用するには若干の工夫が必要です。具体的には、Groonga自体にはレプリケーションなどの機能は含まれないため、耐障害性を高めるためやアクセス数の増加に対応するためなどの目的でデータベースを冗長化するには、複数のGroongaサーバを何らかの方法で協調動作させる必要があります。

Droongaは現在の所、Groongaの上記の問題点が解決された移行先となることを目指して開発が進められています。そのため、現時点で既に以下のような特徴を備えています。

Groongaとの互換性

Droongaは、GroongaのHTTPインターフェースと互換性があります。そのため、フロントエンドとなるWebアプリケーションの改修の必要はありません。

ただし、現時点では以下の制限事項があります。

  • 主要なコマンドのみに対応しており、未対応のコマンドがあります。(コマンドリファレンスに、現時点で対応しているGroonga互換コマンドの一覧があります)
  • 特に、サジェスト関係の機能には全く対応していません。
  • Groonga固有の通信プロトコルであるgqtpには対応していません。

これらの未対応項目については、将来的には互換性を改善していく予定です。とはいえ、現在Groongaを運用している中で使っている機能がDroongaが対応している機能の範囲内に収まっているのであれば、すぐにでもそのままDroongaへ移行することができます。GroongaからDroongaへの移行を検討する際には、まずこの点をチェックしてみて下さい。

レプリケーションへの対応

Droongaは複数ノードによるクラスタとして動作しますので、もちろん、基本機能としてレプリケーションにも対応しています。レプリケーション数を増やすことで、耐障害性の高い運用体制をとれます。

また、ノードの追加・削除を簡単に行えるため、急なアクセス数の増減にも対応しやすいという利点もあります。アクセス数の増大に応じてノードを追加することにより、安定したスループットを維持できます。

目的が変わった?

ここまでの説明を見て、「発表当初のDroongaと目的が変わっているのではないか?」と思われた方もいるかもしれません。この点についても説明しておきましょう。

発表当初、Droongaは「内部的にGroongaを使用した、汎用の分散データ処理システム」という位置付けで、Groongaとの互換性についてはそれほど重視はしていませんでした。もちろん、汎用システムの一つの応用形態としてGroonga互換の検索システムとしても利用できるようになるということは想定していましたが、そのための作業の優先度はそれほど高くはありませんでした。それよりも、汎用システムとしての基盤部分を固めていくことを優先しようというのが、当初の目標設定でした。

しかし、実際に開発を進めていくうちに、ノード構成の管理やレプリケーションなどの基盤部分の開発を進めていく上では、具体的な利用シーンを設定した方がそれらの開発を進めやすい(たくさんあるやるべき事の中から「まずここから実装していった方がよい」という判断を行いやすい)という事に気がついてきました。また、実運用に即した機能が早めに出揃うことで、実運用を開始してみないと気がつかない種類の不具合を見つけやすくなるというメリットもあります。

そこで、Droongaプロジェクトでは当面のところ、「Groonga互換の検索システム」を構築するという場面を想定して、開発やドキュメントの整備を進めることにしました。「汎用の分散型データ処理システム」としての開発自体を諦めたというわけでは決してありませんので、その点についてはご安心下さい。

Droongaを試してみよう

以上、Droongaの現状について簡単に紹介しました。ここからは、実際にDroongaを利用するための手順を解説します。DroongaがGroonga互換の検索システムとして動作することを、まずはお手元の環境で実際に試してみて下さい。

ここでの手順は、大まかに言って以下の通りです。

  1. Groongaをバックエンドに使った簡単なWebアプリケーションを用意する。

  2. Droongaクラスタを構築する。

  3. Groongaのデータベースの内容をDroongaクラスタに引き継ぐ

  4. Webアプリケーションのバックエンドを、GroongaからDroongaクラスタに置き換える。

Droongaクラスタは、レプリケーション機能を利用するためにサーバを2台使うことにします。以下の説明では、サーバとしてUbuntu 14.04LTSがインストールされたサーバが2台あり、それぞれのIPアドレスが192.168.0.10と192.168.0.11であると仮定します。IPアドレスなどは、お手元の環境に合わせて説明を適宜読み替えて下さい。

1. Groongaを使ったWebアプリケーションの用意

まず、Groongaをバックエンドに使って、TODOリストの作成と検索を行う簡単なWebアプリケーションを作成してみましょう。Groongaは複数台のサーバでの動作に対応していませんので、ここでは192.168.0.10の1台だけを使うことにします。

最初に、インストール手順の説明に従ってGroongaをインストールします。ここではPPAを使ってインストールする例を示します。

% sudo apt-get -y install software-properties-common
% sudo add-apt-repository -y universe
% sudo add-apt-repository -y ppa:groonga/ppa
% sudo apt-get update
% sudo apt-get -y install groonga

次に、データベースを作成します。 ~/groonga/db/ 以下にデータベースを置くことにします。

% mkdir -p $HOME/groonga/db/
% groonga -n $HOME/groonga/db/db quit

各テーブルも定義します。

% groonga $HOME/groonga/db/db table_create --name Topic --flags TABLE_PAT_KEY --key_type ShortText
% groonga $HOME/groonga/db/db column_create --table Topic --name title --flags COLUMN_SCALAR --type ShortText
% groonga $HOME/groonga/db/db table_create --name Term --flags TABLE_PAT_KEY --key_type ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto
% groonga $HOME/groonga/db/db column_create --table Term --name topic_title --flags "COLUMN_INDEX|WITH_POSITION" --type Topic --source title

データベースができたら、GroongaをHTTPサーバとして起動します。

% groonga -p 10041 -d --protocol http $HOME/groonga/db/db

これでバックエンドの準備ができました。続いて、フロントエンドとなるWebアプリケーションを作成します。説明を簡単にするため、アプリケーションの機能はWebページ中に埋め込んだJavaScriptだけで実装することにします。

まずWebページを作成します。

% mkdir ~/groonga/public/
% vi ~/groonga/public/index.html

Webページの内容は以下の通りです。

<!DOCTYPE html>
<meta charset="UTF-8">
<title>TODO List</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p><input type="text" size="30" id="title-field"><button id="add-button">登録</button></p>
<p><input type="text" size="20" id="search-field">
   <button id="search-button">検索</button></p>
<div id="result"></div>
<script type="text/javascript"><!--
  var base = 'http://' + location.hostname + ':10041';

  // レコードを追加する。
  $('#add-button').click(function() {
    var title = $('#title-field').val();
    if (!title)
      return;
    $.ajax({
      url:      base + '/d/load',
      data:     {
        table:  'Topic',
        values: JSON.stringify([{
          _key:  title,
          title: title
        }])
      },
      dataType: 'jsonp',
      success:  function() {
        $('#title-field').val('');
      }
    });
  });

  // レコードを検索する。
  $('#search-button').click(function() {
    var data = {
      table:          'Topic',
      output_columns: 'title',
      limit:          10
    };
    var term = $('#search-field').val();
    if (term)
      data.filter = 'title @ ' + JSON.stringify(term);
    $.ajax({
      url:      base + '/d/select',
      data:     data,
      dataType: 'jsonp',
      success:  function(result) {
        if (!result)
          return;
        var body = result[1];
        var searchResult = body[0];
        var count = searchResult[0][0];
        var topics = searchResult.slice(2);
        $('#result')
          .empty()
          .append($('<p>').text(count + '件見つかりました。'))
          .append($('<ul>')
                    .append(topics.map(function(topic) {
                      var title = topic[0];
                      return $('<li>').text(title);
                    })));
      }
    });
  });
// --></script>

Webページの準備ができたら、Webサーバを起動します。設定ファイルなどを用意しなくてもよく簡単に起動できるため、Rubyの標準機能を使ったWebサーバを使います。

% ruby -run -e httpd -- --port 8080 groonga/public &

Webブラウザを起動し、早速Webアプリケーションにアクセスしてみましょう。URLは 「http://192.168.0.10:8080/index.html」です。

(サンプルWebアプリケーションを開いた所のスクリーンショット)

試しにTODOタスクを追加してみましょう。「追加」ボタンの左の入力欄に「バナナを買う」と入力して「追加」ボタンをクリックします。すると、レコードが追加されて入力欄が空になります。続けて「リンゴを買う」「牛乳を買う」も追加しましょう。

(入力中の様子のスクリーンショット)

タスクの追加が終わったら、検索してみましょう。まずは、何も入力せずに「検索」ボタンをクリックします。すると、登録されているすべてのレコードのうち先頭10件が表示されます。(現在は3件しかレコードがないので、3件すべてのレコードが表示されます。)

(すべてのレコードが表示されたスクリーンショット)

続いて、全文検索もしてみましょう。「検索」ボタンの左の入力欄に「牛乳」と入力してから「検索」ボタンをクリックします。すると、登録されているレコードのうち、内容に「牛乳」を含んでいるレコードの先頭10件が表示されます。(現在は1件しか該当するレコードがないので、レコードが1件だけ表示されます。)

(検索結果のスクリーンショット)

ということで、無事にTODOリストの管理アプリケーションが実装されました。

2. Droongaクラスタの構築

それでは、先のGroongaと互換性があるDroongaクラスタを構築してみましょう。

192.168.0.10と192.168.0.11の両方に、Droongaを構成するパッケージをインストールします。詳細はチュートリアルを参照してください。

(on 192.168.0.10, 192.168.0.11)
% sudo apt-get update
% sudo apt-get -y upgrade
% sudo apt-get install -y libgroonga-dev ruby ruby-dev build-essential nodejs nodejs-legacy npm
% sudo gem install droonga-engine
% sudo npm install -g droonga-http-server
% mkdir ~/droonga

パッケージのインストールが完了したら、catalog.jsonを作成します。これは、Droongaクラスタのノード構成の設定が書かれたファイルです。droonga-engineパッケージに含まれているdroonga-engine-catalog-generateコマンドを使って簡単に作成することができます。 --hostsオプションには、クラスタを構成するすべてのノードのIPアドレス(またはホスト名)をカンマ区切りで指定します。この操作も、2台のサーバ両方で行ってください。

(on 192.168.0.10, 192.168.0.11)
% droonga-engine-catalog-generate --hosts=192.168.0.10,192.168.0.11 --output=~/droonga/catalog.json

catalog.jsonができたら、それぞれのサーバ上でDroongaのサービスを起動します。Droongaはdroonga-engineとdroonga-http-serverという2つのサービスに別れており、それぞれ個別に起動する必要があります。以下に、192.168.0.10で実行するコマンドを示します。

(on 192.168.0.10)
% host=192.168.0.10
% export DROONGA_BASE_DIR=$HOME/droonga
% 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=10042 \
                    --receive-host-name=$host \
                    --droonga-engine-host-name=$host \
                    --daemon \
                    --pid-file=$DROONGA_BASE_DIR/droonga-http-server.pid

起動オプションでそのノード自身のIPアドレスを指定していることにも注意して下さい。192.168.0.11では、起動オプションに含めるホストのIPアドレスを変える必要があります。

(on 192.168.0.11)
% host=192.168.0.11
% export DROONGA_BASE_DIR=$HOME/droonga
% 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=10042 \
                    --receive-host-name=$host \
                    --droonga-engine-host-name=$host \
                    --daemon \
                    --pid-file=$DROONGA_BASE_DIR/droonga-http-server.pid

これで、Droongaクラスタが動作し始めました。DroongaのHTTP APIにアクセスしてみて以下のような結果を得られれば、Droongaクラスタは正常に動作しています。

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

3. データの引き継ぎ

Droongaクラスタを構築できたので、次は、Groongaデータベース内に定義済みのテーブルや投入済みのデータをDroongaクラスタに引き継ぎます。

まず、データの引き継ぎに必要なツールをインストールします。Groongaのダンプ形式をDroongaのリクエスト形式に変換するコマンドgrn2drnを含む同名パッケージと、DroongaのリクエストをDroongaクラスタに送信するコマンドであるdroonga-requestを含んでいるdroonga-clientパッケージの、2つのGemパッケージをインストールしましょう。

% sudo gem install grn2drn droonga-client

ツールがインストールされたら、Groongaのデータベースの内容をダンプ出力し、Droongaクラスタへ流し込みます。

% grndump ~/groonga/db/db | grn2drn | droonga-request --host=192.168.0.10

以上で、データの引き継ぎは完了です。ダンプ出力を通じて、テーブルの定義も含めてGroongaのデータベース内のすべての情報がDroongaクラスタに引き継がれました。

4. WebアプリケーションのバックエンドをGroongaからDroongaに切り替える

それではいよいよ、WebアプリケーションのバックエンドをDroongaに切り替えてみましょう。~/groonga/public/index.html でエンドポイントとして参照しているGroongaのHTTPサーバの接続先を、以下のようにしてDroongaの物に書き換えて下さい。

-  var base = 'http://' + location.hostname + ':10041';
+  var base = 'http://' + location.hostname + ':10042';

ファイルを編集したら、Webアプリケーションのページ( http://192.168.0.10:8080/index.html )をブラウザ上で再読み込みします。

これで、WebアプリケーションのバックエンドがGroongaからDroongaに切り替わりました。

それでは動作を試してみましょう。まずはデータが正常に引き継がれたことを確認するために、何も入力せずに「検索」ボタンをクリックします。すると、Groongaのデータベースから引き継いだレコード3つが検索結果として表示されます。

(すべてのレコードが表示されたスクリーンショット)

次はレコードの追加です。「追加」ボタンの左の入力欄に「ぶどうを買う」「パイナップルを買う」「コーヒー牛乳を買う」とそれぞれ入力し、レコードを追加して下さい。

(入力中の様子のスクリーンショット)

タスクの追加が終わったら、検索してみましょう。何も入力せずに「検索」ボタンをクリックすると、登録されているすべてのレコードのうち先頭10件が表示されます。ここでは、Groongaのデータベースから引き継いだレコード3つと、今追加したレコード3つを合わせて、計6つのレコードが表示されます。

(すべてのレコードが表示されたスクリーンショット)

続いて、全文検索もしてみましょう。「検索」ボタンの左の入力欄に「牛乳」と入力してから「検索」ボタンをクリックすると、登録されているレコードのうち、内容に「牛乳」を含んでいるレコードの先頭10件が表示されます。ここでは、Groongaのデータベースから引き継いだレコード1つと、今追加したレコードのうちの1つの、計2つのレコードが検索結果として表示されます。

(検索結果のスクリーンショット)

ということで、以上の一連の操作を通じて、DroongaのHTTPインターフェースはGroongaと互換性があるという事と、GroongaからDroongaへはデータを容易に引き継げるという事を確認できました。

なお、この状態ですでに2台構成のレプリケーションが実現されているため、仮にDroongaノードの片方が停止してもWebアプリケーションは正常に動作し続けますし、アクセス数が増大した場合でも2台のノードで処理を分担することができます。

まとめとイベントの告知

以上、Droongaの概要の紹介と、Droongaクラスタを構築してGroongaサーバをDroongaクラスタに移行する手順を簡単に解説しました。Droongaを少しでも身近に感じていただけたら、また、Groongaからの移行先としてDroongaを検討していただけたら幸いです。

最後に、イベントの告知もしておきます。

この記事で述べたような情報に加えて、Droongaの現状や今後の展望についてより詳細な紹介を行うイベント「Droonga Meetup」を7月30日水曜日 夜8時から開催します(参加無料)。 質疑応答の時間を長めに取る予定ですので、Groongaユーザの方々の生の声をぜひお聞かせ下さい。 イベントの詳細情報やお申し込みは、Doorkeeperのイベント案内ページをご覧下さい。