隔週連載groonga

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

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

これまでのあらすじ

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

そのパート1である前回は、主に次のトピックについて解説しました。

  • Tritonnとmroongaそれぞれの紹介と移行時の要注意点
  • 現在利用できる日本語全文検索ソリューションの比較
  • ストレージエンジンをMyISAMからmroongaへ移行する際に気をつけたいAuto Incrementの挙動の違い

今回の流れ

Tritonnからmroongaへの移行はもちろんのこと、稼働中のシステムを最小ダウンタイムで移行したい。それを叶えるにはMySQL 5.6を、既存のMySQL 5.0を用いたMaster-Slave構成のレプリケーションに追加し、段階的移行をする必要があります。この段階的移行おうとする中で、Tritonn(MySQL 5.0)で動く更新系クエリをMySQL 5.6およびmroonga互換にするという壁が現れます。

今回はその壁を乗り越え、MySQL 5.0のレプリケーションマスタにMySQL 5.6をスレーブとして追加するところまでを、詳しく解説していきます。

MySQL 5.0からMySQL 5.6へのデータマイグレーション(事前準備編)

移行対象のサーバ構成

移行対象のサーバ構成は次の図の通り、レプリケーションマスタをDRBDで冗長化している以外、オーソドックスなMySQLのMaster-Slave構成のレプリケーションです。

移行前のサーバ構成
画像

これらは、Tritornnの最終リリース版であるMySQL 5.0.87 + Senna 1.1.4 + mecab 0.98を利用し、レプリケーションマスタはDRBD + keepalivedを用いた冗長化を行っています。ストレージエンジンは基本的にInnoDBを、フルテキスト検索を用いる場合にはMyISAMを利用しています。

移行の進め方とサーバ構成

既にTritonnで稼働中のシステムをmroongaへ移行するわけですから、ダウンタイムを最小にしたアップグレードを行う必要があります。

そこで、既存MySQL 5.0レプリケーションマスタの下に、MySQL 5.6のみで構成するレプリケーションを追加するという手法を採用しました。この、いわゆる「孫スレーブ構成」を取ることで、プログラムからの更新系・参照系クエリの発行先をMySQL 5.6のマシンに切り替えるだけで移行ができます。

今回の記事では第1段階のステップ5、MySQL 5.0と5.6の混在レプリケーション構成を作るまでを解説します。

第1段階

MySQL 5.0のスレーブにMySQL 5.6を追加します。月に1度しか動かないCronによる定期実行タスクを考慮し、この状態で1ヶ月の稼働試験期間を設けます。後ほど紹介する、変更が必要な更新クエリを先に潰せていれば特にトラブルはありませんが、レプリケーションが止まるようなクエリに遭遇した場合には随時対処しましょう。

MySQL 5.0配下にMySQL 5.6をスレーブとして追加した後のサーバ構成
画像

具体的には次のように、6つのステップに分けて進めます。

  1. プロダクション環境のスレーブを複製し、MySQL 5.0用の作業サーバを用意する
  2. MySQL 5.0用の作業サーバから、Tritonn依存のFULLTEXTインデックスを除去する
  3. Tritonn依存の無くなったMySQLのダンプデータをMySQL 5.6へ流し込む
  4. 更新クエリをTritonn・mroonga互換の実装にする
  5. 既存のMySQL 5.0レプリケーション構成に、ダンプデータの流し込まれたMySQL 5.6をスレーブとして追加する
  6. MySQL 5.6スレーブにmroongaをインストールし、mroongaを用いたフルテキスト検索対応のスキーマに更新する

第2段階

MySQL 5.0のスレーブに追加したMySQL 5.6を複製し、孫Slaveを追加します。これで、MySQL 5.6のみで構成するレプリケーションが構築できました。この時期には参照クエリに関してもTritonn/mroonga両対応になりましたので、影響範囲や時間を絞った上で、参照クエリのみMySQL 5.6のスレーブに振り分けるという稼働試験を行いました。

MySQL 5.6のマスタ・スレーブ構成を追加した後のサーバ構成
画像

