GitLab.comのデータベース障害から学ぶ

(最終更新:2017-02-15)

2017/01/31、GitLab.comでデータベース障害が発生し、サービスの停止および約6時間分のデータベースのデータ(projects、 issues、comments、snippetsなど)が消失しました。

なお、Gitリポジトリに障害はなく、リポジトリのコンテンツ(Wikiを含む)にデータ消失はありませんでした。

GitLab.comとは?

GitLab.comはGitLab社が提供しているGitHubのようなGitリポジトリのホスティングサービスです。無料でプライベートリポジトリも使えます。

タイムライン

日時(UTC) 出来事
2017/01/31 23:27 GitLab.com、オペレーションミスにより本番データベースのデータを削除。サービス停止
2017/01/31 23:28 データベースの緊急メンテナンス中である旨をツイート
2017/02/01 00:00 約6時間前のスナップショットをリストアすることを決定
2017/02/01 00:44 Google Docsでのライブの状況報告についてツイート
2017/02/01 11:42 YouTubeでの復旧作業のライブストリームについてツイート
2017/02/01 18:14 GitLab.com復旧
2017/02/10 Postmortem(事後分析)公表

透明性

今回のGitLabのインシデント対応で際立ったことは、迅速な報告と透明性です。

恐ろしく早く状況が詳細にGoogle Docsで報告されていき、ついに復旧作業がYouTubeでライブで流されました。

データバックアップは失敗したのか?

データベースの消失は、複数のインシデント対応中にオペレーションミスが発生し、本番のPostgreSQLデータベースのデータディレクトリが削除されたことによります。

GitLabは事前に以下のバックアップ手段を講じていました。

  1. 24時間ごとのデータベースダンプ(pg_dump)
  2. データベースダンプのS3へのバックアップ
  3. 24時間ごとのAzureのディスクスナップショット
  4. 24時間ごとのLVMスナップショット
  5. PostgreSQLのレプリケーション

しかし、実際には、

5.のレプリケーションはもともとリカバリ目的ではなくフェイルオーバーのためのものであり、厳密にはバックアップとは言えません。また、そもそもレプリケーションを再構築する途中で結果的に今回のインシデントが発生しています。障害発生時にはレプリケーションにはすでにデータはありませんでした。

1.のデータベースダンプは、pg_dumpのバージョンが9.2と古く、実際のデータベースはPostgreSQL 9.6だったため、エラーでダンプが取得できていませんでした。そのため、2.のS3へのバックアップも当然空でした。

3.のAzureのディスクスナップショットはNFSサーバでは取得されていましたが、データベースサーバでは取得されていませんでした。また、仮にスナップショットがあったとしても、ディスクスナップショットから一部のファイル(データベースファイル)をリストアすることは簡単ではありません。

4.のLVMスナップショットは機能していましたが、障害の6時間前に手動で取得された別のスナップショットがありました。そのため、今回は6時間前のスナップショットからリストアされました。

つまり、いろいろとバックアップに不具合はありましたが、それでも最悪24時間前の状態にはLVMスナップショットから戻れたということになります。

仮にすべてのバックアップ手段が正常に機能していたとしても、データベースダンプは最大24時間前のものですし、ディスクスナップショットも最大24時間前です。

このことから、リカバリ要件は最大24時間のデータロスを前提としていたと考えられます。そして、少なくとも24時間前のLVMスナップショットがあったことから、その時点への復旧は可能でした。

結論としては、今回のインシデントに関しては、バックアップは要件を満たして機能していたことになると思います。

重要なのはリカバリ要件

もし、24時間のデータロスが許容できないのであれば、それはリカバリ要件が間違っていたということになります。

また、リカバリ要件には復旧時間もありますので、そちらの要件はたぶん満たせていなかったのでしょう。Postmortemには、復旧に18時間以上かかったことが問題であるとされています。

Postmortem(事後分析)

事前に予告されていた通り、GitLabからPostmortemが02/10に公表されました。

根本原因分析(なぜなぜ分析)やリカバリ手順の改善など、非常に興味深い内容が含まれています。是非、お読み下さい。

データディレクトリが削除された原因は、レプリケーションの再構築手順が自動化されておらず、また、作業のドキュメントもきちんと作成されていなかったからとされています。

バックアップ手順が検証されていなかった原因については、責任者が決まっていなかったことがあげられています。

まとめ

