MySQL から Cloud Spanner への移行

この記事では、オンライン トランザクション処理(OLTP)データベースを MySQL から Cloud Spanner に移行する方法について説明します。

移行の制約

Spanner では、特定のコンセプトの扱いが他のエンタープライズ データベース管理ツールとは異なるため、その機能を最大限に活用するにはアプリケーションのアーキテクチャを調整する必要がある場合があります。ニーズを満たすために、Spanner を Google Cloud の他のサービスで補完する必要がある場合もあります。

ストアド プロシージャとトリガー

Spanner はデータベース レベルでのユーザーコードの実行をサポートしていないため、移行の一環として、データベースのストアド プロシージャとトリガーによって実装されたビジネス ロジックをアプリケーションに移す必要があります。

シーケンス

Spanner ではシーケンス ジェネレータは実装されません。次に説明するように、Spanner では通常、単調に増加する数を主キーとして使用することはありません。その代わりに、ランダムな UUID を使用して一意の主キーを生成できます。

Cloud Spanner の外部でシーケンスが必要とされる場合は、それらをアプリケーション レイヤに実装する必要があります。

アクセス制御

Spanner は、Identity and Access Management(IAM)のアクセス権限とアクセスロールを使用したデータベース レベルのアクセス制御のみをサポートしています。データベースの読み取り / 書き込み権限、または読み取り専用権限を付与できるロールが事前定義されています。よりきめ細かな権限を必要とする場合は、それらをアプリケーション レイヤに実装する必要があります。通常、データベースの読み取り / 書き込み権限を付与する必要があるのは、アプリケーションのみです。

データベースをレポート用にユーザーに公開する必要があり、きめ細かなセキュリティ権限(テーブル / ビューレベルの権限など)を使用する場合は、データベースを BigQuery にエクスポートすることをおすすめします。

データ検証制約

Spanner は、データベース レイヤで一部のデータ検証制約をサポートしています。より複雑なデータ制約が必要な場合は、それらをアプリケーション レイヤに実装する必要があります。

次の表に、MySQL データベースで一般的に見られる制約の種類、およびそれらを Spanner で実装する方法を示します。

制約 Spanner での実装
null 以外 NOT NULL 列の制約
一意 UNIQUE 制約付きのセカンダリ インデックス
Foreign key(通常のテーブル用) 外部キー関係の作成と管理をご覧ください。
外部キーの ON DELETE/ON UPDATE アクション インターリーブされたテーブルでのみ使用可能で、それ以外の場合はアプリケーション レイヤに実装
CHECK 制約またはトリガーによる値の確認と検証 アプリケーション レイヤに実装

サポートされているデータ型

MySQL と Spanner はそれぞれ異なるデータ型のセットをサポートしています。次の表に、MySQL のデータ型とそれに対応する Spanner のデータ型を示します。Spanner の各データ型の定義の詳細については、データ型をご覧ください。

MySQL のデータを Spanner データベースに適合させるために、備考列の説明に沿って、データに対して追加の変換を行う必要がある場合があります。たとえば、大きな BLOB をデータベースではなく Cloud Storage バケットにオブジェクトとして保存してから、Cloud Storage オブジェクトへの URI 参照を STRING としてデータベースに保存できます。

