隔週連載groonga

第10回[実録] MySQL向け全文検索エンジン「Tritonn」から「mroonga」への移行ガイド(3)

こんにちは、株式会社リブセンスの吉田健太郎です。第8回に続いて、私が体験したTritonnからmroongaのシステム移行プロジェクトを舞台裏からお届けします。

これまでのあらすじ

将来的な技術負債を残さない、そしてInnoDBの性能向上等の恩恵を受けるため、もはやレガシーとなったMySQL 5.0を捨てて、MySQL 5.6への移行を行いたい。しかしSolrへ乗り換えるほどでもなく、引き続きシンプルにSQLを用いた、リレーショナルな日本語対応の全文検索を使いたい。この構想を実現するため、groongaのMySQLバインディング版である「mroonga」を用いた、MySQL 5.6への移行プロジェクトが始動しました。

これまでの記事と、主なトピックは次の通りです。

今回の流れ

前回までのオペレーションでは、稼働中であるMySQL 5.0のレプリケーションスレーブとしてMySQL 5.6を接続する所までを完了させました。いよいよ大詰めとなる今回、次の流れで移行プロジェクトの最終章を紹介します。

  1. mroongaのインストール方法
  2. Tritonnからmroongaへのテーブルスキーマ書き換えガイド
  3. Tritonnからmroongaへの全文検索クエリ書き換えガイド
  4. mroongaのプロダクション環境への投入
  5. Tritonnからmroongaへ移行する際の要注意ポイント
  6. mroongaのトラブルシューティング
  7. まとめ

なお、次の事柄については適宜必要に応じて実施しているものとするため、説明は省略します。

  • GRANT文を用いたMySQLユーザの追加
  • MySQL 5.6レプリケーションスレーブのクローン
  • 稼働試験用データベースの作成と、それを用いたWebアプリケーションとの結合動作試験
  • LVSを用いたMySQLのDSR構成化

ここまで完了すれば移行の準備はほぼ完了です。今回の事例では段階を追わずに一気にMySQL 5.6構成への切り替えを行いました。段階的に移行するならば、マスタをMySQL 5.0、スレーブをMySQL 5.6という構成を挟んでも良いでしょう。

mroongaのインストール方法

前回までの記事では、MySQL 5.0のスレーブとしてMySQL 5.6をぶら下げるため、MySQLのインストールのみを行いました。続いて、RPM作成の記事でビルドしたRPMパッケージを用いて、mroongaのインストールを行います。

# groongaリポジトリをまだインストールしていない場合に実行
$ sudo rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm

# groongaリポジトリより、関連パッケージのインストール
$ sudo yum install mecab mecab-devel mecab-ipadic groonga-libs groonga-tokenizer-mecab --enablerepo=groonga

# MySQLが起動していることを確認
$ sudo /etc/init.d/mysql status

# 自分でパッケージビルドしたmroongaのRPMパッケージをインストール
$ sudo yum localinstall /home/build/rpmbuild/RPMS/x86_64/mysql56-mroonga-3.05-1.el6.x86_64.rpm

# mroongaがインストールされていることをmroongaという行があることで確認
$ mysql -uroot -p -e "SHOW PLUGINS" | grep mroonga
| mroonga | ACTIVE | STORAGE ENGINE | ha_mroonga.so | GPL |

# インストールできたmroongaのバージョンや設定を確認
$ mysql -uroot -p -e "SHOW VARIABLES LIKE 'mroonga_%'"
+------------------------------------+-------------+
| Variable_name                      | Value       |
+------------------------------------+-------------+
| mroonga_database_path_prefix       |             |
| mroonga_default_parser             | TokenBigram |
| mroonga_default_wrapper_engine     |             |
| mroonga_dry_write                  | OFF         |
| mroonga_enable_optimization        | ON          |
| mroonga_libgroonga_version         | 3.0.5       |
| mroonga_log_file                   | groonga.log |
| mroonga_log_level                  | NOTICE      |
| mroonga_match_escalation_threshold | 0           |
| mroonga_version                    | 3.05        |
+------------------------------------+-------------+

もしMySQLを停止した状態でmroongaのRPMパッケージをインストールした場合には、次の記事を参考に手動でmroongaの有効化を行います。

Tritonnからmroongaへのテーブルスキーマ書き換えガイド

mroongaのインストールが完了したので、mroongaストレージエンジンを用いてフルテキストインデックスを作成します。それでは、Tritonnで動かしていたときとほぼ互換となるmroongaスキーマの紹介を行います。

