【Cloud Spanner】無停止で安全に漸進的にDatastoreからSpannerへの移行を行う
はじめに
こんにちは! テラーノベルでサーバーサイドを担当している@yuhasです。
私は2023年3月にテラーノベルにJOINいたしまして、最初のタスクの中で、GCPのDatastoreからSpannerへの一部テーブル移行を行いました。その移行は過渡期にあり、その中の一部を担当したという感じになります。
(ここでは詳細は省略しますが)サーバーサイドで使用しているGo言語をバージョンアップするとタイムアウトが頻出してしまう問題があり、その原因がDatastoreの一部テーブルへの大量の読み込みにありそうということを突き止めました。そこで、(Spanner移行が全体の方向性であったこともあり、)そのDataStoreテーブルを優先的にSpannerへ移行しようということになりました。
私自身としては、もうすでに移行作業が行われてきたものに沿ったという形にはなりますが、DatastoreからSpannerへの移行についてご紹介できればと思います!
移行のモチベーション
Datastoreはデータの構造化に対してあまり強くないので、クエリがやや弱く、一部の単純なフィルタをかけるようなクエリでも全文検索エンジンを利用していたという事情がありました。
その分を全文検索エンジンから移行先のSpannerに移すことで、コストカットが見込めました(全文検索エンジンのクエリは高コストなので)。
また、テラーノベルではBigQueryを用いたデータ分析を重視しており、テーブルのBigQueryへのエクスポートも必要です。DatastoreからBigQueryへのエクスポートはレコード数に応じた従量課金ですが、SpannerからBigQueryへのエクスポートはPU(Processing Unit)課金なので、その点でもコストカットが見込めました。
どのように移行したのか
全体としてまずは、バックアップ・リストアの確認、スキーマ変更時の挙動の確認、想定されるクエリ数をさばけるインスタンス数の確認、全文検索エンジンで行っていたクエリが実現できるかの検証、等を行っていました。
そして、テーブルを1つずつDatastoreからSpannerに移行していくという方針でした。
小さいタスク(リリース)として分けられて漸進的に進められるということで、安全でタスク割り振りもしやすいやり方でした!
スキーマを決める
そしてここから私が実際に作業していったものですが、個別テーブルの移行に関してです。
Datastoreで定義したものをベースにそのままで移行したものもありますが、このタイミングでリファクタリングを行えないかどうかをまず考えます(テーブルを分割したりなど)。今回はしませんでした。
決めたスキーマの通りに、Spanner用のテスト環境にテーブルを作りました(スキーマ定義はイメージです)。
CREATE TABLE Test (
UserID STRING(255) NOT NULL,
Platforms ARRAY<STRING(255)> NOT NULL,
) PRIMARY KEY(UserID);
次に、dataflowを利用して、本番環境のDatastoreにあるデータを先ほど作成したSpannerのテーブルにコピーしました。
これで本番データを取り込んだSpannerのテーブルが使えるようになりましたので、パフォーマンステストを行いました。
実際に使われるクエリを一つ一つ書いて実行し、10ms以下で返ってくることを目指していました。それより遅くなってしまうようであれば、インデックスの作成を行いました。
この段階でスキーマと実行するクエリを決めることができました。
コードを実装する(書き込み)
1テーブルのSpannerへの移行を、書き込みと読み込みに分けて行っていました。
まずは書き込み対応をリリースし、Datastoreに書き込んでいるところで同時にSpannerにも書き込むようにします。ここで、DatastoreとSpannerの当該テーブルのデータが一致するようになります。
書き込み対応に問題がないことが確認できれば、読み込みをDatastoreからSpannerに切り替えるリリースも行います。これで安全に移行することができます。
コードの実装にあたって、Spannerのクエリを書く必要があります。単純なクエリを利用するのにわざわざクエリを書くのは大変ですので、コード自動生成のyoを利用しました。
これで対応できない複雑なクエリは生のクエリ文を書くという対応をしています。CI環境のspannerについては、spanner-emulatorを利用しました。
書き込み対応をリリースすると、「リリース後」の分についてはDatastoreとSpannerの当該テーブルのデータが一致します。
DatastoreからSpannerに「リリース前」のデータを(dataflowを利用して)コピーしてくることで、Datastoreの当該テーブルの全てのデータが漏れなくSpannerの上に載ってくることになります。
コードを実装する(読み込み)
書き込み対応のリリースでDatastoreとSpannerのデータが一致するようになるので、読み込みをDatastoreからSpannerに切り替えることができます。
まとめ
1テーブルずつ、書き込みと読み込みに分けてリリースしていくことで、タスク(リリース)を細分化しながら無停止で安全に漸進的にDatastoreからSpannerへの移行を行うことができました。
また、機械的に移行することを考えるのではなく、リファクタリングするという考えを持って移行することができるのも良かったです。
Discussion