はじめましてこんにちは。SREの@masartzです。
私は最近joinしたのですが、今回は本番環境に古くからあるテーブルの掃除作業をした案件をご紹介します。
tl;dr;
- 本番の住所情報テーブルを消したけど問題なかった話
絶対要らないハズだけど、なかなか削除できずにいるもの
を対処する話
本番環境の住所情報テーブルをdropするまでの作業
今回、本番環境の住所情報テーブルをdropしました。
と言っても、事故でもうっかりでもなく、既に使われていなかったものの整理という作業でした。
何故使われていなかったかというのは、メルカリの住所情報の保持の仕方の変遷が関係しています。
初期にはuser情報と住所情報は1対1の関係でした。イメージとしては以下です。
CREATE TABLE IF NOT EXISTS users ( id INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, ..(略)..
CREATE TABLE IF NOT EXISTS users_address ( user_id INT UNSIGNED NOT NULL, zipcode VARCHAR(255) NOT NULL, ..(略).. PRIMARY KEY (user_id) ..(略)..
この設計でサービス運用をしていましたが、あるとき機能追加の要件が発生します。
配送先住所を複数登録するというものでした。
この機能を実現するために、user情報と1対多の関係にある新たな住所情報テーブルが必要になりました。
以下のようなイメージです。
CREATE TABLE IF NOT EXISTS users_addresses ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, user_id INT UNSIGNED NOT NULL, is_default ENUM('yes', 'no') NOT NULL DEFAULT 'no', zipcode VARCHAR(255) NOT NULL, ..(略).. PRIMARY KEY (id) ..(略)..
かくして新テーブルが作成され、旧住所テーブルのデータも新テーブルに移行されました。
めでたしめでたし、、というだけでは終わりませんでした。
移行から期間が経過して
移行から年月が経ち、社内のITSで「新旧テーブルを整理する」というチケットだけが残タスクとしてある状況が続きました。私が入社直後、とっつきやすい作業はないかと未完了チケットを洗っていたところ、この課題に辿り着きました。そのまま何気ない気持ちで自分にアサインしたところ、以下の作業がありました。
一部の社内向けAPIに旧テーブルを参照してレスポンスを返している処理が残っていた
これは該当のAPIが移行当時まだ本格的に使用されていなかったため、うっかりそのままになってしまっていました。
現在ではもちろんちゃんと使われており、修正が必要でした。
これはコンポーネント内をgrepしてすぐに見つけることができたため、APIを改修するPRを作って事なきを得ました。
別コンポーネントから直接旧テーブルを参照している処理が残っていた
メルカリのコンポーネント間のデータフローは、ユーザー向けのサービス部分では
クライアントアプリからリクエストを受けるサーバーサイドAPIからDBにアクセスする一般的な形式をとっています。
そして、それと同様、社内のサブシステム間もAPI経由でDBとやりとりしています。
しかし、一部の社内サブシステムにおいては直接DBと通信する処理があり、今回はその処理で旧テーブルを参照していました。
これは機械的なgrepで検知が漏れてしまい、社内への共有によって発見できたため、こちらもPRを送って解決できました。
テーブルの削除作業とその周知
ここまで来てようやく、参照処理がなくなりました。
ここで「旧テーブルをなくします」と周知を行い、結果として実行したSQL文は以下でした。
ALTER TABLE users_address RENAME TO no_more_use_users_address;
念には念を入れて、です。こうすることによって、全く予期せぬどこかの処理が
旧テーブルを参照していた場合に、それはキッチリエラーになるハズです。
仮にそうなった場合、それが急を要するものであれば再び元の名前にRENAMEすることで復旧可能ですし、時間があるならば他と同様に参照先を新テーブルに向けるよう処理を修正すれば良いわけです。
本当の本当に削除
結果的に参照箇所が見つかることはありませんでした。どれくらいの期間をかけて「ありませんでした」と判断したかで言うと1ヶ月くらいです。
これは、急いで削除するものでもない(それ自体に大きなメリットはない)のと、
予期せぬ処理がなんらかのバッチ処理の可能性もあります。そう考えると月を一度くらいは跨いでおきたい、というのがあり、それを越えての判断です。(月初に動くバッチとかよくありますよね?)
今度こそ以下のSQL文を発行して作業完了です。
DROP TABLE no_more_use_users_address;
今回の事例から
今回の事例にはいくつかポイントがあります。
(1)使っていないハズ、でも実はそうではないかもしれない
(2)DBへのアクセス方法としてコンポーネント経由/直に接続などの複数パターンがある
(3)user情報と住所情報の設計方法
(1)今回は、当初は本格的に使われていなかったAPIがいつの間にかしっかり使われるものになっていた、という状況でしたが、さらに新たに参照する処理が生まれることもあり得ました。いずれにしても使わなくしたから大丈夫、というのはある一時の状態ですので、存在しつづける限り新規利用をモグラたたきしなければならず、それはコストとリスクになってしまうでしょう。
今回も実際に消そうと試みて初めて 実は使ってた
ことに気づけたため、やってみて良かったと思っています。
(2)については、全く問題なく、そうあるべきと考えます。
1人のユーザーの動作に紐づく処理が多いサーバーサイドAPIのインターフェースと比べて、特にCS管理画面などの社内ツールはデータの集合を扱う事が多くなります。そのような異なるデータ取得をするために直接接続するのは自然なことです。
それら全てを1人で把握して作業するのはやはりなかなか難しく、共有が欠かせないというのが教訓です。今回は関係者の方々がメルカリのvalueでもある All for one
の力を発揮され、各所で助けていただきました。
(3) については、「そもそも最初から1対多のデータ構造にしておけば、こんな歴史的経緯はなかったのでは」という視点があるかもしれませんが、私はそうは思いません。
歴史的経緯はサービスの発展の証です。
MVP(Minimum Viable Product)で考えれば、1対1の住所情報で十分ですし、なんならuser情報のテーブル内に住所情報カラムを持つ設計もあり得たと思います。
最初は一つの住所で済んでいたのが、複数住所が必要になったのはそれだけサービスが発展したということです。
私のように後から入った人間が、現状だけを見て「これはおかしい、負債だ」と言うべきではありません。
その上で、今後もまた次の発展していくために、現時点で解消できる課題は対応しておくことに価値があると考えます。
まとめ
絶対要らないハズだけど、なかなか削除できずにいるもの
はどこの環境にも割とあると思います。
今回の住所情報など元々重要だったデータは特にそうなりがちです。今回も不要とわかっていてもビビリながらの対応だったのですが、それでも着手した根拠として仮に保持し続けた場合のデメリットを考慮しました。
- 不要な情報に対する新メンバーの学習コスト&既存メンバーの教育コストの無駄
- 使われないデータを保持しつづけるDBの保持コストの無駄
- 新規に誤った参照をする処理が生まれてしまうリスク
これらのデメリットと処理作業のコストとのトレードオフで実際に対応するかを判断すべきかと思います。
今回のケースは、私を含めメンバーが増えている環境であり、住所という事業にとって主要な情報の性質からよく目につく、あるいはうっかり使われるリスクがあり、といった事を考慮し対応しました。
逆に言うならば、特定のメンバーしか関与しないものや、限定的なデータであれば放置しておいても問題ないでしょう。例えばトライアルな機能に対して、その都度潔癖に対応していては事業の成長スピードを阻害することにもなりかねません。どう扱うべきかはケースバイケースだと思います。
告知
メルカリのSREチームは、2017/06/07(水)にDrink Meetupを計画しております。
今回のような細かい作業だけではなく、よりスケールがある様々なテーマにご興味のある方からのご応募お待ちしております。
詳細は以下のリンク先を御覧ください。
また当日ご都合がつかないというような場合はこちらから個別相談を受け付けております。