MySQL のデータ型 Spanner の対応するデータ型 備考
INTEGERINTBIGINT MEDIUMINTSMALLINT INT64
TINYINTBOOLBOOLEAN BOOLINT64 TINYINT(1) 値は、ブール値「true」(ゼロ以外)または「false」(0)を表すために使用されます。
FLOATDOUBLE FLOAT64
DECIMALNUMERIC FLOAT64INT64STRING NUMERIC データ型は最大 65 桁の精度をサポートしているのに対し、Spanner の FLOAT64 データ型は最大 16 桁の精度をサポートしています。
代替メカニズムについては、任意精度の数値データの保存をご覧ください。
BIT BYTES
DATE DATE Spanner と MySQL は、どちらも日付に「yyyy-mm-dd」形式を使用するため、変換する必要がありません。日付を書式設定済み文字列に変換するための SQL 関数が提供されています。
DATETIMETIMESTAMP TIMESTAMP Spanner では時間はタイムゾーンから切り離して保存されます。タイムゾーンを保存する必要がある場合は、個別の STRING 列を使用する必要があります。タイムスタンプをタイムゾーン付きの書式設定済み文字列に変換するための SQL 関数が提供されています。
CHARVARCHAR STRING 注: Spanner では全体的に Unicode 文字列が使用されます。
VARCHAR では最大 65,535 バイトがサポートされ、Spanner では最大 2,621,440 文字がサポートされます。
BINARYVARBINARYBLOBTINYBLOB BYTES 小さなオブジェクト(10 MiB 未満)は BYTES として保存できます。より大きなオブジェクトを保存する際には、Cloud Storage などの代替 Google Cloud サービスの使用を検討してください。
TEXTTINYTEXTENUM STRING 小さな TEXT 値(10 MiB 未満)は STRING として保存できます。より大きな TEXT 値をサポートするには、Cloud Storage などの代替 Google Cloud サービスを使用することを検討してください。
ENUM STRING ENUM 値の検証は、アプリケーションで実行する必要があります。
SET ARRAY<STRING> SET 要素値の検証は、アプリケーションで実行する必要があります。
LONGBLOBMEDIUMBLOB オブジェクトへの URI を含む BYTES または STRING 小さなオブジェクト(10 MiB 未満)は BYTES として保存できます。より大きなオブジェクトを保存する際には、Cloud Storage などの代替 Google Cloud サービスの使用を検討してください。
LONGTEXTMEDIUMTEXT STRING(データまたは外部オブジェクトへの URI を含む) 小さなオブジェクト(2,621,440 文字未満)は STRING として保存できます。より大きなオブジェクトを保存する際には、Cloud Storage などの代替 Google Cloud サービスの使用を検討してください。
JSON STRING(データまたは外部オブジェクトへの URI を含む) 小さな JSON 文字列(2,621,440 文字未満)は STRING として保存できます。より大きなオブジェクトを保存する際には、Cloud Storage などの代替 Google Cloud サービスの使用を検討してください。
GEOMETRYPOINTLINESTRINGPOLYGONMULTIPOINTMULTIPOLYGONGEOMETRYCOLLECTION Spanner は地理空間データ型をサポートしていません。標準のデータ型を使用してこのデータを保存し、検索 / フィルタリング ロジックはアプリケーション レイヤに実装する必要があります。

移行プロセス

移行プロセスの全体的なタイムラインは次のようになります。

  • スキーマとデータモデルを変換します。
  • SQL クエリを変換します。
  • MySQL に加えて Spanner を使用するようにアプリケーションを移行します。
  • MySQL からデータを一括エクスポートし、Dataflow を使用して、そのデータを Spanner にインポートします。
  • 移行時には、両方のデータベース間で整合性を維持します。
  • アプリケーションを MySQL から移行します。

データベースとスキーマを変換する

既存のスキーマを Spanner のスキーマに変換してデータを保存します。アプリケーションの変更をより簡単にするために、変換されたスキーマが既存の MySQL スキーマとできるだけ一致するようにしてください。ただし、機能の違いにより、いくつかの変更が必要になる場合があります。

スキーマ設計のベスト プラクティスに従うと、Spanner データベースのスループットを向上させ、ホットスポットを減らすことができます。

主キー

いずれのテーブルでも、複数の行を保存する必要がある場合は、そのテーブルの 1 つ以上の列からなる主キーが必要になります。テーブルの主キーはテーブル内の各行を一意に識別します。また、テーブルの行は主キーで並べ替えられるため、テーブル自体が主インデックスとして機能します。

単調に増減する列(シーケンスやタイムスタンプなど)を主キーの最初の部分として指定しないでください。キースペースの最後に挿入が集中してホットスポットが生じる可能性があります。ホットスポットとは単一ノードへのオペレーションの集中であり、この現象が発生すると、書き込みが複数の Spanner ノード間で負荷分散されないため、書き込みスループットが 1 つのノードの容量までに制限されます。