具体的には次のような細かいタスクとなります。

  1. MySQL 5.6スレーブをの配下に、MySQL 5.6の孫スレーブを作る
  2. 準同期レプリケーションの設定をMySQL 5.6のレプリケーションマスタ・スレーブに行う
  3. 参照クエリ稼働試験として、影響範囲と時間を限定した上で部分的にプロダクション環境へ投入する
  4. プロダクション環境からの参照クエリを、MySQL 5.6スレーブに完全に切り替える

第3段階

プログラムから利用するサーバを、MySQL 5.6のレプリケーションマスタ・スレーブに完全に切り替えます。レプリケーションマスタの切り替え以降は、データの整合性的な意味合いで切り戻しができません。そのため、慎重に行う必要があります。

MySQL 5.6のマスタ・スレーブ構成ヘ切替後のサーバ構成
画像

具体的にはこのような細かいタスクとなります。

  1. 更新系クエリを止めるため、サイトをメンテナンス画面に切り替えてオフラインにする
  2. MySQL 5.6にバイナリログがすべて到達していることを確認した後に、レプリケーションから切り離す
  3. MySQL 5.6のレプリケーションマスタに更新クエリが直接発行されるアプリケーションをデプロイする

この段階的移行手法をとる場合の注意として、現レプリケーションマスタで実行される更新系クエリがMySQL 5.0およびmroonga (MySQL 5.6)の両方で動く必要があります。

具体的にはFULLTEXTインデックスへ作用するCREATE TABLEやALTER TABLEといったスキーマを修正するクエリが対象となります。例えば、検索テーブルにおいて参照するテーブルと更新するテーブルを分けて、更新が終わり次第その2テーブルを入れ替えるという運用の場合には確認が必要です。この2テーブル運用は、更新中は排他ロック (テーブルロック) となり、参照クエリの実行ができないというMyISAMの制約から生まれたパフォーマンスチューニングです。

更新クエリをTritonn・mroonga互換の実装にする

周知の通り、MySQL 5.0およびTritonn専用の更新クエリが発行される状態では、レプリケーションのスレーブとして追加できません。そこでまずは、MySQL 5.0で動く既存システムへ発行される更新系クエリの精査を行います。MySQL 5.6環境でも互換のある実装にしておくことでMySQLバージョン混在でのレプリケーションが可能となり、ダウンタイム最小での移行を果たせます。

それでは、どういったクエリをどのように書き換えることで実現できるのか、具体例と共に紹介します。

プログラムの修正の必要がある実装例

問題となるmroonga非互換のSQLはCREATE TABLE文のFULLTEXTインデックスにあります。厳密には、USING句からのオプションです。例えば、次のようなSQLです。

-- FULLTEXT行のUSINGから括弧までの箇所が、Tritonn依存
CREATE TABLE search_fulltext_ready (
  id INT,
  content TEXT,
  FULLTEXT INDEX ft USING MECAB, NORMALIZE (content)
) ENGINE=MyISAM DEFAULT CHARSET utf8;

Cronでの定期実行タスクなどによりTritonn専用のCREATE TABLE構文などが発行されると、mroongaでレプリケーションエラーが起きてしまいます。私のケースではCronで次のようなSQLを発行していました。

