MySQL から Spanner への移行

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

移行の制約

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

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

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

シーケンス

Spanner では、主キーの値を生成するデフォルトの方法として、UUID バージョン 4 を使用することをおすすめします。GENERATE_UUID() 関数(GoogleSQLPostgreSQL)は、STRING 型で表される UUID バージョン 4 値を返します。

整数値を生成する必要がある場合は、Spanner でビット反転した正のシーケンス(GoogleSQLPostgreSQL)がサポートされ、これにより 64 ビットの正の数空間に均等に分散する値が生成されます。ホットスポットの問題を回避するため、これらの数値を使用できます。

詳細については、主キーのデフォルト値の戦略をご覧ください。

アクセス制御

Spanner は、テーブルレベルと列レベルでのきめ細かいアクセス制御をサポートしています。ビューに対する細かいアクセス制御はサポートされていません。詳細については、きめ細かいアクセス制御についてをご覧ください。

データ検証制約

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

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

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

生成列

Spanner は生成列をサポートしています。列値は常に、テーブル定義の一部として指定された関数によって生成されます。MySQL の場合と同様に、生成された列は DML ステートメントで指定された値に明示的に設定できません。

生成列は、CREATE TABLE または ALTER TABLE データ定義言語(DDL)ステートメントで列定義の一部として定義されます。AS キーワードの後には、有効な SQL 関数と必要な接尾辞キーワード STORED が続きます。STORED キーワードは ANSI SQL 仕様の一部であり、関数の結果がテーブルの他の列とともに格納されることを示します。

SQL 関数 generation expression には、決定論的な式、関数、演算子を含めることができます。また、セカンダリ インデックスで使用することも、外部キーとして使用することもできます。

この列タイプを管理する方法について詳しくは、生成された列の作成と管理をご覧ください。

サポートされるデータタイプ

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 NUMERICSTRING MySQL では、列宣言で定義されているように、NUMERIC データ型と DECIMAL データ型は最大 65 桁の精度と尺度をサポートします。Spanner NUMERIC のデータ型は、最大 38 桁の精度と 10 進 9 桁の尺度をサポートします。
精度の向上が必要な場合は、代替メカニズムについて任意精度の数値データの保存をご覧ください。
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 JSON 小さな JSON 文字列(2,621,440 文字未満)は JSON として保存できます。より大きなオブジェクトを保存する際には、Cloud Storage などの代替 Google Cloud サービスの使用を検討してください。
GEOMETRYPOINTLINESTRINGPOLYGONMULTIPOINTMULTIPOLYGONGEOMETRYCOLLECTION Spanner は地理空間データ型をサポートしていません。標準のデータ型を使用してこのデータを保存し、検索 / フィルタリング ロジックはアプリケーション レイヤに実装する必要があります。

移行プロセス

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

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

ステップ 1: データベースとスキーマを変換する

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

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

主キー

Spannerでは、いずれのテーブルでも、複数の行を保存する必要がある場合は、そのテーブルの 1 つ以上の列からなる主キーが必要になります。テーブルの主キーはテーブル内の各行を一意に識別し、Spanner は主キーを使用してテーブルの行を並べ替えます。Spanner は高度に分散型であるため、データの増加に合わせてスケーリングされる主キーを生成する方法を選択することが重要です。詳細については、推奨の主キーの移行戦略をご覧ください。

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

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

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 回に制限することをおすすめします。セカンダリ インデックスの作成方法の詳細については、セカンダリ インデックスをご覧ください。インデックス作成の制限事項の詳細については、スキーマの更新をご覧ください。

ステップ 2: SQL クエリを変換する

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

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

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

ステップ 3: Spanner を使用するようにアプリケーションを移行する

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

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

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

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

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

ステップ 4: 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 ファイルにデータが格納されている必要があります。

ステップ 5: 両方のデータベース間で整合性を維持する

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

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

変更データ キャプチャ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

次のステップ