NGRAMインデックスを作成する場合

Tritonnで言うNGRAMはいわゆるbigramで、mroongaでのBigramとイコールです。

-- Tritonnでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX USING NGRAM `fulltext` (text)
) ENGINE = MyISAM DEFAULT CHARSET utf8;

-- mroongaストレージモードでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX `fulltext` (text) COMMENT 'parser "TokenBigram"'
) ENGINE = mroonga DEFAULT CHARSET utf8;

-- mroongaラッパーモード(MyISAM)での定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX `fulltext` (text) COMMENT 'parser "TokenBigram"'
) ENGINE = mroonga DEFAULT CHARSET utf8 COMMENT='engine "MyISAM"';

TritonnでUSING句を省略するとNGRAMが適用されていたように、mroongaでもBigramがデフォルトです。そのため、FULLTEXT INDEXの後ろで指定していたCOMMENTでのparser指定を次のように省略しても構いません。

-- mroongaストレージモードでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX `fulltext` (text)
) ENGINE = mroonga DEFAULT CHARSET utf8;

-- mroongaラッパーモード(MyISAM)での定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX `fulltext` (text)
) ENGINE = mroonga DEFAULT CHARSET utf8 COMMENT='engine "MyISAM"';

そして、N-gramは文書をN文字ずつに切り出した見出し語を元に、転置インデックスを作成する方法です。詳細は次の記事が参考になります。

また、mroongaではTokenBigram以外にも様々なN-gramに対応しておりますので、気になる方は次の記事を参照しましょう。

単語(MeCab)インデックスを作成する場合

形態素解析エンジンであるMeCabも、Tritonn同様に使えます。TritonnではMeCabで使える辞書はipadicのみでしたが、mroongaではipadicの他にjumandicも利用できます。先ほどのyum installコマンドのmecab-ipadicの代わりにmecab-jumandicを指定するだけで使えるようになります。

-- Tritonnでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX USING MECAB (text)
) ENGINE = MyISAM DEFAULT CHARSET utf8;

-- mroongaストレージモードでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX `fulltext` (text) COMMENT 'parser "TokenMecab"'
) ENGINE = mroonga DEFAULT CHARSET utf8;

-- mroongaラッパーモード(MyISAM)での定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX `fulltext` (text) COMMENT 'parser "TokenMecab"'
) ENGINE = mroonga DEFAULT CHARSET=utf8 COMMENT='engine "MyISAM"';

DELIMITEDインデックスを作成する場合

空白で区切られた文字列単位でインデックスを作成します。これはスペース区切りで単語を入れて利用する、いわゆるタグ検索に便利です。

-- Tritonnでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX USING DELIMITED (text)
) ENGINE = MyISAM DEFAULT CHARSET utf8;

-- mroongaストレージモードでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX `fulltext` (text) COMMENT 'parser "TokenDelimit"'
) ENGINE = mroonga DEFAULT CHARSET utf8;

-- mroongaラッパーモード(MyISAM)での定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX `fulltext` (text) COMMENT 'parser "TokenDelimit"'
) ENGINE = mroonga DEFAULT CHARSET utf8 COMMENT='engine "MyISAM"';

MySQLの素のfulltext indexを作成する(sennaインデックスを使用しない)場合

mroongaはTritonnのようなMyISAMに手を入れることは行っていないため、素直にテーブルを作るだけで事は足ります。

-- Tritonnでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX USING NO SENNA (text)
) ENGINE = MyISAM DEFAULT CHARSET utf8;

-- mroonga時代の定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  FULLTEXT INDEX (text)
) ENGINE = MyISAM DEFAULT CHARSET utf8;

マルチセクション機能の使用/不使用(tritonn-1.0.4より対応)

mroongaではスキーマで定義せずとも、マルチセクション機能(Wプラグマ)が使えます。複合FULLTEXTインデックスを作れば、その中から任意のフィールドで検索できます。

-- Tritonnでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  text2 TEXT NOT NULL,
  FULLTEXT INDEX USING MECAB, SECTIONALIZE (text, text2)
) ENGINE = MyISAM DEFAULT CHARSET utf8;

-- mroongaストレージモードでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  text2 TEXT NOT NULL,
  FULLTEXT INDEX `fulltext` (text, text2) COMMENT 'parser "TokenMecab"'
) ENGINE = mroonga DEFAULT CHARSET utf8;

