新卒エンジニアがデータマイグレーションに挑戦して学んだこと

こんにちは。メルカリアドベントカレンダー 2020 13日目担当のメルカリBackendチーム/Software Engineerの森本望です。 皆さんご存知かもしれませんが、メルカリではモノリシックなAPI (以下モノリス)からマイクロサービスアーキテクチャへ移行するプロジェクトが進んでいます。

メルカリのマイクロサービス移行の進捗 (2019年冬)

私達のチームではメルカリのすべての商品を管理するitemサービスを開発、運用しています。アプリケーションのマイクロサービス化はしているものの、歴史的な経緯により利用しているデータベースは他のサービス(特にモノリス)からアクセスされています。

上記の図のように、Itemサービスが利用しているデータベースは複数のサービスに共有された状態になっています。これだとスキーマの変更を行う際の影響範囲が大きく、すべてのサービスを確認し修正しなければなりません。この状態は一般的にマイクロサービスにおけるアンチパターンである 共有データベース の状態です。

これを解消するため、私達は新しいItemサービス専用のデータベースを構築しようとしています。ですが、メルカリの商品データは過去の膨大な蓄積があり非常に大きいため、移行作業に大変な手間と時間がかかります。 そこで、まずは準備フェーズとしてデータベースのスキーマをシンプルにし移行作業を簡単にすることにしました。今回はその変更の一つである商品画像のデータ周りのスキーマ変更で得られた知見について解説します。

移行前の状況

移行前の商品画像データ関連のスキーマには、2点の問題がありました。 それは

  1. 画像データの更新日時の保存先が複数に分かれていること
  2. 不完全なデータが存在すること

です。

画像データの更新日時の保存先が複数に分かれていること

メルカリでは元々、1つの商品につき4枚の画像を登録することが可能でした。画像データの更新日時が items というテーブルに保存されていました。一部のカラムは省略していますが、次のようなスキーマになっています。

2018年12月に、メルカリは1つの商品に登録できる画像を4枚から10枚に増やす対応を行いました。 【機能改善のお知らせ】商品出品時に画像を最大10枚登録できるようになりました!

この対応によりメルカリの画像データは大幅に増えると予想されたため、新しく増えるデータは item_photos という新しいテーブルを作成し、正規化しました。この時は急いで機能を作らなくてはならない状況だったらしく、データマイグレーションが行われないまま実装が行われました。 つまり2018年12月以前に出品された商品画像のデータは items テーブルに、それ以降に出品された商品画像のデータは item_photos テーブルに保存されるようになったため、画像を取得するためのロジックが複雑になってしまいました。

不完全なデータが存在すること

items テーブルには画像の順番に関するデータも存在していましたが、歴史的な事情で不完全なデータも存在していました。

そのため、APIが画像データを返す際に、抜けているデータはスキップするような実装になっていました。データが不完全であることを把握しておく必要があることは開発者にとって負担となっていました。

修正プラン これらの問題を解決するためにスキーマを以下のように変更し、2018年12月以前のデータをitem_photosテーブルへ移動し、不完全なデータをなくす方針にしました。

そうすることで、アプリケーションからは複雑なロジックがなくなります。 またこの対応の時点でitemsテーブルとitem_photosテーブルへのダブルライトも行っていました。

移行するために行っていること

移行するために3段階のステップを考えました。

  1. データを移行する
  2. itemsテーブルの旧カラムを参照している処理を消す
  3. ダブルライトをやめる です。

データを移行する

まず、2018年12月以前の商品をitemsテーブルからitem_photosテーブルにデータをすべて移し、また不完全なデータも前詰めして治すスクリプトを作りました。 このスクリプトは対象件数が多いのと、本番で稼働しているデータベースに対して行うため1つの商品IDごとにトランザクション処理を行うことで、ロックの保持期間を短くし、他との競合を起こりにくくすることで影響が少なくなるように作成しました。

メルカリにおける累計出品数は2020年1月時点で15億品以上あります。すべてのデータを移行するのは無駄があるかもしれないと考え、なるべく移行作業を簡素化しようと対象データを絞れないかと考えました。 例えば、メルカリでは出品した商品の削除が可能です。削除された商品の画像データがどこからも参照されないとしたら、それらのデータは安全に無視してデータの移行をスキップすることができるはずです。 この調査はitemsテーブルのデータがすべて反映されているBigQueryを使って行い、メルカリ内で有効な状態の商品のみデータを移行すれば十分であると思いました。これで、有効なデータに絞って移行作業を行うことができました。 しかし、参照を削除する変更をリリースしようとした際に想定していなく移行していなかった状態のデータにリクエストがきてエラーが起きたり、itemサービスを利用している他のマイクロサービスを使っているチームから問い合わせがあったりしました。 結果的に想定していたデータの範囲では足らなかったため、すべてのデータを移行しなければならなかったことがわかりました。 結局調査及び移行作業のやり直しをしなければならず、ここだけで3~4ヶ月ほどかかってしまいました。

itemsテーブルの旧カラムを参照している処理を消す

次に、itemsテーブルのphoto_[2-4]_updatedのカラムに対する参照を消していく作業に入りました。 GitHubのorganization内のコードを調査してみると、私達が管理しているマイクロサービスだけでなく、モノリスのアプリケーションや、カスタマーサポート用ツール等の内製ツールを含めた多くのサービスがこのカラムを利用していることがわかりました。それぞれを管理しているチームに掛け合ったり、プルリクエストを送るなどして、依存をなくしていきました。

ダブルライトをやめる

items テーブルのphoto_[2-4]_updatedのカラムへの参照が上記の変更でなくなったため、対象カラムの書き込みを消していきます。 書き込みをしているアプリケーションを調べたところ、モノリスとitemサービスのみだったので、その2つのアプリケーションからitemsテーブルのphoto_[2-4]_updatedのカラムへの書き込みを止めていく作業を現在進めているところです。 ダブルライトを止めた後にはカラムを消す予定なのですが、大量のデータがあるためカラムを消す操作で数週間かかってしまいそうな見込みなため、現在未定です。

勉強になったこと

このタスクをやる際にどのようなアプリケーションからリクエストが来ているか、どのような状態のデータに対して読み取りのリクエストが来ているかなどの調査をログをちゃんとみたり、実際に使ってるサービスを調べたりして綿密に行う必要性を実感しました。そうすることでデータ移行バッチの実行途中に想定していないパターンに出会ってしまうことや、それに伴う修正のやり直しの作業がもっと少なくできたはずです。 また、新しく機能を開発する際に、不完全なデータを直していくことや、データマイグレーションを同時に行うように開発をすすめると技術的負債を溜め込みにくくなるのかなと勉強になりました。 今回の学びを今後のマイグレーションや、機能の修正をする際に活かしていきたいです。

終わりに

最後までお読み頂きありがとうございました。 明日のMercari Advent Calendar 2020の執筆担当は、catatsuyさんです。引き続きお楽しみください。