Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

サービスを止めずに旧システムから新システムへデータ移行した話

こんにちは。Eight事業部の古本です。業務ではEight企業向けプレミアムサービスのサーバー側の開発・運用を担当しています。ひそかに次世代機はXBoxが気になっています。

Eight企業向けプレミアムサービスについて

今回のデータ移行の対象となった企業向けプレミアムですが、これはEightで培った個人の人脈を企業内のメンバーと共有することで、組織としても人脈を活用できるようにするサービスです。

サービスの詳細や背景について書かれた記事がありますので、こちらも御覧ください。
buildersbox.corp-sansan.com

この企業向けプレミアムですが、昨年の5月に新システムをリリースしました。
内部的な課題解決がメインなため、ユーザーに提供する機能自体は新システムでも変わりはありませんでしたが、内部のデータ構造が一新されたため大きな改修となりました。

新システムのリリース後も課金契約の絡みで課題が残っていたため、旧システムを止めずに稼働させていました。

今回の記事で触れるデータ移行は、旧システムの情報を新システムへ移行する話となります。
データ移行をどのように行い、そしてトラブルとどう向き合ったかを書きたいと思います。

データ移行を行う背景

新システムと旧システムの2つのシステムが稼働していた事により既存のユーザーは旧システムで、新規で契約して頂いたユーザーは新システムでサービスを稼働する状況が発生していました。

f:id:yudedako1026:20201220232205p:plain

システム運用に携わる者にとっては胃付近がざわつく状況ですが、これにより開発や運用において新旧のシステムを意識する必要があって通常よりコストが掛かる状態になっていました。

この問題をStripeから新しく提供された銀行振込機能を利用することで解決出来るようになりました。
本来ならこの機能を先に・・という話ですが、当時はPilot版だったので先にデータ移行を行いました。

Stripeの銀行振込の導入について書かれた記事がありますので、こちらも御覧ください。
buildersbox.corp-sansan.com

これにより旧システムを止める算段が付いたので、全ての契約ユーザーを新システムで稼働させるために旧システムで提供しているユーザーのデータを新システムへデータ移行を行いました。

f:id:yudedako1026:20201220234219p:plain

データ移行を行うにあたって

データ移行を行う場合はまず調査を行い、それからどう移行するかを考えることが多いかと思います。

しかし、今回の移行では先に サービスを止めずにデータ移行する という方針を決めてから開発を行うことにしました。
チームメンバーが全員新システムの開発に関わっていたので、システムの土地勘はある状態ではありましたが、この進め方は作業に大幅な変更が発生し得るリスクのある進め方だと思います。

それでも以下の理由でサービスを止めたくなかったので、この方針で進めることにしました。

  • サービスを止めるとなるとチームだけでなく営業側や他チームなど幅広く影響が出てしまう
  • 支払い方法別に複数回に分けて移行を実施する可能性があった
  • メンバーの大半が健康診断でバリウムか胃カメラを飲んでるベテラン揃いなので単に夜勤がつらい

後に触れますが、初期段階でサービスを止めずにデータ移行するを決めたことは今回の移行作業において大きな意味があったと思います。

フェーズ分けの話

サービスを止めずに移行を行うという方針が決まっているので、それをどうやって実現するかという視点で移行方法を考えました。

Q. サービスを止めずに移行するためには?
→ ユーザーに影響の出る移行作業時間を極めてゼロに近づける必要がある

Q. 移行作業時間を減らすためには?
→ 新システムに予め必要なデータを移行しておき、本番では最低限の移行で済むようにする

Q. 予めデータを移行するためには?
→ 過去分については、事前に移行しておく
→ 更新性のあるデータについては、新旧両方のシステムにデータを保存する並行書き込みを行うようにする

このような議論を経て事前移行と並行書き込みを実施することで、移行本番ではほぼ切り替えだけで済む状態を目指して移行を進める事にしました。

また並行書き込みや過去分の事前移行は一度に行う事は難しいので、段階に分けて実行することもこの段階で決めました。
そのために移行をフェーズを分けて開発を行い、フェーズ間でテストも行うことにしました。

フェーズ1(事前準備)

旧システムと新システムは展開するサービスは同じでも内部のデータ構造は分離して存在しています。
そのため新旧のデータを関連付けるためのマッピングが必要となり、同時に新システム側にも旧システムデータの受け口となるデータを用意する必要がありました。

f:id:yudedako1026:20201220234926p:plain

ただし、マッピングテーブルのレコードはレコードが存在する=平行書き込み処理が開始されていると置きたかったのでこのフェーズで作成を行いませんでした。

上記のデータを用意、もしくは作成するためのロジックを実装するフェーズをフェーズ1としました。

フェーズ2(事前移行処理の開発、およびその実施)

今回の移行のキモとなる工程で、事前移行が可能なデータの移行を行う処理の実装、およびその実施をフェーズ2としました。

f:id:yudedako1026:20201220235601p:plain

事前移行には過去分の移行平行書き込みの2つがあります。

  • 過去分の移行は、処理の実施時点で旧システムに存在しているデータを新システムに移行させる処理です。
  • 並行書き込みは、旧システムでデータ更新があった場合に新システム側へそれを同期させる処理です。

それぞれは処理的に独立しており、仕様も複雑なのでそれぞれの対応ごとにQAテストを実施しました。
リリースも分割して行っているので実質フェーズ2−1,フェーズ2−2のように分かれていたと思います。

工夫したポイントとしては、並行書き込み・過去分の移行でも同じデータに対して書き込みが行われたとしても同じ結果になるようにロジックを工夫しました。
そのため並行書き込み時は単純に作成・更新をせずにデータが無ければ作成をするように実装しました。