-- mroongaラッパーモード(MyISAM)での定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  text2 TEXT NOT NULL,
  FULLTEXT INDEX `fulltext` (text, text2) COMMENT 'parser "TokenMecab"'
) ENGINE = mroonga DEFAULT CHARSET utf8 COMMENT='engine "MyISAM"';

正規化機能の使用/不使用

TritonnではNO NORMALIZEとすることで、正規化を行わない指定ができました。例では正規化なしでNGRAMトークナイズを行います。カラム単位での例を挙げますが、テーブル単位でCOLLATE=utf8_binとしても良いでしょう。

-- Tritonnでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT NOT NULL,
  text2 TEXT NOT NULL,
  FULLTEXT INDEX USING NGRAM, NO NORMALIZE (text, text2)
) ENGINE = MyISAM DEFAULT CHARSET utf8;

-- mroongaストレージモードでの定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT utf8_bin NOT NULL,
  FULLTEXT INDEX `fulltext` (text) COMMENT 'parser "TokenBigram"'
) ENGINE = mroonga DEFAULT CHARSET utf8;

-- mroongaラッパーモード(MyISAM)での定義
CREATE TABLE test (
  id INTEGER AUTO_INCREMENT,
  PRIMARY KEY (id),
  text TEXT utf8_bin NOT NULL,
  FULLTEXT INDEX `fulltext` (text) COMMENT 'parser "TokenBigram"'
) ENGINE = mroonga DEFAULT CHARSET utf8 COMMENT='engine "MyISAM"';

以上、Tritonnとmroongaのスキーマ書き換えガイドでした。

mroongaはTokenMecab, TokenNgramだけでなく合計約20種類のトークナイザを利用できます。詳細は次の記事が参考になります。

Tritonnからmroongaへの全文検索クエリ書き換えガイド

Tritonnのユーザガイドにてまとめられている検索クエリは基本的にmroongaでもそのまま使えます。ただ一部異なる点があるため、mroongaではどうクエリを書き換える必要があるのか、順を追って紹介します。

基本的な検索

このパターンのクエリは完全互換があり、そのまま使えます。

SELECT * FROM tbl WHERE MATCH(col) AGAINST("検索キーワード");

boolean modeの利用(演算子の利用)

Tritonnと同様に、mroongaも次のような検索時に使うboolean modeをサポートしています。

AND検索

キーワードAを含み、かつキーワードBを含む文書の検索

OR検索

キーワードAまたはキーワードBを含む文書の検索

AGAINST句の括弧の末尾にIN BOOLEAN MODEとつけることで演算子が利用できるようになります。例えば「東京」「神奈川」を含み、⁠埼玉」を含まない文書を検索する場合には次のようなSELECT文になります。

SELECT * FROM t1 WHERE MATCH(document) AGAINST("+東京 +神奈川 -埼玉" IN BOOLEAN MODE);

Tritonnでは解釈のできない演算子や不正バイト文字列が渡された場合には、それ以降の文字列を無視していました。しかしmroongaではその処理を厳格に行うため、演算子に使われる()~+><-*記号を含む文字列を検索すると次のようなエラーが起きます。

ERROR 1064 (42000): failed to parse fulltext search keyword: <<<foo>>>: <Syntax error! (<foo>)>

演算子に使われる記号を含む検索を行う方法を紹介します。ポイントとしては、AGAINST句のシングルクォートの中で、ダブルクォートでワードを囲むということです。

-- mroongaで<foo>という検索をする例(動かない)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("+<foo>" IN BOOLEAN MODE);

-- mroongaで<foo>という検索をする例(動かない)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("+'<foo>'" IN BOOLEAN MODE);

-- mroongaで<foo>という検索をする例(動く)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("+\"<foo>\"" IN BOOLEAN MODE);

-- mroongaで<foo>という検索をする例(動く)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('+"<foo>"' IN BOOLEAN MODE);

さらに、ダブルクォートを含む検索を行う場合にはこのように2重でエスケープする(⁠\をもう一つ追加する)必要があります。理由としては、MySQLで解釈された後にgroongaへ渡されるという2回の処理を挟むためです。

-- mroongaで"foo"という検索をする例(動かない)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("+"foo"" IN BOOLEAN MODE);

-- mroongaで"foo"という検索をする例(動かない)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("+'\"foo\"'" IN BOOLEAN MODE);

-- mroongaで"foo"という検索をする例(動かない)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('+"\"foo\""' IN BOOLEAN MODE);

-- mroongaで"foo"という検索をする例(動く)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('+"\\"foo\\""' IN BOOLEAN MODE);

