さて、今回はWebサービスを運用しているとたびたび発生する
「新機能開発の為にINDEX追加やカラム追加を行いたい」
「新機能開発の為にINDEX追加やカラム追加を行いたい」
という課題を解決する方法をご紹介します。
数年前まではそういった変更の際は、ゲームをメンテナンス状態にしてから
スキーマ変更を行っていました。
スキーマ変更を行っていました。
しかしゲームをメンテナンス状態にする事は、各方面への調整や利用者への影響が大きいため
気軽にできることではありませんでした。
Clik here to view.
Clik here to view.
Clik here to view.
Clik here to view.
Image may be NSFW.オンラインでスキーマ変更
そこで私達のチームではオンラインでスキーマ変更が可能なpt-online-schema-change(pt-osc)を導入することにしました。
pt-online-schema-changeとは「Percona-Toolkit」に同梱されているツールで、
通常はロックが掛かってしまうALTER TABLEクエリをロックなしで実行することが可能になります。
MySQL5.6からは無停止でINDEX追加やカラム追加ができるオンラインDDLが実装されましたが、
運用しているゲームのデータベースはMySQL5.5の為、このツールによりスキーマ変更を実現させています。
pt-online-schema-changeの仕組み
下記のような順番でスキーマ変更が行われています。
1.対象テーブルAと同じスキーマ構造をした作業用テーブルBを作成Image may be NSFW.
Clik here to view.
Clik here to view.

2.作業用テーブルBに変更するALTER文を適用
Image may be NSFW.Clik here to view.

3.トリガーを3つ作成し、対象テーブルAへの挿入・削除・更新が作業用テーブルBに反映されるようにする
Image may be NSFW.Clik here to view.

4.対象テーブルAから作業用テーブルBへレコードをコピー
Image may be NSFW.Clik here to view.

5.RENAMEして対象テーブルAと作業用テーブルBを入れ替える
Image may be NSFW.Clik here to view.

6.入れ替え後の古いテーブルAとトリガー3つを削除する
Clik here to view.

実際の利用例
インストールは簡単です(例:CentOSに導入する場合)# yum install http://www.percona.com/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm# yum install percona-toolkit
あとは実行するだけです
# pt-online-schema-change --execute --alter="ADD INDEX idx_hogehoge(hoge_id)" h=localhost,D=DB_NAME,t=TABLE_NAME,u=root
オプション名 | 動作内容 |
---|---|
--alter | スキーマ変更を行うクエリを指定 |
h | 接続先 |
D | DBスキーマ名を指定 |
t | テーブル名を指定 |
u | 実行MySQLユーザを指定 |
pt-online-schema-changeを慎重につかうオプション
動作の仕組み上、テーブルのコピーやトリガーの処理が走るため、実行中は普段より高負荷になりやすいです。
そのためpt-online-schema-changeには本番環境へ悪影響が出ないように、リソース状況をチェックしながら動作を制御できる便利なオプションが存在しています。
オプション名 | デフォルト設定 | 動作内容 |
---|---|---|
--max-load | 25スレッド | 指定スレッド数を超えた場合、処理を一時停止 |
--critical-load | 50スレッド | 指定スレッド数を超えた場合、処理を終了する |
--check-slave-lag | なし | 全スレーブのレプリ遅延チェック(実行ユーザはMaster,Slaveで同じ権限が必要) |
--max-lag | 1秒 | 指定秒数のレプリ遅延が発生すると処理を一時停止する |
--check-interval | 1秒 | --max-lagをチェックする間隔 |
--dry-run | なし | 実際には処理が行われないドライラン確認用オプション |
--execute | なし | 処理の実行を行うオプション |
--nodrop-old-table | yes | スキーマ変更前の旧テーブルのDROPを行わないオプション。確認で残しておきたい時に |
--set-vars | なし | MySQLの変数を渡すオプション。レプリケーションさせない時にset-vars="sql_log_bin=0"等 |
過去の失敗事例
このように色々な機能がある便利なツールですが、
運用で利用できなかったパターンや事故が発生したこともあります。
運用で利用できなかったパターンや事故が発生したこともあります。
あまりこのような事例がネットの記事になかったのでいくつかご紹介したいと思います。
事例1:外部キー制約で実行失敗
発生環境
pt-online-schema-change 2.2.14
OS CentOS6.2
MySQL 5.5.34
検証環境でpt-online-schema-changeを実行
実行時のエラー文
事例1:外部キー制約で実行失敗
発生環境
pt-online-schema-change 2.2.14
OS CentOS6.2
MySQL 5.5.34
検証環境でpt-online-schema-changeを実行
# pt-online-schema-change --execute --alter="ADD INDEX idx_hoge(id_hoge)" h=localhost,D=d1,t=t_1,u=root
実行時のエラー文
You did not specify --alter-foreign-keys-method, but there are foreign keys that reference the table. Please read the tool's documentation carefully.
--alter-foreign-keys-methodというオプションで外部キー制約の動作を制御できるようです。
https://www.percona.com/doc/percona-toolkit/2.1/pt-online-schema-change.html#cmdoption-pt-online-schema-change--alter-foreign-keys-method
auto
自動判別するモード。rebuild_constraintsが使えれば使い、使えなかったらdrop_swapを使う
rebuild_constraints
新しいテーブルを参照する外部キー制約を削除してから再追加する。高速
drop_swap
外部キーチェックを無効にする(FOREIGN_KEY_CHECKS = 0)、新しいテーブルを作成して元のテーブルを削除して入れ替える。
この方法は高速でブロックしないが、元のテーブルを削除し一時テーブルの名前を変更する間の時間はテーブルは存在せず、それに対するクエリはエラーになる。
このオプションでテストしてみます
pt-online-schema-change --execute --alter-foreign-keys-method auto --alter="ADD INDEX idx_hoge(id_hoge)" h=localhost,D=d1,t=t1,u=root
実行結果
Image may be NSFW.Clik here to view.