これにより過去分の移行が並行書き込みより遅れて実施されても、平行書き込みで書かれたデータを正としデータの冪等を保つことが出来ました。

それでもタイミング次第でデータに不整合が出る可能性があったので、そこは整合性をチェックするクエリを作成し、それをRedashのAlert機能で定期的に監視するようにしました。

フェーズ3(本番切り替え)

本番の移行実施時に行う処理の実装、およびその実施をフェーズ3としました。

本番切り替え処理と事前移行が難しいデータの移行処理の開発を行い、本番切り替えと同時に事前移行を行えなかったデータの移行処理を実施しました。
この間、時間にして数秒程度ですが機能にアクセスされるとエラーになる可能性があったので簡単なメンテナンス画面も用意し切り替えも行いました。

これ以降、利用ユーザーに対して新システムが表示されるようになり、見える形での移行が完了することになります。

フェーズ4(旧システムのデータ削除)

移行は無事に完了しましたが、旧システムのデータがまだ残っています。
その削除を行うための実装、およびその実施をフェーズ4としました。

万が一に備える意味も込めて残して新システムに移行したデータの動きを見てましたが、問題無いので今後削除予定です。

トラブルにどう立ち向かったか

結果的にはデータ移行は成功したと言えますが、順風満帆とは言えず数々のトラブルが起きました。
ここではそういった出来事にどのように向き合い、対処していったかを書きたいと思います。

並行書き込みで起きる差分問題

フェーズ2で用意したデータの整合性をチェックするアラートが、1日に数件程度の頻度で鳴ってしまいました。

原因として非同期で動いているbatch処理が怪しい・・という所までは絞れましたが、完全に判明はしませんでした。

原因を特定して穴を塞げればベストでしたが、

  • タイミング起因の同期漏れなので、改めて並行書き込み処理を実施する事で解決すること
  • 再現性の難しい箇所なので調査・修正コストが高く付きそうなこと
  • 発生頻度が全体の母数から見ると極少数であること(0.01%未満)

以上を考慮し温かみのある人の手対応で切り抜けることにしました。

共有名刺情報の移行に時間がかかりすぎる問題

共有名刺情報は、企業向けプレミアムでメンバー間で名刺情報を共有するために用意された専用のデータです。
サービスの拡大により共有された名刺情報も増えてきた事で移行時点でかなりのデータサイズになっていました。

今回の移行処理は既存のビジネスロジックを可能な限り利用するという方針で開発を進めていましたが、データ数の多い共有名刺情報では検索エンジンへ書き込みを行う処理、正確には前後の処理を含むbatch処理が時間が掛かりすぎる事がわかりました。

その時間は概算で10日ほど、しかも既存の書き込み処理も同時に走っているので下手をすれば移行対象でないユーザーへの処理に影響が出る可能性がありました。

ここに関しては、既存の処理を使わないbatch処理を新たに作り、その中で一括で登録させるようにして回避するようにしました。

f:id:yudedako1026:20201221000348p:plain

上記は実際に移行用に用意したソースコードですが、既存の処理ではRDBと検索エンジンの状態を同期させるロジックになってました。
そこから今回の移行のために一括登録の部分だけを切り出し、batchで処理するようにしました。

これによりデータの不整合が発生するリスクもありましたが、新たなチェッククエリを用意して監視する事にしました。

この対応により、10日程度と見積もられた共有名刺情報の移行時間を2日弱まで圧縮する事が出来ました。

特定移行ユーザーの契約開始日時問題

これまで企業向けプレミアムでは決済管理にStripeを利用しており、それは新システムになっても変わりはありません。

しかし、旧システムには請求書支払いというStripeを利用しない支払い方法が存在しました。
このケースが先に述べた銀行振込機能によって移行出来るようになったケースとなるのですが、ここで初めてStripe上に登録されるようになります。

Stripeの契約開始日時は登録した日時が基準となってしまいますが、該当ユーザーはその前から契約されているので契約日時もその時の日付にならなければなりませんでした。
そこの考慮が漏れていたため、契約開始日時が移行した時点の日付になってしまい、それが元で契約更新時に問題が起きてしまいました。

幸いにも銀行振込の場合は請求書発行から支払いまで期間があったため、影響が出る前に対処する事が出来ました。
そのため大きな問題にはなりませんでしたが、今回の移行でもっともヒヤッとした場面でした。

まとめ

多少想定とは違う出来事はありましたが、移行そのものはサービスを止めずに無事に旧システムのユーザーを新システムに移行出来たので成功だったと思います。

今回上手く行った要因を振り返ってみると、設計前にサービスを止めずに移行するという方針を決めたことが大きかったと思います。

もちろん今振り返ってもリスクのある進め方だったとは思いますが、最初にサービスを止めないという方針を決めたので、以後の設計、タスク出しにおいてメンバー全員がサービスを止めないためにはどうしたらいいか?という視点を持って開発を行うことが出来たと思います。

また、移行のフェーズを分けてQAテストや移行のための機能リリースを分割して行えた事も大きかったと思います。
当初の見積もりではテストに1週間はかかりそうだと言われてましたが、分割することで1,2日程度に抑えられ、また問題の早期発見にも繋がりました。

分割リリースで最も怖かったのは機能のデグレでしたが、そこではmablを利用したデグレチェックが役に立ちました。

mablついて書かれた記事がありますので、こちらも御覧ください。
buildersbox.corp-sansan.com


この手の移行作業は、実施日が近づくたびに忙しくなる印象がありましたが、今回は実施日が近づくにつれてヒマになるゆとりが生まれるという変わった移行作業になりました。

もちろん移行作業は、データボリューム、業種、期間、コストなどといった様々な要因でベストプラクティスは変わると思いますが、今回の事例が少しでもお役に立てれば幸いです。

© Sansan, Inc.