MySQL, トラブルシューティング

[訓練]誤って有料会員を無料会員に全件更新してしまったので、テーブルを復旧する

経緯 とある動画をラジオ感覚で流していた

 

  • スタートアップに在籍
  • 有料会員数は100程度のサービスを運用
  • メンテナンス作業のミスによるインシデントが発生

事故の内容

1件の特定の有料会員を無料会員に修正するメンテナンスを行う時に

// WHERE句の付け忘れ

UPDATE users SET is_paid = false;

// ;の位置が早くてWHEREが入らないケース

UPDATE users SET is_paid = false; WHERE id = 100

// 改行されている && 1行目のみ選択されている && 「;」がなくても保管してくれるSQLエディタを利用したケース

UPDATE users SET is_paid = false
WHERE id = 100

 

条件

  • バイナリログは存在しないものとする😹
  • 作業前に関連テーブルのdumpは取得している

 

アラートをあげる

即座に社内共有して相談し、対応方針の合意を取る。
1人エンジニアなので、「どうでも良い!今すぐ復旧しろ!早く!」となるとは思う。

復旧する

1.メンテナンスを可能であれば入れる

メンテナンスが入った時点から書き込みは行われなくなる

2.全件更新のインシデント発生から、現在までに有料会員になっている会員を調べる

 

SELECT * FROM users WHERE is_paid = true;

存在した場合は別途テーブルを作成する

 

CREATE TABLE is_paid_after_incident_users LIKE users;

INSERT INTO is_paid_after_incident_users
SELECT * FROM users WHERE is_paid = true;

3.現時点の異常状態のDBをスナップショットを取得する

4. スナップショットから移行先DBを新規作成で複製

以降は移行先DBで作業を行います。

5.一時テーブルの作成

CREATE TABLE temp_users LIKE users;

6.users_202306031200.dumpファイルの書き換え

sed -e '/CREATE TABLE `users`/ s/`users`/`temp_users`/g' \
    -e '/INSERT INTO `users`/ s/`users`/`temp_users`/g' users_202306031200.dump > temp_backup.sql

usersをsedコマンドでtemp_usersに書き換えます。

7.エディタでtemp_backup.sql開いて更新がうまくいっているか確認

8.一時テーブルにインポート

mysql -u {username} -p{password} {database_name} < temp_backup.sql

9.一時テーブルからusersに上書き更新でリストア

UPDATE users
INNER JOIN temp_users ON users.user_id = temp_users.user_id
SET users.is_paid = temp_users.is_paid;

// 全件更新のインシデント発生から、現在までに有料会員になっている会員がいた場合の対応

UPDATE users
INNER JOIN is_paid_after_incident_users ON users.user_id = is_paid_after_incident_users.user_id
SET users.is_paid = is_paid_after_incident_users.is_paid;

10. DBの接続先を移行先DBにアプリケーションを書き換える

接続変更

 .env

- DB_HOST=db.example.local
+ DB_HOST=db.v2.example.local

 

感想

1.命綱の作業前スナップショット

この操作は作業前にテーブルのdumpを、誤操作する前に取っていたからできたこと。

UPDATE users SET is_paid = false; WHERE id = 100

手動でSQLでUPDATEやDELETEを行う場合には、関連のテーブルのdumpを取得しておくのは大事😺

もし1行の書き換えだからと、作業前のtableのdumpがなかったらこの復旧はできなかった🥶

2.TRANSACTIONを使っていれば確認できる

START TRANSACTION;

SELECT COUNT(*) FROM users
WHERE is_paid = true
AND id = 100;
-- 1

UPDATE users SET is_paid = false WHERE id = 100;
-- 反映された件数が1であること

-- 確認
SELECT COUNT(*) FROM users
WHERE is_paid = true
AND id = 100;
-- 期待する結果
-- 0
SELECT * FROM users WHERE id = 100;
-- 反映を確認


-- 異常時 | トランザクションをロールバック
ROLLBACK;

-- 正常時 | トランザクションをコミット
COMMIT;

もし期待する結果と異なる場合はROLLBACK; 期待する結果通りであればCOMMIT;で安全に実行できる。

3.能力高い

  • 冷や汗だらだらで、普段行わない操作を1人で冷静に短時間としれっと対処できた動画のエンジニアさんの能力が高い。
  • 簡単なSQLだからと甘くみずに、作業前にスナップショットを取得していていえらい!
    「たまたまdumpをとっていたので…」たまたま作業直前に取っていたなんてことはない。えらい!

4.ここはダメ!Badなニュースこそすぐに共有すること

しれっと対応してはダメ。クリティカルな状況になったら周りに共有すること。

  • 動画のエンジニアさんは、1時間で復旧できたので何事もなく午後を過ごしたと笑い話にしていたが(面白くするために冗談かも?)、悪いニュースほど関係者にすぐに共有しなくてはいけない。
  • 間違うこともいけないが、黙ってしれっと復旧させてはいけない。
  • 責任者が責任を持てなくなるし、作業者に任せられなくなるから。

5.決済系はログが欲しい

  • usersテーブルは状態でしかないので、決済の履歴テーブルが欲しい

6.レビューが欲しい。修羅場の1人判断は危険

冷静さを失っている場合がある。ビジネスサイドの怒号が飛んでいたり、電話が鳴り止まない状況もあり得る

音のない環境での作業が望ましい

 

怠惰を求めて勤勉に行き着く

事前に「どのような事態が起きうるか」「起きたときにどう対応するか」を様々な角度から想定し、あらゆる「準備」をしておく
@see https://kinacoinu.com/blog-laziness-to-diligently/

 

Amazonおすすめ

iPad 9世代 2021年最新作

iPad 9世代出たから買い替え。安いぞ!🐱 初めてならiPad。Kindleを外で見るならiPad mini。ほとんどの人には通常のiPadをおすすめします><

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)