実行したマスターDBサーバで良さそうな感じの実行ログが出てご満悦。
でしたが、スレーブのステータスを確認してみるとレプリケーションが停止していました。
Last_SQL_Error: Error 'Cannot add or update a child row: a foreign key constraint fails *****
事例にもある通り、サービス稼働時のdrop_swapは難しいかもしれません。
事例2:複合主キーを持つ巨大テーブルで実行失敗
発生環境
pt-online-schema-change 2.2.13
OS CentOS6.5
MySQL 5.5.34
実行コマンド
実行エラー文
PT3.0では解決してくれるのでしょうか?
Chunking handles multi-column indexes poorly
発生環境
pt-online-schema-change 2.2.13
OS CentOS6.5
MySQL 5.5.34
実行コマンド
# pt-online-schema-change --execute --alter="ADD INDEX idx_hoge_event(hoge_id, hoge_type, fuga_id)" h=localhost,D=d1,t=t1,u=root
実行エラー文
2015-03-28T00:54:36 Error copying rows from `d1`.`t1` to `d1`.`_t1_new`:2015-03-28T00:54:36 Error copying rows at chunk 1 of d1.t1 because MySQL used only 12 bytes of the PRIMARY index instead of 20. See the --[no]check-plan documentation for more information.解決策はテーブル設計を変更して2列までの複合主キーにするしかなさそうです。
PT3.0では解決してくれるのでしょうか?
Chunking handles multi-column indexes poorly
事例3:--set-vars="sql_log_bin=0"が無視されてエラー
発生環境
pt-online-schema-change 2.2.7-1
OS CentOS6.2
MySQL 5.5.24
pt-online-schema-changeでは普通に実行してもレプリ遅延は発生しづらい作りになっているのですが、
より遅延の発生をなくすために、sql_log_bin=0(binlogを出さないオプション)をつけて実行する運用をしている事がありました。
マスター、スレーブ構成のDBで、先にスレーブ全台でonline-schema-changeでカラム追加してから、
最後にマスターでsql_log_bin=0で実行してカラム追加を行う方法です。
※Rolling Schema Upgrade(RSU)というかっこいい呼び方もあるようです。
この方法でカラム追加を行ったところ、マスターでpt-osc実行後にスレーブ全台のレプリケーションがエラーで停止しました。
1.対象テーブルAと同じスキーマ構造をした作業用テーブルBを作成
2.作業用テーブルBに変更するALTER文を適用
3.トリガーを3つ作成し、対象テーブルAへの挿入・削除・更新が作業用テーブルBに反映されるようにする
という動きがあります。
本来はpt-oscのオプションでsql_log_bin=0をつけているのでこれらのクエリはスレーブ側には飛ばないはずです。
しかし、仕組みの項目(3)のトリガー作成だけがスレーブ側で実行され、
スレーブ側には既に存在しない「作業用テーブルB」への更新がトリガーで実行されてしまい
エラーになってレプリケーションが停止しました。
このエラーが発生する状況を調査したのですが、最終的にこれだという結論に辿り着くことはできませんでした。
もし今後このRolling Schema Upgradeをする運用を続けるのであれば、
仕組みの項目(1)の作業用テーブルBを事前にスレーブ側に作成しておくことでエラーの回避をすることが可能です。
カラム追加するテーブル名が「user_account」の場合、「_user_account_new」という作業用テーブルを作成しておくことで、
トリガーがもし作成されてレプリケーションの停止は発生しなくなります。
発生環境
pt-online-schema-change 2.2.7-1
OS CentOS6.2
MySQL 5.5.24
pt-online-schema-changeでは普通に実行してもレプリ遅延は発生しづらい作りになっているのですが、
より遅延の発生をなくすために、sql_log_bin=0(binlogを出さないオプション)をつけて実行する運用をしている事がありました。
マスター、スレーブ構成のDBで、先にスレーブ全台でonline-schema-changeでカラム追加してから、
最後にマスターでsql_log_bin=0で実行してカラム追加を行う方法です。
※Rolling Schema Upgrade(RSU)というかっこいい呼び方もあるようです。
この方法でカラム追加を行ったところ、マスターでpt-osc実行後にスレーブ全台のレプリケーションがエラーで停止しました。
Last_SQL_Error: Error executing row event: 'Table 'hoge._fuga_table_new' doesn't exist'先程説明したpt-oscの仕組みの中で
1.対象テーブルAと同じスキーマ構造をした作業用テーブルBを作成
2.作業用テーブルBに変更するALTER文を適用
3.トリガーを3つ作成し、対象テーブルAへの挿入・削除・更新が作業用テーブルBに反映されるようにする
という動きがあります。
本来はpt-oscのオプションでsql_log_bin=0をつけているのでこれらのクエリはスレーブ側には飛ばないはずです。
しかし、仕組みの項目(3)のトリガー作成だけがスレーブ側で実行され、
スレーブ側には既に存在しない「作業用テーブルB」への更新がトリガーで実行されてしまい
エラーになってレプリケーションが停止しました。
このエラーが発生する状況を調査したのですが、最終的にこれだという結論に辿り着くことはできませんでした。
検証内容 | 結果 |
---|---|
OSやMySQL、pt-oscのバージョン変更して検証 | エラー再現 |
同じテーブル構造でレコード数を少ない状態で検証 | エラーなし |
別のテーブルでレコード数を数億件追加して検証 | エラーなし |
もし今後このRolling Schema Upgradeをする運用を続けるのであれば、
仕組みの項目(1)の作業用テーブルBを事前にスレーブ側に作成しておくことでエラーの回避をすることが可能です。
カラム追加するテーブル名が「user_account」の場合、「_user_account_new」という作業用テーブルを作成しておくことで、
トリガーがもし作成されてレプリケーションの停止は発生しなくなります。
事例4:テーブルのCOMMENTが日本語の場合すべて文字化けする
発生環境
pt-online-schema-change 2.2.14
OS CentOS6.2
MySQL 5.5.34
このようなテーブル構造があるとします。Image may be NSFW.
Clik here to view.![]()
pt-oscを実行します
Clik here to view.![]()
--charsetオプションでutf8を指定してみます。MySQLのSET NAMESとperlの標準出力を制御してくれます。
Clik here to view.![]()
発生環境
pt-online-schema-change 2.2.14
OS CentOS6.2
MySQL 5.5.34
このようなテーブル構造があるとします。Image may be NSFW.
Clik here to view.

