このページでは、大容量データを Spanner に効率よく一括読み込みするためのガイドラインを説明します。
Spanner にデータを一括で読み込む方法はいくつかあります。
- データ操作言語(DML)を使用して行を挿入する。
- ミューテーションを使用して行を挿入する。
- Dataflow コネクタを使用してデータをインポートする。
- Avro ファイルを使用してデータベースをインポートする。
- CSV 形式のデータをインポートする。
Google Cloud CLI を使用して行を挿入することもできますが、一括読み込みに gcloud CLI を使用することはおすすめしません。
一括読み込みのパフォーマンス ガイドライン
一括読み込みのパフォーマンスを最適化するには、パーティショニングを最大限に活用して、ワーカータスク間でデータの書き込みを分散します。
Spanner は負荷に基づいた分割を使用して、インスタンスのコンピューティング リソース全体にデータ負荷を均等に分散します。負荷の高い状態が数分続くと、Spanner は行間にスプリットの境界を設定します。一般に、データの負荷が適切に分散されている場合、スキーマ設計と一括読み込みのベスト プラクティスに従うと、インスタンスで使用可能な CPU リソースが飽和するまで、書き込みスループットは数分ごとに倍増します。
主キーによるデータの分割
Spanner は、テーブルを小さな範囲に自動的にパーティション分割します。行の主キーにより、その行がどのパーティションに配置されるかが決まります。
一括読み込みの書き込みスループットを最適化するには、次のパターンになるようにデータを主キーで分割します。
- 各パーティションに、主キー列で決定された連続した行範囲が含まれる。
- 各 commit に、1 つのパーティションのデータのみが含まれる。
パーティションの数は、Spanner インスタンスのノード数の 10 倍にすることをおすすめします。以下の方法でパーティションに行を割り当てます。
- 主キーでデータを並べ替えます。
- データを 10 ×(ノード数)個の同じサイズのパーティションに分割します。
- 各パーティションにそれぞれワーカータスクを作成して割り当てます。ワーカータスクはアプリケーション内で作成されます。これは Spanner の機能ではありません。
このパターンにより、負荷が大きい場合、一括書き込み全体のスループットの最大値がノードごとに 10~20 MB/秒になります。
データを読み込みながら、Spanner はスプリットを作成して更新し、インスタンスのノード間で負荷を分散します。このプロセスにおいてスループットが一時的に低下する場合があります。
例
3 つのノードを持つリージョン構成があるとします。この構成の非インターリーブ テーブルには 90,000 行あるため、主キーは 1〜90,000 の範囲になります。
- 行数: 90,000 行
- ノード数: 3
- パーティション数: 10 × 3 = 30
- パーティションあたりの行数: 90,000 ÷ 30 = 3,000
最初のパーティションには 1 から 3,000 までのキー範囲、2 番目のパーティションには 3,001 から 6,000 までのキー範囲が含まれています。同様の範囲指定を行っていくと、30 番目のパーティションのキー範囲は、87,001 〜 90,000 になります(大規模なテーブルでは連続したキーを使用しないでください。この例はデモ用です)。
各ワーカータスクは単一のパーティションに書き込みを送信します。各パーティション内では、主キーによって行を順番に書き込みます。主キーを基に、行をランダムに書き込む場合でも、比較的高いスループットが得られます。テスト実行を測定することで、データセットに最大のパフォーマンスをもたらすアプローチがわかります。
パーティショニングを使用しない一括読み込み
連続した行セットを commit に書き込むと、ランダムな行を書き込むよりも高速になる場合があります。ランダムな行には、異なるパーティションのデータが含まれる可能性があります。
commit に書き込まれるパーティションが多くなると、サーバー間の調整が必要になり、commit のレイテンシとオーバーヘッドが増加します。
各ランダム行が異なるパーティションに属している可能性があるため、複数のパーティションが関係する可能性があります。最悪のシナリオでは、各書き込みが Spanner インスタンスのすべてのパーティションに関係します。前述のように、書き込みスループットが低下するのは、パーティションが増加した場合です。
過負荷を回避する
Spanner が処理できる以上の書き込みリクエストが送信される場合があります。Spanner は、トランザクションを中止することにより過負荷に対処します。これをプッシュバックと呼びます。書き込み専用トランザクションの場合、Spanner は自動的にトランザクションを再試行します。この場合、プッシュバックによりレイテンシが高くなります。高負荷が続くと、プッシュバックは 1 分間続くことがあります。非常に高い負荷の場合、プッシュバックが数分間続くことさえあります。プッシュバックを避けるには、書き込みリクエストを調整して CPU 使用率を相応の範囲内に抑える必要があります。また、CPU 使用率が制限内に収まるようにノードの数を増やすこともできます。
1 MB~5 MB のミューテーションを一度に commit
書き込みが大きくても小さくても、Spanner に書き込むたびにオーバーヘッドが生じます。スループットを最大化するには、1 回の書き込みで格納されるデータ容量を最大まで増やします。書き込みが大きいほど、書き込みごとのオーバーヘッドの比率が低くなります。適切な方法は各 commit で数百単位の行を変更することです。比較的大きな行を書き込むときは、commit のサイズを 1 MB~5 MB にすると、通常最適なパフォーマンスが得られます。小さい値やインデックス付きの値を書き込むときは、通常、1 回の commit で数百行まで書き込むことをおすすめします。commit のサイズや行数とは関係なく、1 回の commit あたりのミューテーションは 80,000 までという制限に注意してください。最適なパフォーマンスを見いだすには、スループットをテストして測定する必要があります。
5 MB 超のミューテーションや数百行を超えるミューテーションを commit してもメリットはなく、commit サイズと commit あたりのミューテーション数に関する Spanner の制限を超えるリスクがあります。
セカンダリ インデックスのガイドライン
データベースにセカンダリ インデックスがある場合は、データベース スキーマへのそのインデックスの追加を、テーブルデータの読み込み前に行うか、後に行うかを選択する必要があります。
データの読み込み前にインデックスを追加すると、スキーマ変更をすぐに完了できます。ただし、インデックスに影響する書き込みごとにインデックスの更新も必要になるため、時間がかかります。データの読み込みが完了すると、すべてのインデックスが配置されたデータベースをすぐに使用できます。テーブルとそのインデックスを同時に作成するには、新しいテーブルと新しいインデックスの DDL ステートメントを 1 つのリクエストで Spanner に送信します。
データの読み込み後にインデックスを追加すると、書き込みが効率的になります。ただし、インデックスのバックフィルごとにスキーマの変更には長時間を要する可能性があります。 データベースは完全には使用できず、スキーマの変更がすべて完了するまで、クエリでインデックスを使用できません。データベースは書き込みとクエリを処理できますが、速度は遅くなります。
データを読み込む前に、ビジネス アプリケーションにおいて非常に重要なインデックスを追加することをおすすめします。重要でないインデックスはすべて、データの移行後に追加します。
一括読み込み中に INTERLEAVE IN
を使用する
テーブル間で多くの親子参照があるスキーマの場合は、参照完全性を確保するために、常に子よりも前に親を読み込む必要があります。このオーケストレーションは、特に階層が複数ある場合は複雑になることがあります。この複雑さにより、バッチ処理と並列処理が非常に困難になり、一括読み込み時間全体が大幅に増加する可能性があります。Spanner では、これらの関係は INTERLEAVE IN PARENT
または外部キーを使用して適用されます。詳細については、CREATE TABLE
ドキュメントを参照してください。
一括読み込み後に外部キーを追加すると、インデックスが自動的に作成されるため、secondary-indexes のガイドラインに従ってください。
ただし、INTERLEAVE IN PARENT
テーブルの場合は、一括読み込み時に INTERLEAVE IN
セマンティクスを使用してすべてのテーブルを作成することをおすすめします。これにより、行が物理的にインターリーブされますが、参照完全性は適用されません。これにより、地域区分のパフォーマンス上のメリットが得られますが、事前注文は必要ありません。子行を対応する親の前に挿入できるようになったため、Spanner はすべてのテーブルに並列に書き込むことが可能です。
すべてのテーブルが読み込まれたら、インターリーブされたテーブルを移行して、ALTER TABLE t1 SET INTERLEAVE IN PARENT t2
ステートメントを使用して親子関係の適用を開始できます。これにより参照完全性が検証され、孤立した子行が存在する場合は失敗します。検証が失敗した場合は、次のクエリを使用して、不足している親行を特定します。
SELECT pk1, pk2 FROM child
EXCEPT DISTINCT
SELECT pk1, pk2 FROM parent;
スループットのテストと測定
スループットは予測が困難な場合があります。最終読み込みを実行する前に、一括読み込みの戦略をテストすることをおすすめします。パーティショニングとパフォーマンスのモニタリングを使用した詳細な例については、データ読み込みスループットの最大化をご覧ください。
既存データベースへの一括読み込みを定期的に行う際のベスト プラクティス
データは含むがセカンダリ インデックスがない既存のデータベースを更新する場合にも、このドキュメントのベスト プラクティスが当てはまります。
セカンダリ インデックスがある場合、同様の手順で一定のパフォーマンスが得られますが、パフォーマンスは、トランザクションに関係するスプリットの平均数に依存します。スループットが低すぎる場合は、次の方法を試してください。
- 各 commit に含まれるミューテーションの数を減らします。これにより、スループットが向上する場合があります。
- アップロードの容量が更新対象テーブルの現在の合計サイズよりも大きい場合は、いったんセカンダリ インデックスを削除し、アップロードが完了した後で追加し直してください。この手順は通常は必要ありませんが、スループットが向上する場合があります。