これはMisskey Advent Calendar 2021の22日目の記事です。
私は世界最大のMisskeyインスタンス、Misskey.ioを運営しています。(物は言いよう)
そんな世界最大のインスタンスでヤバい障害を起こしてしまったため、この記事では障害の詳細や同様の障害を起こさないために必要な対策について書いていきます。
クソデカフラグを立ててしまった
まず大規模障害の前日、私はDiscordの通話で以下のような発言をしていました。
障害内容
起こしてしまった障害ですが、Misskey.ioのデータを半年分吹き飛ばしました。
この吹き飛ばしてしまったデータには投稿だけでなく半年間に作成されたアカウントも含まれており、消失してしまったアカウント数は2,000アカウントほどになります。
原因
Misskey.ioは同一システム内にMisskey.devがあり、そこに対してDDoS攻撃が行われました。
どうしてDDoS攻撃がデータの消失につながったのか説明するために、まずは基本的なサーバー構成を知っていただく必要があります。
Misskey.ioのDB構造
設計されていた構造
まず想定されていたMisskey.ioのDB構造は以下の通りになります。
- DB-1はDB-2と同期レプリケーション
- DB-2から1日ごとに全てのデータをGoogleドライブに暗号化してアップロード
- 非同期で東京や仙台にもデータをコピーし、それらのデータも1日ごとにGoogleドライブへアップロード、Btrfsによるスナップショットも作成
障害発生時の構造
これが設計通り動いていればどんな障害も怖くない超冗長設計だったのですが、実際障害が発生した時点でのサーバー構成は以下のようになっていました。
つまりこれはどういうことかというと、正常なデータを持っているのは札幌第一のみということです。
管理Botの設計が甘かった
Misskey.ioはメンテナンスやシステム障害の対応をほぼ全てBotに任せています。
そして今回の大規模障害は主にBotの設計ミスによるもので、本来であれば障害発生時に自動的に復旧するはずのプロセスが、自動的にデータを破壊するプロセスとなってしまったとも言えます。
登場人物
- Bot-1
- DB-1, DB-2のデータが破損した際に他のDBからデータを上書きする
- Bot-2
- 各DBのプロセスを定期的に確認し、ダウンした場合は再起動を試みる
- Bot-3
- DB-1とDB-3、DB-2とDB-4を同期する
- 村上さん(私)
- ポンコツ管理者
さて、この愉快な仲間たちはどんな物語を繰り広げてくれたのでしょうか。
反映を忘れただけなのに
今年の3月下旬、NUROの不具合によりDB-1とDB-3の通信が不安定になっためDB-4を東京から仙台に移動を行いました。
その後、仙台の回線工事が終わるまでDB-4は更新が停止しており、仙台の回線工事終了後Bot-3により正常にDB-3とDB-4を同期が完了したとの通知が来ていたため、DB-2とDB-4を同期するよう設定ファイルの変更はしました。
そう、設定ファイルの変更”は”したんですね。
みなさんご存知ないかもしれませんが、設定ファイルを変更したら適用もしないといけないらしいんですね。
つまり実際には障害発生時までDB-3とDB-4のみが同期されており、DB-2とは全く同期されていない状態になっていたということです。
DDoSを許すな
今年の4月上旬、DDoS攻撃が頻繁に行われたことによりDB-1, 2とDB-3の同期が遅れ始め、4月16日にはついにWALの保存期間を過ぎ、DB-1とDB-3の同期ができなくなりました。
Bot-2によりプロセスの監視は行っていましたが、PostgreSQLは同期に問題が発生してもプロセスが落ちないため、Bot-2には正常に動作していると記録し続けていました。
プロセスは落ちていないが同期に失敗しているためデータのダンプに失敗し、Googleドライブには中身が空の圧縮ファイルがアップロードされていました。
Botの暴走
上記までだと同期のミスは発生しているものの、半年分のデータが消失するとは思えない。
DB-1が仮にハードウェア障害で完全に起動できなくなったとしてもDB-2に同期レプリケーションを行なっていたので全く同じデータを持っているはず。
では、一体何が起こったのか・・・
Misskey.devにDDoS攻撃が行われ、PostgreSQLに大量のクエリが送信されたためDBのプロセスが完全にフリーズ
この際、プライマリはDB-1であったためすぐにDB-2がプライマリへと昇格・・・したが、DDoS攻撃が継続されていた事により一瞬でフリーズ
5分以上経過してもプロセスがハング状態になっていたため、Bot-2がDB-1, 2に対して再起動命令を行った。
この瞬間までは最新の情報がしっかりと保存されていた。
できるならこの瞬間まで戻りたい。
その後、再起動には成功しているものの攻撃が継続していたためすぐにハングしてしまい、Bot-2がDB-1, DB-2のデータが破損し起動できないと判断し、Bot-1に対してDB-3からデータを復元するように命令。
そう、DB-3には4月16日のデータが入っているのである・・・
Bot-1がDB-1, DB-2のBtrfsサブボリュームを自動的に削除して再生成し、rsyncを使いDB-1, DB-2のデータを4月16日で同期が停止しているDB-3のデータに自動上書きした。
Btrfsのスナップショットから復元・・・できなかった
Btrfsを知っている人は、どうしてスナップショットから復元しなかったのだろうと疑問に思うと思います。
これに関しては完全に設計をミスっていて、本来であれば以下のようになっているべきでした。
disk
├ postgres
└ snapshot
└ postgres_2021xxxxxxxx
そして実際障害が発生した際の構造は・・・
disk
└ postgres
└ snapshot
└ postgres_2021xxxxxxxx
そう、postgresというサブボリュームが削除されてしまうとスナップショットも一緒に消える構造になっていたわけです。
これによりMisskey.ioは全てのデータを失うこととなりました。
この障害を起こさないための対策
結局どうすればよかったのか
この障害発生のポイントは大きく分けて4つあります。
- 大量のサーバーが正常に同期できていなかったこと
- バックアップが正常に作成できていなかったこと
- 人の目で定期的にチェックしていなかったこと
- 復元テストを定期的に行なったいなかったこと
この障害を起こさないためには適切なバックアップを行い、それを定期的に人間の手によって復元するテスト行うことです。
現在Misskey.ioでは再発防止策として以下の対策を行なっています。
- 1週間に1回は手動でバックアップを作成する
- 1ヶ月1回、バックアップデータからの復元をテストする
- 最小構成の非同期レプリケーションを同一リージョン内に用意する
1、2に関しては言わずもがなという感じですが、3に関しては同一リージョンに置く事によってネットワーク遅延での同期ミスを無くし、Btrfsなどの特殊なことをしない環境を用意する事によって自動化されたプロセスによる破壊を防ぐ目的で設置しています。
どれも基礎的な事ですが、基礎的なことを見直す事によって障害の発生を未然に防ぎ、障害発生時の対応も迅速に行えるものだと痛感しました。
今回アドベントカレンダーをギリギリになって書いてしまって居たので一部省略している部分がありますが、後ほど追記する可能性が高いので気が向いた時に再度読んで頂ければ幸いです。
コメント
[…] 明確な初出はMisskey.ioのデータ消失事件もあって現存していないのですが、みれい氏の発言が最初だったとされています。当時流行っていたウマ娘 プリティーダービーに登場するキャラクター「メジロマックイーン」をもじったものですね。世の中上手いこと言う人多くて怖いよ…。 […]