次の手法を使用して一意の主キー値を生成すると、ホットスポットのリスクを軽減できます。

テーブルの主キーを指定した後は、テーブルを削除して再作成しない限り、後で主キーを変更することはできません。主キーの指定方法の詳細については、スキーマとデータモデルの主キーをご覧ください。

次に、音楽トラックのデータベース用のテーブルを作成する DDL ステートメントの例を示します。

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
  BirthDate  DATE,
) PRIMARY KEY(SingerId);

テーブルをインターリーブする

Spanner には、1 対多の親子関係を持つ 2 つのテーブルを定義できる機能があります。この機能を使用すると、ストレージ内でデータ行の親子がインターリーブされます。これにより、テーブルが効果的に事前結合され、親と子をともに対象とするクエリでより効率的なデータ取得が可能になります。

子テーブルの主キーは、親テーブルの主キー列で始まる必要があります。子行の観点からは、親行の主キーは外部キーとなります。最大 6 レベルの親子関係を定義できます。

子テーブルに対して削除時アクションを定義すると、親行が削除された場合の動作を指定できます。子行をすべて削除するか、子行が存在する間は親行の削除をブロックするかのいずれかの動作を選択できます。

次に、Albums テーブルを作成して、前に定義した親の Singers テーブルにインターリーブする例を示します。

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId)
INTERLEAVE IN PARENT (Singers)
ON DELETE CASCADE;

セカンダリ インデックスを作成する

セカンダリ インデックスを作成して、テーブル内のデータを主キー以外でインデックス登録することもできます。Spanner ではテーブルと同じ方法でセカンダリ インデックスが実装されるため、インデックス キーとして使用される列値にはテーブルの主キーと同じ制約が適用されます。したがって、Spanner のテーブルと同様にインデックスの整合性も保証されます。

セカンダリ インデックスを使用した値検索は、テーブル結合を使用したクエリと実質的に同じです。STORING 句を使用して元のテーブルの列値のコピーをセカンダリ インデックスに保存して Covering index とすることで、インデックスを使用したクエリのパフォーマンスが向上します。

Spanner のクエリ オプティマイザでは、クエリの対象となるすべての列がセカンダリ インデックス自体に保存されている場合(カバードクエリ)に限り、セカンダリ インデックスが自動的に使用されます。元のテーブルの列をクエリするときにインデックスを強制的に使用するには、次の例に示すように、SQL ステートメントで FORCE INDEX ディレクティブを使用する必要があります。

SELECT *
FROM MyTable@{FORCE_INDEX=MyTableIndex}
WHERE IndexedColumn=@value

インデックスを使用してテーブルの列内の値を一意にするには、その列に UNIQUE インデックスを定義します。重複する値の追加はインデックスによって防止されます。

次に、Albums テーブルにセカンダリ インデックスを作成する DDL ステートメントの例を示します。

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);

データの読み込み後に追加のインデックスを作成すると、インデックスへのデータの入力に時間がかかることがあります。追加する頻度を 1 日平均 3 回に制限することをおすすめします。セカンダリ インデックスの作成方法の詳細については、セカンダリ インデックスをご覧ください。インデックス作成の制限事項の詳細については、スキーマの更新をご覧ください。

SQL クエリを変換する

Spanner は、拡張機能を含む ANSI 2011 SQL に対応し、データの変換や集計に役立つ多数の関数と演算子を備えています。MySQL 固有の構文、関数、型を使用する SQL クエリについては、Spanner との互換性を保つために変換する必要があります。

Spanner では構造化データを列定義として使用することはできませんが、ARRAY<> 型と STRUCT<> 型を使用すれば、SQL クエリで構造化データを扱うことができます。たとえば、1 つのクエリで STRUCTARRAY を使用して(事前結合されたデータを利用して)、アーティストのすべてのアルバムを返せます。詳細については、クエリ構文に関するドキュメントのサブクエリをご覧ください。

