Percona xtrabackupを使用したバックアップ、リストアでハマったこと

10月からリブセンスに入社した @takemotto です。
この記事は 2015年 Livesense Advent Calendar 第21日目です。
現在、本番DBと開発DBのデータ同期にxtrabackupの導入を進めています。
本日は実際にxtrabackupを使ってみて、私が「ハマった」ことをご紹介したいと思います。


# 環境や前提条件
CentOS 6.4
MySQL 5.6.12
xtrabackup 2.2.11
lz4がインストールされていること

# 構築方法
弊社一部環境ではmroogaを使用しているため、弊社先輩方の記事をありがたく参考にさせていただきました。あざます。
[Percona xtrabackupでmroongaをバックアップ・リストア](http://qiita.com/Ets/items/13aafd6f8e4bc9aab792)
[CentOS6.xに最新版のMySQL5.6とmroongaを最速でRPMインストールする方法](http://y-ken.hatenablog.com/entry/how-to-install-mysql5.6.x-with-mroonga-for-centos6)


#使い方
## Xtrabackupバックアップ、リストア手順
まずバックアップの使い方を簡単にご紹介します。
今回のバックアップはxtrabackupコマンドではなく、そのラッパーツールであるinnobackupexコマンドを使用します。
こんな感じです。

```
$ sudo innobackupex --user=root --stream=xbstream --parallel=4  /var/tmp/xtrabackup/ | lz4 -9 > /var/tmp/<ファイル名>.xbstream.lz4
```

これでフルバックが取得できます。圧縮しているのは、バックアップファイルをリストア先に転送するためです。
圧縮をかけないと日付のフォルダが作成されます。ちなみに圧縮にはlz4を使用しています。
当初、pigzを使用していましたがCPUリソースをフルに使うし気軽に検証ができないことと、
lz4がいいかも！といった流れになったのでlz4を使用しています。
この辺りの検証はこちらの記事を参考にさせていただきました。使用しているオプションもこちらの記事を参考にしています。
[Percona XtraBackupの抽出と圧縮の並列処理](http://blog.father.gedow.net/2012/12/05/percona-xtrabackup-parallel/)

データベース単位でバックアップしたい場合は、「--database」オプション使用すればOKです。
指定しなければすべてのDBがバックアップされます。

```
$ sudo innobackupex --user=root --stream=xbstream --parallel=4 --databases='<DB名>' /var/tmp/xtrabackup/ | lz4 -9 > /var/tmp/<ファイル名>.xbstream.lz4
```

リストアしたい場合は、こんな感じです。

```
# /var/tmpに上記手順で取得したバックアップがある前提で
$ cd /var/tmp
$ mkdir xtrabackup
$ cd xtrabackup

## 解凍処理
$ lz4 -d ../<ファイル名>.xbstream.lz4 | xbstream -x -v
```
するとMySQLのデータディレクトリ配下のファイルが展開されます。
次にリストアです。
事前にmysqlを停止して、mysqlのデータディレクトリ（今回の場合だと/var/lib/mysql）を退避もしくは削除しておく必要があります。

```
$ cd ../
## WAL(Write Ahead Log)を適用
$ sudo innobackupex --user=root --apply-log xtrabackup/
・・・・・
151218 10:55:06  innobackupex: completed OK!

## リストア開始
$ sudo innobackupex --copy-back xtrabackup/
innobackupex: Finished copying back files.

151218 11:31:05  innobackupex: completed OK!

# 起動
$ sudo chown -R mysql:mysql /var/lib/mysql
$ sudo service mysql start
Starting MySQL... SUCCESS!
```
以上でリストア完了です。

#ハマったこと
## 本番環境DBと開発環境DBに差異がある問題
たとえば本番環境には「A,B」という2つのDB、開発環境には「A,B,C」という3つDBがあったとします。
ここで言うDB「C」は個人用の開発DBだったり。
上記手順では、開発環境のデータディレクトリを一旦退避させ、copy-backコマンドでリストアしているため、リストアした際に開発環境DBの「C」が消えてしまいます。

その対策として見つけた方法がテーブル単位でバックアップリストアする方法です。
バックアップする際に指定したテーブルだけをバックアップ、リストアすることで、その他のDB消されることはありません。
こちらの記事を参考にしました。
[Percona XtraBackupの機能紹介 (2) テーブル単位で抽出](http://blog.father.gedow.net/2012/10/18/percona-xtrabackup-features-vol02/)


手順はこんな感じにしました。

```
## バックアップ
$ sudo mysqldump -uroot --no-data <DB名> > /var/tmp/<DB名>_mysql_no_data.sql
$ sudo xtrabackup --backup --datadir=/var/lib/mysql --target-dir=/var/tmp/export_tables --tables="^<DB名>[.]*.*"
$ sudo xtrabackup --prepare --target-dir=/var/tmp/export_tables --export

## リストア
$ myslq -uroot <DB名> < /var/tmp/<DB名>_mysql_no_data.sql

## DISCARD
$ myslq -uroot <DB名> -e "ALTER TABLE <DB名> DISCARD TABLESPACE"

## expとibdファイルを配置
$ sudo cp -p /var/tmp/export_tables/<DB名>/*.exp /var/lib/mysql/<DB名>/
$ sudo cp -p /var/tmp/export_tables/<DB名>/*.ibd /var/lib/mysql/<DB名>/
$ sudo chown -R mysql:mysql $MYSQL_DATADIR/$target_db

## IMPORT
$ myslq -uroot <DB名> -e "ALTER TABLE <DB名> IMPORT TABLESPACE"
$ sudo service mysql start
```

上記手順で検証して、スクリプト化していざ実行ということに。
金曜日の夜にしかけて、土曜日の朝確認し、問題なければ完了という想定。
という私の甘い考えは脆くも崩れ去りました。
朝ログ確認するとDISCARD TABLESPACE処理でこけているではありませんか。。。
更に一旦DBを削除して、元のスクリプトに戻そうとしてもDBが削除できない。。。

DISCARD TABLESPACEがこけた原因は２つでした。

- 外部キー制約にひっかかかった

```
ERROR 1451 (23000) at line 1: Cannot delete or update a parent row: a foreign key constraint fails ()
```

- ストレージエンジンがInnoDB 以外のものが含まれていた

```
ERROR 1031 (HY000) at line 1: Table storage engine for '<テーブル名>' doesn't have this option
```

###まずは「外部キー制約にひっかかかった」件について
DISCARD TABLESPACEで .ibdファイルを削除しているのですが、外部キー制約があるテーブルは削除前に外部キー制約を一旦解除する必要があるようです。よく見ると参考元の記事にも書いてありました。
以下のコマンドで解除できます。

```
mysql> SET FOREIGN_KEY_CHECKS = 0
```

###次に「ストレージエンジンがInnoDB 以外のものが含まれていた」件について
DISCARD TABLESPACEは、InnoDB以外では使用できないようです。
今回はリストア対象にストレージエンジンがMyISAMとmroongaのテーブルが含まれていたため失敗していました。考慮不足。。。
IMPORT TABLESPACEも同様です。
対策はシンプルでストレージエンジンがInnoDBであるものだけ実行するロジックに変更しました。


## データベースの数が多すぎて、バックアップできない
上記作業を実施するにあたり、事前に開発環境DBのフルバックをしようと考えていました。
以下のコマンドです。--databaseを指定していないため、フルバックが走るはずです。

```
$ sudo innobackupex --user=root --stream=xbstream --parallel=4  /var/tmp/xtrabackup/ | lz4 -9 > /var/tmp/<ホスト名>.xbstream.lz4

2015-12-18 09:51:19 7f935bdbe720  InnoDB: Operating system error number 24 in a file operation.
InnoDB: Error number 24 means 'Too many open files'.
InnoDB: Some operating system error numbers are described at
InnoDB: http://dev.mysql.com/doc/refman/5.6/en/operating-system-error-codes.html
InnoDB: Error: could not open single-table tablespace file ./kaguno_production_dev6059/product_texts.ibd
InnoDB: We do not continue the crash recovery, because the table may become
InnoDB: corrupt if we cannot apply the log records in the InnoDB log to it.
InnoDB: To fix the problem and start mysqld:
InnoDB: 1) If there is a permission problem in the file and mysqld cannot
InnoDB: open the file, you should modify the permissions.
InnoDB: 2) If the table is not needed, or you can restore it from a backup,
InnoDB: then you can remove the .ibd file, and InnoDB will do a normal
InnoDB: crash recovery and ignore that table.
InnoDB: 3) If the file system or the disk is broken, and you cannot remove
InnoDB: the .ibd file, you can set innodb_force_recovery > 0 in my.cnf
InnoDB: and force InnoDB to continue crash recovery here.
innobackupex: got a fatal error with the following stacktrace: at /usr/bin/innobackupex line 2711
	main::wait_for_ibbackup_file_create('/tmp/xtrabackup_suspended_2') called at /usr/bin/innobackupex line 2731
	main::wait_for_ibbackup_suspend('/tmp/xtrabackup_suspended_2') called at /usr/bin/innobackupex line 1984
	main::backup() called at /usr/bin/innobackupex line 1609
```

なんだと。。。
結論からいうとDB数が多すぎたことが原因？でした。
「Error number 24 means 'Too many open files'.」
といわれていますし、恐らくそうなんじゃないかなと。
なので今回は、バックアップするDBを明示的に指定して、絞ることで対応しました。

```
$ sudo innobackupex --user=root --stream=xbstream --database='DB1 DB2 DB3' --parallel=4  /var/tmp/xtrabackup/ | lz4 -9 > /var/tmp/<ホスト名>.xbstream.lz4
```

#まとめ
開発環境で試したところ、mysqldumpと比べてリストアの速度が約2倍早くなりました。
また今回のようにテーブル毎にバックアップ、リストアする必要がなければ、xtrabackupを使用することで比較的に簡単にバックアップ、リストアが実現できるのではないでしょうか。