Herokuが開発したWAL-Eを使って、AWSでPostgreSQLのバックアップを取る


今回はEC2インスタンスのOSとしてUbuntuを選択。
CentOS系のAmazon Linuxだとデフォルトで入るバージョンが色々古くてめんどくさいからね。でも`apt-get`を`yum`に変えたらだいたい動くんじゃないかと期待。

[wal-e](https://github.com/wal-e/wal-e)とはもともとHerokuがAWS上でpostgresqlのバックアップを取るために開発していたPythonスクリプト。EC2上で走っているPostgreSQLのデータをS3に保存してくれる。
S3にデータ送信すること自体が`scp`などを使えず厄介なので、そこを解決してくれているところもあるのだが、単に`s3cmd`等でバックアップを取るのと違ってデータを圧縮して送ってくれていたり、今あるバックアップ一覧をコマンド一発で見れたりする部分が良いと思う。それ以上には詳しく調べてないけど、HerokuはAWS上でPostgreSQLを動かすノウハウは一番あるのではないかと言うようなサービスなので、なんとなく良さそうに思えるｗ

## wal-eのインストール

最新を入れたい人は、gitのソースコードから入れる。

```
git clone https://github.com/wal-e/wal-e.git
cd wal-e
sudo python setup.py install
```

ちょっと古くてもいい人は、以下のようにやると良い。(pip派はpip使って下さい)

```
sudo apt-get install python-setuptools
sudo apt-get install python-dev libevent-dev
sudo easy_install wal-e
```

ちなみに、`postgresql`も`apt-get`で入れた。そしたらVersion9.1.8が入った。最新の9.2が使いたかったら自分でやること。

```
$ sudo apt-get install postgresql
$ psql --version
psql (PostgreSQL) 9.1.8
```


## wal-e用設定

https://github.com/wal-e/wal-e にそのまま従って設定

```
sudo su -
umask u=rwx,g=rx,o=
mkdir -p /etc/wal-e.d/env
echo XXXXXXXXX > /etc/wal-e.d/env/AWS_ACCESS_KEY_ID
echo xxxxxxxxxxxxxxxxxxxxx > /etc/wal-e.d/env/AWS_SECRET_ACCESS_KEY
echo 's3://your-bucket-name/backups' > /etc/wal-e.d/env/WALE_S3_PREFIX
chown -R root:postgres /etc/wal-e.d
```

ここyour-bucket-nameはs3のUS Standardに作る。(wal-eは現状us-eastでしか動かない。[Issue](https://github.com/wal-e/wal-e/pull/3)は出来ているのでもうすぐ出来るようになるかもしれないけれど。)
さらに以下のブログによると悪意のあるデータ削除対策としてVersioningを有効にすると良いらしい。
http://blog.opbeat.com/2013/01/07/postgresql-backup-to-s3-part-one/

## wal-eでバックアップを取る

まず必要な環境をInstall

```
sudo apt-get install daemontools pv lzop mbuffer
```

wal-eでログを取るためにはwal_levelがarchive以上が必要。
wal_levelはminimal(デフォルト), archive, hot_standbyの順に上がっていく。
http://www.postgresql.jp/document/9.1/html/runtime-config-wal.html

今回はarchiveでやるが、hot_standbyにするときは、
http://blog.gomiso.com/2012/02/13/adventures-in-scaling-part-3-postgresql-streaming-replication/
を参考にすると良さそう。

```/etc/9.1/main/postgresql.conf
#------------------------------------------------------------------------------
# WRITE AHEAD LOG
#------------------------------------------------------------------------------

wal_level = archive # hot_standby in 9.0 is also acceptable
archive_mode = on
archive_command = '/usr/bin/envdir /etc/wal-e.d/env /usr/local/bin/wal-e wal-push %p'
archive_timeout = 300
```

と設定して、リスタート！

```
sudo /etc/init.d/postgresql restart
```


なお、`archive_timeout`は少し大きめの5分間隔にした。本当にデータを失いたくないなら、hot_stanbyにするべきだし、archive-modeで1分は短すぎてパフォーマンス的に嬉しくない気がする。詳しくは[postgresqlのドキュメント](http://www.postgresql.jp/document/9.1/html/runtime-config-wal.html#GUC-ARCHIVE-TIMEOUT)参照。

`archive_command`では念のため、`envdir`や`wal-e`コマンドをfull pathで指定しておくと良いと思う。自分はこのコマンドが走るときだけ、`/usr/local/bin`がPATHに入ってないことに気づかずにだいぶハマった。なお、`archive_command`における`%p`は格納されるファイルの絶対パスを表す。
http://www.postgresql.jp/document/9.1/html/runtime-config-wal.html#GUC-ARCHIVE-COMMAND


一応`archive_command`に設定した`wal-push`がちゃんと動くか確かめておこう。(`0000000100000000000000B4`の部分は各自の環境で、`ls /var/lib/postgresql/9.1/main/pg_xlog`した結果の中から一つ選ぶ。)

```
sudo su - postgres
envdir /etc/wal-e.d/env wal-e wal-push /var/lib/postgresql/9.1/main/pg_xlog/0000000100000000000000B4
```

うまくいってそうだったら`backup-push`してみる。

```
sudo su - postgres
envdir /etc/wal-e.d/env wal-e backup-push /var/lib/postgresql/9.1/main/
envdir /etc/wal-e.d/env wal-e backup-list  # ちゃんととれたか確認
```

ここで、`/var/lib/postgresql/9.1/main/`はPostgreSQLのデータが入ったディレクトリのこと。`postgresql.conf`に以下の様な記述があるはず。

```/etc/postgresql/9.1/main/postgresql.conf
data_directory = '/var/lib/postgresql/9.1/main' 
```

ちなみに自分たちは、これを追加したEBS上に設定しなおしている。

## バックアップタスクをcronに登録

```
sudo su - postgres
crontab -e
```

とし、以下のように打つ。

```
0 4 * * * /usr/bin/envdir /etc/wal-e.d/env /usr/local/bin/wal-e backup-push /var/lib/postgresql/9.1/main/
```

これで毎日午前4時にバックアップがとられる。
(dateコマンドを打ってTimezoneがJSTになっていることを確認すること)

JSTにするには以下のようにすれば良い。

```
sudo mv /etc/localtime /etc/localtime.default
sudo ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

## バックアップからのリカバリ

バックアップからのリカバリはこれが参考になる。
https://gist.github.com/ruckus/2293434

とりあえずリカバリ実験用に無理やりpostgresをkillする。

```
sudo pkill -9 postgres
```

ここからリカバリするには、

```
sudo su - postgres
cp /usr/share/postgresql/9.1/recovery.conf.sample /var/lib/postgresql/9.1/main/recovery.conf
emacs /var/lib/postgresql/9.1/main/recovery.conf
```

で`restore_command`にwal-fetchを設定する。

```recovery.conf
restore_command  = '/usr/bin/envdir /etc/wal-e.d/env /usr/local/bin/wal-e wal-fetch %f %p'
```

これで、`sudo /etc/init.d/postgresql start`や`pg_ctl start`などでPostgreSQLを再起動させたらWALが残っているところまで(ほぼ全部)復元され、サービスが再開される。

なお、DBがrecoveryされたら、`recovery.conf`は`recovery.done`という名前になる。

### データそのものが失われてしまっていた場合

ハードディスクが飛んでデータも消えちゃったことを想定し、

```
rm -rf　/var/lib/postgresql/9.1/main/
```

する。
これは、

```
umask u=rwx,g=,o=
envdir /etc/wal-e.d/env wal-e backup-fetch /var/lib/postgresql/9.1/main/ LATEST
```

として`backup-push`したデータから基本的な構成を復元した後、先ほどの手順に従って`recovery.conf`を作成してpostgresqlを再起動したら、`wal_push`されs3にアーカイブが残っている部分まで復元できる。

## 不要なアーカイブログとチェックポイントのクリーンアップ

過去の`backup-push`のデータや、`wal-push`で送られたアーカイブログは際限なく溜まっていく。S3なのでこれらのファイルを削除しないという方針でいいかもしれないが、DBを復元する際に必要のない、直前の`backup-push`より前のものは削除してしまっても問題ない。

これを行うためには、以下のコマンドを叩けば良い。

```
envdir /etc/wal-e.d/env wal-e backup-list | tail -n1 | cut -f1 | xargs envdir /etc/wal-e.d/env wal-e delete --confirm before
```

もう少し慎重に、例えば、3つ前まで残しておきたいというときは、`tail -n1`を`tail -n3 | head -n1`に変えればよい。

## ハマったポイント

設定に書くときは、`wal-e`を`/usr/local/bin/wal-e`にしないとダメだったという話。エラーから検索してたどり着く人もいるかもと思って書いておく。

```
NOTICE:  pg_stop_backup cleanup done, waiting for required WAL segments to be archived
WARNING:  pg_stop_backup still waiting for all required WAL segments to be archived (60 seconds elapsed)
HINT:  Check that your archive_command is executing properly.  pg_stop_backup can be canceled safely, but the database backup will not be usable without all the WAL segments.
WARNING:  pg_stop_backup still waiting for all required WAL segments to be archived (120 seconds elapsed)
HINT:  Check that your archive_command is executing properly.  pg_stop_backup can be canceled safely, but the database backup will not be usable without all the WAL segments.
```

これは調べても、ディスク容量が足りないことやpermissionの設定が間違っているのではといった情報しか出てこないのだが、どれも当てはまらなかった。ここで、走らせる`archive_command`を

```
archive_command = 'date >> test.txt && echo %p >> test.txt && envdir /etc/wal-e.d/env wal-e wal-push %p || echo $? >> test.txt'
```

とし、`/var/lib/postgresql/9.1/main/test.txt`を確認することで、エラーが出た時の終了コードが111となっていることがわかり、[daemontoolsのenvdirの項目](http://www.emaillab.org/djb/tools/daemontools/envdir.html)を見ると、envdirはエラーコード111で死ぬことがわかり、原因を特定できた。

実際は以下の様なエラーが出ていたみたい。

```
envdir: fatal: unable to run wal-e: file does not exist
```

これで、

```
envdir /etc/wal-e.d/env wal-e wal-push %p
```

を

```
envdir /etc/wal-e.d/env /usr/local/bin/wal-e wal-push %p
```

に変えればいいことがわかった。

bashrc的なものはshを読み込んだ時しか動かないので、手元で走らせても動くけど、postgresが走らせるときには動かないみたいなことが起こるんだなと思った。気づけば当たり前なんだけど・・・。