🤕

本番DBのマスターデータを全行ぶっとばすやらかしをしたときのお話、その反省

2024/11/26に公開
2

はじめに

はじめまして、さかがみ かずと(@_skgm092)です。
今回は自分がお手伝いしているプロジェクトで、本番DBへのアクセス作業中に発生したトラブルについて記録します。
自分の失敗を公開することは恥ずかしいものですが、同じような事故を防ぐための参考になればと思い、共有することにしました。
自分のしかばねを糧にして、皆様は同じ失敗をしないようにしてください。

トラブルの概要

起きてしまったこと

担当プロジェクトはコアタイムがPM10-AM03頃のSNSサービスでした。
サービスの主要機能が完全に機能停止する事態が発生し、コアタイムの大部分を緊急メンテナンスで停止せざるを得ない状況となりました。

どんな作業で発生したか

マスタデータを含むテーブルの列を書き換える作業中に発生しました。
具体的には、とあるマスタデータのJSON型の列を全てブランク値で上書きしてしまいました。

何が問題だったか

作業用に用意したスクリプトの誤作動が原因でした。
UPDATE hoge SET col = "";のようなクエリが発行され、データが失われました。
過去に同様のスクリプトで作業を実施した実績があったため、DEV環境での動作確認を省略してしまっていました。

復旧策

本事象はRDSで管理されているMySQLで発生しました。RDSではスナップショットを取得していたため、以下の手順で復旧を行いました:

  • スナップショットから新規インスタンスを立ち上げ
  • 新規インスタンスからマスターデータを取得
  • 取得したマスターデータを本番DBに再インサート

これにより、システムを復旧することができました。
ちなみにスナップショットからのインスタンス立ち上げはこちらの資料を参考に実施しました。

反省と振り返り

作業前にバックアップを取得するべきだった

今回はスナップショットからの復元で対応しました。
しかしデータ容量の大きいDBであったため、インスタンス立ち上げから修正完了まで1時間近くを要しました。
より迅速な復旧のために、以下のような方法でバックアップを取得しておくべきでした:

  • Sequel ACEのEXPORT機能によるテーブルレコードのエクスポート
  • 対象データのCSV形式でのバックアップ(Copy Insert SQL機能の活用)

Update文によるバックアップではデータの加工が必要となります。
その際はCopy As CSV機能を活用し、再加工が可能な形でバックアップを取得するのを推奨します。
またレコード数が多くデータ容量が大きい場合、クリップボードに乗らないこともあり得るため、その場合はLIMIT, OFFSETを使って何度かに分けてバックアップを取得します。

Sequel ACEでのレコードエクスポート


Copy Insert SQL

トランザクションを貼ってから作業するべきだった

MySQLではSTART TRANSACTIONを使用することで、以降のクエリをCOMMITで確定させるか、ROLLBACKでキャンセルすることができます。
トランザクション内ではSELECT句も実行できるため、以下のような安全な作業フローを構築すべきでした:

START TRANSACTION;

// アップデート実施
UPDATE hoge;

// クエリ結果の確認
SELECT hoge;

-- 問題がなければコミット、問題があればロールバック
-- COMMIT;
-- または
-- ROLLBACK;

このように更新後の結果を確認してから変更を確定させることで、データの整合性を確認することができます。

作業スクリプトからのクエリ発行は行わない

今回の作業では、Pythonスクリプトを作成し、スクリプト内で直接更新SQLを発行する実装を行っていました。
このような実装は作業速度が速いという利点がありますが、今回のように問題が発生した際の影響が甚大になるため、避けるべきです。
どうしても作業スクリプトが必要な場合(例:PKを使った複数列のアップデートなど)は、以下のような運用とすべきです:

  • スクリプトではクエリの生成のみを行う
  • クエリの実行は必ず手作業で行う

DEV動作確認を絶対に行う

今回の作業では、過去に同じスクリプトで正常に動作した実績があったため、動作確認を省略してしまいました。
また、作業を早めに完了させたいという時間的プレッシャーもあり、確認作業を省略する判断をしてしまいました。
しかし、今回の事故の重大さを考えると、まさに「急がば回れ」という教訓となりました。
作業の緊急性に関わらず、DEV環境での動作確認は必ず実施すべきです。

最後に

今回のトラブルにより、関係者の皆様、そしてユーザー様に多大なご迷惑をおかけしましたことを、心よりお詫び申し上げます。
深夜にも関わらず、トラブル対応に付き添い、励ましていただいたチームメンバーの皆様には深く感謝しています。
エンジニアとしての5年間、大きなトラブルを起こすことなく過ごしてきましたが、今回初めて重大なミスを経験しました。この経験から、油断や慣れがミスを引き起こす大きな要因になることを痛感しました。
今後は凡事徹底を心がけ、安全かつ効率的な作業を行えるエンジニアを目指していきます。

この経験が、皆様の教訓として少しでもお役に立てば幸いです。

Discussion

takezoux2takezoux2

スクリプトをgit管理して、PullRequestを通すことでダブルチェックできる体制にはなっていましたか?
どうしてもスクリプトで実行のような状況は発生することあると思うので、次善の策として適切なダブルチェック体制を整えるのも対策としてあると良いと思いました。

さかがみ かずとさかがみ かずと

コメントありがとうございます。

おっしゃる通りですね。

今回はスクリプトの事前レビューは行なっていましたが、
スクリプト実行中に不適切なSQLが発行されたことによって発生してしまいました。

そのため
スクリプトの程度にもよると思いますが、更新・削除系のデータメンテナンスではスクリプト自体からのクエリ発行はやめておくのがよいかなと痛感しました。