律儀にエスケープ処理する実装もできますが、業務要件によっては検索キーワードにこれらの演算子がある場合には取り除いた上で検索する処理としても良いでしょう。

なお、MySQL 5.0のTritonnでもダブルクォートでワードを囲った検索も使えます。しかし制御文字や不正なバイト文字列が渡された場合にはTritonnが動くMySQLが落ちる不具合があるため、入力文字のフィルタリングを適宜行いましょう。

mroongaのプラグマ・演算子対応状況

TritonnではSennaのクエリーが使えますが、mroongaではそのすべてに対応しているわけではありません。2013年8月執筆時点のプラグマ及び演算子対応状況は次の通りです。

プラグマの対応状況

Dプラグマ・Wプラグマに対応しています。Eプラグマには対応していません。例えば演算子の既定値(演算子を省略した場合にどの演算を行うか)を指定するDプラグマは次のように使います。

-- 東京 と アジア料理 を共に含むレコードを検索(Tritonn)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("*D+ 東京 アジア料理" IN BOOLEAN MODE);

-- 東京 と アジア料理 を共に含むレコードを検索(mroonga)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('*D+ 東京 アジア料理' IN BOOLEAN MODE);

演算子を含むワードの検索をする場合には次のようなクエリになります。検索語をダブルクォートで囲う必要があるため、シングルクォートの中でダブルクォートを使います。

-- 東京 と <アジア料理> を共に含むレコードを検索(mroonga)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('*D+ 東京 "<アジア料理>"' IN BOOLEAN MODE);

OR検索は次のように使います。

-- 東京 または アジア料理 を含むレコードを検索(Tritonn)
SELECT * FROM t1 WHERE MATCH(document) AGAINST("*DOR 東京 アジア料理" IN BOOLEAN MODE);

-- 東京 または アジア料理 を含むレコードを検索(mroonga)
SELECT * FROM t1 WHERE MATCH(document) AGAINST('*DOR 東京 アジア料理' IN BOOLEAN MODE);
演算子の対応状況

10種類の演算子のうち、主要な6つの演算子に対応しています。

  • +単語
  • -単語
  • OR 単語
  • (単語・演算子群)
  • 単語*
  • "単語"

ただし、次の演算子には対応していません。

  • ~単語
  • 単語
  • *S[数値]"文字列"
  • *N[数値]"文字列"

kwic関数

Tritonnのkwic関数は、mroongaではmroonga_snippetで代替します。詳細はmroonga公式ドキュメントのスニペットの取得方法 (文脈付き索引)が参考になります。

2ind機能(2インデックス同時使用機能)

Tritonnでは不要なデータアクセスを省略するための2indパッチが提供されていました。mroongaではこれが標準となり、必要最小限のコストで行カウントの結果を返すことができます。詳細はmroonga公式ドキュメントの3.3.1.10. 行カウント高速化が参考になります。

mroongaのプロダクション環境への投入

以前の記事で紹介した「Tritonn」から「mroonga」への移行ガイド(2)移行の進め方とサーバ構成の第2~第3段階を実施する準備ができました。つまり、次のような流れでアプリケーションから利用するデータベースをmroongaに切り替えれば移行完了となります。

第2段階
  1. MySQL 5.6スレーブをの配下に、MySQL 5.6の孫スレーブを作る
  2. 準同期レプリケーションの設定をMySQL 5.6のレプリケーションマスタ・スレーブに行う
  3. 参照クエリ稼働試験として、影響範囲と時間を限定した上で部分的にプロダクション環境へ投入する
  4. プロダクション環境からの参照クエリを、MySQL 5.6スレーブに完全に切り替える
第3段階
  1. 更新系クエリを止めるため、サイトをメンテナンス画面に切り替えてオフラインにする
  2. MySQL 5.6にバイナリログがすべて到達していることを確認した後に、レプリケーションから切り離す
  3. MySQL 5.6のレプリケーションマスタに更新クエリが直接発行されるアプリケーションをデプロイする
  4. メンテナンス画面を解除する

さて、レガシーとなったMySQL 5.0を捨てて、MySQL 5.6への移行を行う準備は整いました。 事前の予行演習無しでの移行は大変なリスクですので、同様の構成を仮想マシンで作成し、当日立ち会うスタッフと共に手順の読み合わせや調整を行います。それを何度か繰り返すうちに個別業務要件に合った手順書ができると思いますので、それを元に当日の切り替え作業に臨みます。そして五月雨式にシステムの切り替えを進め、これといったトラブルも無くあっけなく移行は完了させることができました。