-- 更新中に利用する作業テーブルを作成する
-- これが問題のSQL
CREATE TABLE search_fulltext_ready (... FULLTEXT INDEX ft USING MECAB (content) ...;

-- 作業テーブルへデータをINSERTやLOAD DATAなどを利用して流し込む
LOAD DATA INTO INFILE search_fulltext_ready ...;

-- 2テーブルを入れ替える。1クエリで行うことで、search_fulltextが存在しない瞬間ができることを避ける
RENAME TABLE search_fulltext TO search_fulltext_old, search_fulltext_ready TO search_fulltext;

-- 古くなったテーブルを削除する
DROP TABLE search_fulltext_old

では、このSQLをどのように変えれば解決できるか、追って解説しましょう。

2つのテーブルを入れ替えるという修正方法

mroonga互換の更新系クエリにするには、単純にCREATE TABLE文をCREATE TABLE search_fulltext_ready LIKE search_fulltext;というクエリに置き換えるだけと思いきや、ここに落とし穴があります。それは、Tritonnの制限事項にも記述されている「USING句が落ちる」問題です。

この「USING句が落ちる」とは、オリジナルが何であろうともUSING MECAB, NO NORMALIZE, 512と書き換わる現象です。これはTritonnのデフォルトであるUSING MECAB NORMALIZEとは異なるものです。

「TRUNCATE TABLE t1を実行するとUSING句情報が落ちる」

この問題は、WHERE句を省略したDELETE文を使うことで解決します。

DELETE FROM search_fulltext_ready;

「CREATE TABLE t2 LIKE t1を実行するとUSING句情報が落ちる」

この問題は、事前に用意した2つのテーブルを入れ替える運用で解決します。

RENAME TABLE search_fulltext TO search_fulltext_old, 
  search_fulltext_ready TO search_fulltext, 
  search_fulltext_old TO search_fulltext_ready;

具体的な修正は、次の手順で行います。

-- 更新中に利用する作業テーブルを空にする
DELETE FROM search_fulltext_ready;

-- 作業テーブルへデータをINSERTやLOAD DATAなどを利用して流し込む
LOAD DATA INTO INFILE search_fulltext_ready ...;

-- テーブルの入れ替えを行う
RENAME TABLE search_fulltext TO search_fulltext_old, 
  search_fulltext_ready TO search_fulltext, 
  search_fulltext_old TO search_fulltext_ready;

特に該当するものが無ければ、影響しうるような改修が追加で行わないよう、チームへのコードフリーズの周知を行うだけで事足りるでしょう。

データマイグレーション計画

前項にてMySQLバージョン混在のレプリケーションを行うための互換プログラムへの改修を取り上げました。次は、既存データを何らかの方法でMySQL 5.6に流し込み、MySQL 5.0のスレーブとしてレプリケーションさせる準備を行います。

MySQL 5.0と5.6の作業サーバ、計2台を利用したデータのマイグレーションの手順は次の通りです。

  • 本番稼働中の待機系スレーブを一時的に切り離す
  • 待機系スレーブの内容をMySQL 5.0用の作業サーバに複製する
  • MySQL 5.0用の作業サーバからMySQL 5.6用の作業サーバにデータを流し込む

大まかにはこのような手順を踏むことで、MySQLのアップグレードができます。

それでは次に、具体的なデータマイグレーションを行う2つの方法を紹介します。

なお、事前に次の作業は済んでいるものとします。

  1. MySQL 5.0と5.6の作業サーバを構築する
  2. 本番稼働中の待機系スレーブを一時的に切り離す
  3. 待機系スレーブの内容をMySQL 5.0用の作業サーバに複製する

方法1: 段階的なアップデート

MySQLのアップグレードを行う一般的な手法として、mysql_upgradeコマンドがよく知られています。しかし今回は2つ飛び越えての大幅なアップデートとなるため、次のようにとても手間がかかります。

  1. MySQL 5.0用の作業サーバにて、Tritonn依存のないスキーマに変更する
  2. この作業サーバにて、MySQLを5.1へアップデートする
  3. mysql_upgradeコマンドを用いて データもMySQL 5.0 から 5.1へアップデートする。エラーが起きたら都度修正する
  4. この作業サーバにて、MySQLを5.5へアップデートする
  5. mysql_upgradeコマンドを用いて データもMySQL 5.1 から 5.5へアップデートする。エラーが起きたら都度修正する
  6. この作業サーバにて、MySQLを5.6へアップデートする
  7. mysql_upgradeコマンドを用いて データもMySQL 5.5 から 5.6へアップデートする。エラーが起きたら都度修正する
  8. 全文検索を行いたいテーブルを、ALTER TABLE構文を用いてmroonga化する

方法2: mysqldumpを用いた移行方法

もう一つの方法として、mysqldumpコマンドを用いる手法があります。これは、データベースをSQLファイルに書き出した後に、MySQL 5.6のクリーンなデータベースに流し込むというものです。この場合には次のように、比較的シンプルな方法で移行を行えます。

  1. MySQL 5.0用の作業サーバにて、Tritonn依存のないスキーマに変更する
  2. MySQL 5.6用の作業サーバにて、mysqldumpでMySQL 5.0用の作業サーバよりデータベース毎に吸い出す
  3. MySQL 5.6用の作業サーバにて、吸い出したダンプファイルを自身に流し込む
  4. MySQL 5.6用の作業サーバにて、全文検索を行いたいテーブルを、ALTER TABLE構文を用いてmroonga化する
  5. そのまま移すことができないMySQLユーザを、手動で適宜追加する

今回採用した方法

段階的バージョンアップに手間を感じたことと、InnoDBのibdataファイルの肥大化を解消したいという要件があるため、mysqldumpを用いた移行方法を採用します。

というのもMySQL 5.0ではまだ目新しかったinnodb_file_per_table設定を利用していないため、InnoDBの共有データスペースであるibdata1が数百GBレベルに肥大化していたのです。この肥大化の原因となったテーブルを削除したとしても一度拡張されたらデータサイズが減ることはないため、縮小するにはSQLダンプファイルから作り直すほかに方法はありません。今回のタイミングを機にmysqldumpで生成したSQLダンプファイルより、1から綺麗に作り直しを行うことにしました。

MySQL 5.0からMySQL 5.6へのデータマイグレーション(前編)

MySQL 5.0からMySQL 5.6へのデータマイグレーションの手順については前述の通りです。ステップを追って進めていきます。

移行時に使うサーバとその使い分け

CentOS 6.4(X86_64)のサーバを2台、次の使い分けで作業サーバとします。

  • MySQL 5.0用の作業サーバ
  • MySQL 5.6用の作業サーバ

MySQL 5.0 のインストール

MySQL 5.0用の作業サーバへ、SourceForgeのTritonnバイナリ配布ページより必要ファイルを入手の上、yum localinstallコマンドでインストールします。

インストール後、必要に応じて/etc/my.cnfの書き換えを行います。

yum localinstall \
  mecab-0.98-tritonn.1.0.12a.x86_64.rpm \
  mecab-ipadic-2.7.0.20070801-tritonn.1.0.12a.x86_64.rpm \
  senna-1.1.4-tritonn.1.0.12a.x86_64.rpm \
  MySQL-shared-5.0.87-tritonn.1.0.12a.x86_64.rpm \
  MySQL-client-5.0.87-tritonn.1.0.12a.x86_64.rpm \
  MySQL-server-5.0.87-tritonn.1.0.12a.x86_64.rpm \
  MySQL-devel-5.0.87-tritonn.1.0.12a.x86_64.rpm

MySQL 5.6 のインストール

MySQL 5.6用の作業サーバへ、オラクルが配布している公式rpmを用いて、yum installコマンドでインストールします。なお、まだこの段階ではmroongaを使いません。次回、MySQL 5.6用のrpmパッケージのビルド方法を紹介します。インストール後、必要に応じて/etc/my.cnfの書き換えを行います。

$ sudo yum install \
  http://cdn.mysql.com/Downloads/MySQL-5.6/MySQL-shared-compat-5.6.12-1.el6.x86_64.rpm \
  http://cdn.mysql.com/Downloads/MySQL-5.6/MySQL-shared-5.6.12-1.el6.x86_64.rpm \
  http://cdn.mysql.com/Downloads/MySQL-5.6/MySQL-client-5.6.12-1.el6.x86_64.rpm
  http://cdn.mysql.com/Downloads/MySQL-5.6/MySQL-server-5.6.12-1.el6.x86_64.rpm \
  http://cdn.mysql.com/Downloads/MySQL-5.6/MySQL-devel-5.6.12-1.el6.x86_64.rpm

MySQL 5.0用の作業サーバへデータを投入する(プロダクション環境サーバでの作業)

まずはプロダクション環境サーバでの作業での作業を行います。Tritonn依存のないスキーマに変更するために、MySQL 5.0用の作業サーバへプロダクション環境のデータを複製します。何かしらの方法で数時間オフラインにできるサーバを確保してから、次のようにデータを吸い出します。

なお、問答無用でMySQLのサービスを止めてしまうと、一時テーブルが消失してレプリケーションエラーが発生することがあります。そのため、Slave_open_temp_tablesの値を確認しながらの作業を行います。こちらの詳細はMySQL公式ドキュメントレプリケーションとテンポラリ テーブルを参照ください。

-- レプリケーションを一時的に停止する
stop slave;

-- slave_open_tmp_tablesが0であることを確認する
mysql> show status like 'Slave_open_temp_tables';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| Slave_open_temp_tables | 0     |
+------------------------+-------+
1 row in set (0.00 sec)

-- もしslave_open_tmp_tablesが0でない場合には、気長に0となるまで以下を繰り返す
start slave;
stop slave;
show status;

レプリケーションを止めることができたら、続けてMySQLのサービスを停止し、MySQLのデータディレクトリを、MySQL 5.0作業マシンへscpなどで転送します。

# レプリケーションのポジションを確認し、メモしましょう
# 必要になる値は、 Master_Log_File と Exec_Master_Log_Pos の2つ
# MySQL 5.6用の作業サーバでレプリケーション開始する際に必要となる
$ mysql -uroot -p -e "SHOW SLAVE STATUS\G"

# 安全にレプリケーションを停止できたら、MySQLのサービスを停止する
$ sudo /etc/init.d/mysql stop

# 何らかの方法でMySQLの設定ファイルとデータをMySQL 5.0作業マシンへコピーする
$ sudo scp -p /etc/my.cnf ユーザ名@MySQL 5.0用の作業サーバIP:/tmp/
$ sudo scp -rp /var/lib/mysql ユーザ名@MySQL 5.0用の作業サーバIP:/tmp/mysql

# MySQL 5.0作業マシンへのファイル転送が完了したら、MySQLサービスを開始する
$ sudo /etc/init.d/mysql start

# 起動後、レプリケーションがこれまで通りに動いていることを確認する
$ mysql -uroot -p -e "SHOW SLAVE STATUS\G"

MySQL 5.0用の作業サーバへデータを投入する(MySQL 5.0作業マシンでの作業)

プロダクション環境サーバからのコピーが完了したら、MySQL 5.0作業マシンでの作業を始めます。必要ファイルをそれぞれ配置した後、MySQLサービスを開始します。

# もしMySQLが起動していたらサービスを停止する
/etc/init.d/mysql stop

# デフォルトで生成されているデータディレクトリがあれば、移動ないし削除を行う
$ sudo mv /var/lib/mysql /var/lib/mysql-default

# プロダクション環境よりコピーしてきたデータを配置し、mysqlユーザで読み書きできるようにする
$ sudo mv /tmp/mysql /var/lib/mysql
$ sudo chown -R mysql:mysql /var/lib/mysql

# プロダクション環境よりコピーしてきた設定ファイルを配置する
$ sudo mv /tmp/my.cnf /etc/my.cnf

# レプリケーションのポジション情報が変わることを防ぐため、
# 起動時にレプリケーションが動かないようにする設定を追加する
# mysqldセクションにskip-slave-startと記入する
$ sudo vi /etc/my.cnf 

# 設定が済みましたら、MySQLサービスを起動する。
# 心配な場合には、起動前に/var/lib/mysqlのバックアップをとっても良い
$ sudo /etc/init.d/mysql start

# 起動後、レプリケーションが停止状態になっていることを確認する
# 具体的には、Slave_IO_RunningとSlave_SQL_Runningが共にNoであることを確認する
$ mysql -uroot -p -e "SHOW SLAVE STATUS\G" | grep Running

Tritonn依存のないスキーマに変更する

Tritonn依存のスキーマがない状態になってはじめて、MySQL 5.6用の作業サーバへデータの流し込みができるようになります。ここでは、FULLTEXTインデックスを一旦除去するためのDROP INDEX文を作成します。

それでは、MySQL 5.0用の作業サーバにて、mysqldumpコマンドを利用してスキーマを取り出します。なお、このファイルは後ほどスキーマをmroonga対応にする際にも利用しますので、消さずに保管しましょう。

# 次のようにデータベース毎にスキーマをmysqldumpで取得する
$ mysqldump -uroot -p --no-data --triggers \
  --routines データベース名 > /path/to/データベース名.sql

書き出したSQLファイルの中に、"FULLTEXT"という行を持つテーブルをリストアップしましょう。今回はそれらテーブルを対象に、ALTER TABLEクエリを作成していきます。

このスキーマで作られたテーブルを例に説明します。

CREATE TABLE search_fulltext (
  id INT,
  content TEXT,
  FULLTEXT INDEX ft USING MECAB, NORMALIZE (content)
) ENGINE=MyISAM DEFAULT CHARSET utf8;

ftという名前でフルテキストインデックスが作られているため、次のクエリでTritonn依存のインデックスを削除できます。同様にその他のテーブルに関しても確認を行い、MySQL 5.0用の作業サーバにてそれぞれ実行します。

ALTER TABLE search_fulltext DROP INDEX ft;

MySQL 5.0のSQLダンプを作成

先ほどの作業により、MySQL 5.0用の作業サーバにあるデータからTritonn依存のスキーマはなくなりました。ようやく、MySQL 5.0用の作業サーバよりmysqldumpコマンドを用いてデータベース毎に吸い出すことができます。

早速MySQL 5.6用の作業サーバにて、次のようにmysqldumpを行います。

$ mysqldump -h MySQL5.0サーバのIP -u root -p --quick --no-data \
  --triggers --routines --order-by-primary --no-autocommit \
  --set-gtid-purged=OFF データベース名 > /path/to/データベース名.sql

なお、MySQL 5.6からの新しいオプション--set-gtid-purged=OFFは、MySQL 5.6以下のGTID非対応のMySQLからデータをダンプする際に必要になるオプションです。

SQLダンプをMySQL 5.6へインポート

先ほどの作業で吸い出したSQLダンプデータを、MySQL 5.6用の作業サーバへ流し込みます。

# データベースを作成する
$ mysql -uroot -p -e "CREATE DATABSE データベース名"

# ダンプデータを流し込む
$ mysql -uroot -p DB < /path/to/データベース名.sql

MySQL 5.6にてレプリケーションを開始

“MySQL 5.0用の作業サーバへデータを投入する(プロダクション環境サーバでの作業)"にてメモを行った、Master_Log_FileExec_Master_Log_Pos を利用し、CHANGE MASTER構文を作ります。

CHANGE MASTER TO
  MASTER_HOST = 'レプリケーションマスタのIP',
  MASTER_USER = 'レプリケーションユーザ',
  MASTER_PASSWORD = 'レプリケーションパスワード',
  MASTER_LOG_FILE = 'Master_Log_Fileの値',
  MASTER_LOG_POS = Exec_Master_Log_Posの値;

CHANGE MASTER文の準備ができたらいよいよ、レプリケーションの開始です。

-- CHANGE MASTERで登録した内容が期待通り反映されていることを確認する
SHOW SLAVE STATUS\G

-- レプリケーションを開始する
START SLAVE;

-- Slave_IO_RunningとSlave_SQL_Runningが共にYesとなっていれば問題ない
SHOW SLAVE STATUS\G

これで無事、MySQL 5.0の下にMySQL 5.6をスレーブとして追加する、MySQL 5.0と5.6の混在レプリケーションが完了しました。

今回はここまでとし、この続きは次回となります。

まとめと次回予告

次回はいよいよ本プロジェクトの大詰めとなります。 次のトピックをお届けする予定です。乞うご期待!

  • Tritonnからmroongaへのスキーマ書き換えガイド
  • Tritonnからmroongaへの全文検索クエリ書き換えガイド
  • CentOS 6+MySQL 5.6環境で動くmroongaのrpmパッケージ構築方法

おすすめ記事

記事・ニュース一覧