SQL クエリを実行する際には、Cloud Console の Spanner クエリ インターフェースを使用してクエリのプロファイリングを行うことができます。一般に、大きなテーブルを対象としてテーブル全体のスキャンを実行するクエリには非常にコストがかかるため、使用は控えめにする必要があります。SQL クエリの最適化の詳細については、SQL のベスト プラクティスのドキュメントをご覧ください。

Spanner を使用するようにアプリケーションを移行する

Spanner は、さまざまな言語用のクライアント ライブラリを備えています。また、データの読み取りや書き込みには、Spanner 固有の API 呼び出しを使用することも、SQL クエリデータ変更言語(DML)ステートメントを使用することもできます。キーによって行を直接読み取る場合など、一部のクエリでは、API 呼び出しを使用したほうが処理が速くなることがあります(SQL ステートメントの変換が不要であるため)。

Java Database Connectivity(JDBC)ドライバを使用して Spanner に接続し、ネイティブに統合されていない既存のツールやインフラストラクチャを利用することもできます。

移行プロセスの一環として、前述の Spanner で利用できない機能をアプリケーションに実装する必要があります。たとえば、データ値を検証して関連テーブルを更新するトリガーは、既存の行の読み取り、制約の検証、更新された行の両方のテーブルへの書き込みという一連の処理を実行する読み取り / 書き込みトランザクションを使用して、アプリケーションに実装する必要があります。

Spanner に備えられた読み取り / 書き込みトランザクションと読み取り専用トランザクションによってデータの外部整合性が確実化されます。さらに、読み取りトランザクションには、次の方法で指定した整合性のあるデータ バージョンを読み取るようにタイムスタンプ バウンドを適用できます。

  • 過去の正確な時刻(最大 1 時間前まで)。
  • 将来の時刻(その時間になるまで読み取りがブロックされる)。
  • 許容範囲内のバウンド ステイルネス。最新のデータが別のレプリカで利用可能かどうかをチェックすることなく、過去の特定の時点までの整合性のあるビューが返されます。この方法を使用すると、パフォーマンス上の利点が得られる代わりに、データが古くなる可能性があります。

MySQL から Spanner にデータを転送する

MySQL から Spanner にデータを転送するには、MySQL データベースをポータブル ファイル形式(XML など)にエクスポートしてから、Dataflow を使用してそのデータを Spanner にインポートする必要があります。

MySQL から Spanner へのデータ転送

MySQL から一括でエクスポートする

MySQL に含まれている mysqldump ツールは、データベース全体を整形式の XML ファイルにエクスポートできます。また、SELECT ... INTO OUTFILE SQL ステートメントを使用して、テーブルごとに CSV ファイルを作成することもできます。ただし、この方法には一度に 1 つのテーブルしかエクスポートできないという欠点があります。つまり、エクスポートする際には、データベースの整合性を維持するために、アプリケーションを一時停止するか、データベースを静止する必要があります。

データファイルのエクスポートが終了したら、データファイルを Cloud Storage バケットにアップロードして、インポートできるようにすることをおすすめします。

Spanner に一括でインポートする

MySQL と Spanner ではデータベース スキーマが異なる可能性があるため、インポート プロセスでデータ変換が必要になる場合があります。このようなデータ変換処理と Spanner へのデータのインポートは、Dataflow を使用すると最も簡単に実行できます。Dataflow は、Google Cloud から配信される抽出、変換、読み込み(ETL)サービスです。このサービスは、Apache Beam SDK で記述されたデータ パイプラインを実行するプラットフォームです。これにより、複数のマシン上の大量のデータを並列に読み取って処理できます。

Apache Beam SDK では、簡単な Java プログラムを記述するだけで、データの読み取り、変換、書き込みを実装できます。Cloud Storage と Spanner 用のビームコネクタが標準装備されているため、実際にコーディングが必要なのはデータ変換部分だけです。

CSV ファイルから読み取って Spanner に書き込む単純なパイプラインの例については、サンプルコード リポジトリをご覧ください。