pt-oscを実行します
# pt-online-schema-change --execute --alter="ADD COLUMN hoge_test int NULL DEFAULT NULL COMMENT 'テストだよ' AFTER from_days" h=localhost,D=fuga,t=m_hoge,u=root日本語がすべて文字化けしてしまいましたImage may be NSFW.
Clik here to view.

--charsetオプションでutf8を指定してみます。MySQLのSET NAMESとperlの標準出力を制御してくれます。
pt-online-schema-change --execute --charset=utf8 --alter="ADD COLUMN hoge_test int NULL DEFAULT NULL COMMENT 'テストだよ' AFTER from_days" h=localhost,D=fuga,t=m_hoge,u=root無事文字化けしなくなりましたImage may be NSFW.
Clik here to view.

事例5:Replicate_Ignore_Tableを使っているスレーブでレプリケーション遅延が発生する
発生環境
pt-online-schema-change 2.2.5
OS CentOS6.2
MySQL 5.5.24
pt-oscをマスターで実行した際に、スレーブの設定でReplicate_Ignore_Tableで指定されているテーブルに
スキーマ変更をかけた場合、本来ならばスレーブでは無視されるはずのクエリが実行されて、
作業用テーブルが作成されてしまいデータのコピーが始まります。
その間レプリケーションが遅延し続けてしまいます。
Replicate_Do_Tableを利用しているサーバでは発生しませんでした。
設定例
スレーブ名 | レプリケーション設定 |
---|---|
スレーブA | Replicate_Ignore_Table: Table_A |
スレーブB | Replicate_Do_Table: Table_B |
スレーブC | レプリケーションするテーブル指定なし |
実行例
# pt-online-schema-change --execute --alter="ADD INDEX idx_hogehoge(hoge_id)" h=localhost,D=hoge,t=Table_A,u=root
スレーブA
[root@スレーブA ~]# ls -lh /var/lib/mysql/hoge/*.ibd-rw-rw---- 1 mysql mysql 21G 2月 18 11:02 2016 /var/lib/mysql/hoge/_Table_A_new.ibd-rw-rw---- 1 mysql mysql 42G 2月 18 11:05 2016 /var/lib/mysql/hoge/Hoge_Table.ibd-rw-rw---- 1 mysql mysql 11G 2月 17 17:30 2016 /var/lib/mysql/hoge/Table_B.ibd
スレーブB
[root@スレーブB ~]# ls -lh /var/lib/mysql/hoge/*.ibd-rw-rw---- 1 mysql mysql 11G 2月 17 17:30 2016 /var/lib/mysql/hoge/Table_B.ibd
スレーブC
[root@スレーブB ~]# ls -lh /var/lib/mysql/hoge/*.ibd-rw-rw---- 1 mysql mysql 21G 2月 18 11:02 2016 /var/lib/mysql/hoge/_Table_A_new.ibd-rw-rw---- 1 mysql mysql 59G 2月 18 11:02 2016 /var/lib/mysql/hoge/Table_A.ibd-rw-rw---- 1 mysql mysql 42G 2月 18 11:05 2016 /var/lib/mysql/hoge/Hoge_Table.ibd-rw-rw---- 1 mysql mysql 11G 2月 17 17:30 2016 /var/lib/mysql/hoge/Table_B.ibdスレーブAではTable_A以外のテーブルをレプリケーションするように設定されていますが、
_Table_A_newというTable_Aの作業用テーブルが作成されてしまい全力でデータコピーが走っています。
この間スレーブAではずっとレプリケーション遅延が発生してしまいました。
これ以降の運用では、テーブル指定のレプリケーションをしている環境においては
Rolling Schema Upgrade方式でスキーマ変更をかけることにしました。
まとめ
pt-online-schema-changeはかゆいところに手が届く便利ツール!
銀の弾丸ではないので入念な事前検証と動作確認を!