なお、第3段階のMySQLマスタ切り替え時のメンテナンス画面切り替え後は、MySQL 5.0のマスタに更新系クエリが来ても受け入れないようにするため、set global read_only = 1というクエリを発行しました。また、メンテナンス画面の作用範囲には動作試験時に使うグローバルIPアドレスを除外し、アプリケーションデプロイ後の最終稼働試験や品質テストを行いました。

振り返ってみると、ここに行き着くまで・後に行った次のような事前の品質テストが大変でした。

  • トークナイザやノーマライザの組み合わせによる検索ヒット数の違いと最適な組み合わせの検討
  • 検索クエリの動作確認のため、アクセスログから抜き出したURL情報を用いて試験環境で走らせても落ちないことを確認
  • MySQL 5.6で改善されたオプティマイザにより、逆に遅くなったクエリの調整
  • MySQL 5.6でより厳格となったSQLMODEへの追従

例えば3つめの遅くなったクエリの調整は事前の洗い出しが大変手間なため、移行後にスロークエリ集計と逐次対応を行う計画を盛り込んだことで、迅速なクエリチューニングができました。

こういった大規模な切り替えにはリスクは伴いますが、試験環境でのバグ収束を確認できたところで早々に移行を行い、その後に見つかった些細な不具合は随時改修するという対応が良いと思います。

Tritonnからmroongaへ移行する際の要注意ポイント

移行中にハマった要注意箇所は次の通りです。

文字のエスケープ

Tritonnではこれまで緩い処理であったところが厳格になったため、先ほどの「Tritonnからmroongaへの全文検索クエリ書き換えガイド」にて紹介したような、ダブルクォートで囲うだけでなく必要に応じてエスケープする処理が必要になりました。

TokenMecabを用いた際のハマリポイント

記号や半角数字等が4,097文字以上連続すると、トークナイズできないためにINSERT時には無視され、warningとなります。

メモリチューニング

MySQL 5.0時代から使っているmymemcheckを引き続き利用しました。mroongaテーブルのデータは空でもFULLTEXTインデックスを作成しただけで/var/lib/mysql以下の*.mrnファイル群は相当な容量を使いますので、空きメモリを多めに確保しておきましょう。

mroongaのトラブルシューティング

運用中に遭遇したトラブルシューティングをまとめておきます。

API version for STORAGE ENGINE plugin is too different

mroongaで「API version for STORAGE ENGINE plugin is too different」というエラーが起きたときの対処法が参考になります。

.mrnファイルがlock failedとなる

groonga/mroongaの.mrnファイルがlock failedとなった場合の復旧方法が参考になります。

ERROR 1005 (HY000): already used name was assigned

存在しないはずのテーブルが作れない。その場合にはmroonga内の管理データに異常が起きていると推測できます。mroongaで「ERROR 1005 (HY000): already used name was assigned」エラーが起きた際の復旧手順が参考になります。

まとめ

Tritonnからmroongaへの移行プロジェクト、遂にフィナーレを迎えました。移行中に踏み抜いた不具合は数知れず、しかし開発者の皆様との密な連携によりその困難を乗り越えられました。ありがとうございます。

なお、この移行プロジェクトでは数多くの自社WEBサービスを対象に、五月雨式にTritonnからmroongaへの乗り換えを行いました。約3ヶ月に及ぶ検証と実移行を終えた今、プロダクション環境にてmroongaを入れた総計40台以上のMySQL 5.6サーバが稼働している光景はとても感慨深いです。

近日開催のMySQL Casual Talks vol.5にて、ここでは書けないようなお話も交えてお話させていただく予定です。時期としてはYAPC::Asia Tokyo 2013の後に開催予定と聞いておりますので、ご興味のある方はぜひお越しください。

追記:「MySQL Casual Talks vol.5」は、2013年10月25日(金)に日本オラクル 青山センターにて開催です。

Tritonnからmroongaへの移行プロジェクトのご声援ありがとうございました。それではまた、どこかでお会いしましょう。

次回予告

次回はいよいよ最終回です。⁠読者の皆さんがgroongaを使いたくなる!」ことを目指して、2013年4月2日より約半年にわたり隔週でgroongaの情報を届けてきました。最終回ではgroonga関連プロダクトの最新情報と今後の展望について紹介します。

おすすめ記事

記事・ニュース一覧