親子でインターリーブされたテーブルを Cloud Spanner スキーマで使用する場合は、インポート プロセスで親行が子行より先に作成されるように注意する必要があります。したがって、Spanner のインポート パイプライン コードでは、最初にルートレベルのテーブルのすべてのデータをインポートしてから、レベル 1 のすべての子テーブル、レベル 2 のすべての子テーブル(これ以降も同様)という順序でインポートする必要があります。

Spanner のインポート パイプラインを直接使用してデータを一括でインポートすることもできますが、この場合、適切なスキーマ設計を持つ Avro ファイルにデータが格納されている必要があります。

両方のデータベース間で整合性を維持する

多くのアプリケーションには可用性要件が定められており、データのエクスポートとインポートに必要な時間だけアプリケーションをオフラインにすることは困難です。つまり、データを Spanner に転送している間、アプリケーションによって既存のデータベースが変更され続けます。そのため、アプリケーション実行中は、Spanner データベースへの更新内容を複製する必要があります。

変更データ キャプチャ、アプリケーションでの同時更新の実装など、さまざまな方法で 2 つのデータベースを同期できます。

変更データ キャプチャ

MySQL には、ネイティブの変更データ キャプチャ(CDC)ユーティリティはありません。ただし、MySQL バイナリログを受信して CDC ストリームに変換できるさまざまなオープンソース プロジェクトがあります。たとえば、Maxwell のデーモンはデータベースに CDC ストリームを提供できます。

このストリームにサブスクライブして同じ変更(データ変換後の変更)を Spanner データベースに適用するアプリケーションを記述できます。

アプリケーションから両方のデータベースを同時に更新する

別の方法は、両方のデータベースへの書き込みを実行するようにアプリケーションを変更することです。一方のデータベース(最初は MySQL)が信頼できる情報源と見なされ、データベースへの書き込みが発生するたびに、行全体が読み取られ、変換されてから Spanner データベースに書き込まれます。このようにして、アプリケーションによって常に Spanner の行が最新のデータで上書きされます。

すべてのデータが正しく転送されたことを確認したら、信頼できる情報源を Spanner データベースに切り替えることができます。このメカニズムによって、Spanner に切り替えるときに問題が見つかった場合のロールバック パスが確保されます。

データの整合性を検証する

データが Spanner データベースにストリーミングされるときに、Spanner データと MySQL データを定期的に比較して、データの整合性を検証できます。整合性を検証する際には、両方のデータソースに対してクエリを実行し、その結果を比較します。

Dataflow では、Join 変換を使用して大規模なデータセットに対して詳細な比較を行うことができます。この変換に 2 つのキー付きデータセットを渡すと、対応する値がキー別に照合されます。その後、対応する値を比較して、等しいかどうかを確認できます。整合性のレベルがビジネス要件を満たすまで、この検証を定期的に実行できます。

アプリケーションの信頼できる情報源を Spanner に切り替える

データ移行が確実に終了したら、Spanner を信頼できる情報源として使用するようにアプリケーションを切り替えることができます。変更を MySQL データベース側にも継続的に反映することで、MySQL データベースが最新の状態に保たれ、問題が発生した場合のロールバック パスが確保されます。

最終的には、MySQL データベースの更新コードを無効にして削除し、廃止された MySQL データベースをシャットダウンできます。

Spanner データベースをエクスポートしてインポートする

必要に応じて、Dataflow テンプレートを使用して、Spanner から Cloud Storage バケットにテーブルをエクスポートできます。その結果、フォルダには、エクスポートされたテーブルを含む一連の Avro ファイルと JSON マニフェスト ファイルが保存されます。これらのファイルは、次のようなさまざまな目的に役立ちます。

  • データ保持ポリシーの遵守または障害復旧のためにデータベースをバックアップできます。
  • Avro ファイルを BigQuery などの他の Google Cloud サービスにインポートできます。

エクスポートおよびインポート プロセスの詳細については、データベースのエクスポートデータベースのインポートをご覧ください。

次のステップ