こうして見ると、今回の障害はリカバリ要件の定義やバックアップやリストア手順の設計により根本的な問題があったことがわかります。要するに事前の分析検討や準備計画が不十分だったということです。

  • リカバリ要件が不明か誤ってる
  • バックアップの責任者がいない
  • バックアップからリストアできるか検証されていない
  • バックアップが正常に取得できているかの監視がない
  • レプリケーション再構築の手順書が不十分
  • 複雑な作業を一人で行う運用
  • 間違いやすそうな似たようなサーバ名(db*.cluster.gitlab.com)

障害の直接の原因は、担当者が誤ってデータベースのデータディレクトリをrmしてしまったことですが、それは単なるきっかけにすぎないことがわかります。

参考

GitLabからのデータベース障害に関する報告:

GitLabの緊急対応マニュアル:

PostgreSQLのコミッタからのアドバイス:

この障害に関する記事:

Tags: postgres, gitlab, incident, database

PHPUnit 6.0の主な変更点

PHPUnit 6.0.0が2017/02/03にリリースされました。その後立て続けにバージョンがあがり、02/08に6.0.6がリリースされています。

主な変更点

要件

PHP 7.0以上が必要になりました。

名前空間

PHPUnit_Framework_TestCasePHPUnit\Framework\TestCaseに変更されました。

もし、既存のテストコードを変更できない場合は、bootstrapに以下のハックを追加することで対応できます。

if (! class_exists('PHPUnit_Framework_TestCase')) {
    class_alias('PHPUnit\Framework\TestCase', 'PHPUnit_Framework_TestCase');
}

デフォルト設定

デフォルト設定が変更されました。

  • backupGlobals="true"でしたが、backupGlobals="false"に変更されました
  • 役に立たないテストがRiskyになります

既存のテストコードをそのまま動作させたい場合は、phpunit.xmlbackupGlobals="true"を設定してください。

ただし、backupGlobals="false"の方がテストの実行が速くなりますので、falseでテストが通るようにした方が好ましいと思います。

デフォルトはfalseにして、必要なテストケースだけ@backupGlobals enabledにすることも可能です。

役に立たないテストは修正するか削除すべきでしょう。

ログ

  • --log-junitフォーマット変更
  • --log-json廃止

テストランナー

すべてのテストがパスするが警告がある場合、exitコードが1に変更されました。

廃止

  • $this->getMock()
  • $this->setExpectedException()および$this->setExpectedExceptionRegExp()
    • $this->expectException(), $this->expectExceptionCode(), $this->expectExceptionMessage(), $this->expectExceptionMessageRegExp() を使います
  • --tap

その他

  • PHP 5.3以上が要件のPHPUnit 4.8の保守が終了しました
  • PHP 5.6以上が要件のPHPUnit 5.7は2018/02/02まで保守されます

参考

Tags: phpunit

NetBeans 8.2でのPHPのリモートデバッグ(ステップ実行)

NetBeans 8.2、MAMP 4.1(Apache、PHP 7.1、Xdebug 2.5.0)、CodeIgniter 4.0-devという組み合わせでの、ステップ実行のやり方です。

Xdebugの設定

ApacheのPHPでXdebugとリモートデバッグを有効に設定します。

--- php.ini.org 2016-12-16 02:04:54.000000000 +0900
+++ php.ini 2017-02-10 16:24:00.000000000 +0900
@@ -1161,4 +1161,5 @@
   opcache.enable_cli=1

 [xdebug]
-;zend_extension="/Applications/MAMP/bin/php/php7.1.0/lib/php/extensions/no-debug-non-zts-20151012/xdebug.so"
+zend_extension="/Applications/MAMP/bin/php/php7.1.0/lib/php/extensions/no-debug-non-zts-20160303/xdebug.so"
+xdebug.remote_enable=1

php.iniを変更したら、Apacheを再起動します。

phpinfo()を表示して、Xdebugが有効になっていることを確認します。

NetBeansでのプロジェクトの設定

プロジェクト名を右クリックし、プロジェクトの「実行構成」を設定します。

「詳細」ボタンを押して、「詳細Web構成」の設定をします。

これで設定が完了しました。

デバッグの実行

「プロジェクトをデバッグ」ボタンを押します。

「URLを指定」ウィンドウが開きますので、そのまま「OK」を押します。

ブラウザが自動的に立ち上がり、ソースコードの最初の行で実行が止まります。

右上のボタンを使うことで、ステップ実行を操作できます。

Tags: netbeans, mamp, debug, php, codeigniter