【Google Cloud】大規模データベース移行【Cloud SQL for SQL Server】

# 0. はじめに
オンプレミス SQL Server 上の大規模データベースを Cloud SQL for SQL Server へ移行する場合、単純な SQL Server 完全バックアップ (.bak) を利用した移行では、移行のタイムフレーム内でデータ移行を完了させることが困難な場合もあるのではないかと思います。
今回、大規模データベースの移行を効率的に実施することが可能となる **「ストライプ インポートを利用した Cloud SQL for SQL Server へのデータ移行方法」** についてまとめてみようと思います。

# 目次
[0. はじめに](#0-はじめに)
[1. ストライプ インポートの概要](#1-ストライプ-インポートの概要)
[2. Cloud SQL への大規模データベース移行](#2-cloud-sql-への大規模データベース移行)
　- [2.1. オンプレミス SQL Server 上で完全バックアップを複数ファイルに分割して作成](#21-オンプレミス-sql-server-上で完全バックアップを複数ファイルに分割して作成)
　- [2.2 分割した各バックアップファイルを Cloud Storage バケット上の同一フォルダ配下にアップロード](#22-分割した各バックアップファイルを-cloud-storage-バケット上の同一フォルダ配下にアップロード)
　- [2.3 複数に分割して作成した完全バックアップファイルを ストライプ インポート コマンドで Cloud SQL for SQL Server へインポート](#23-複数に分割して作成した完全バックアップファイルを-ストライプ-インポート-コマンドで-cloud-sql-for-sql-server-へインポート)
　- [2.4 オンプレミス SQL Server 上で TLOG バックアップを作成](#24-オンプレミス-sql-server-上で-tlog-バックアップを作成)
　- [2.5 TLOG バックアップファイルを Cloud Storage バケットにアップロード](#25-tlog-バックアップファイルを-cloud-storage-バケットにアップロード)
　- [2.6 TLOG バックアップファイルを Cloud SQL for SQL Server へインポート](#26-tlog-バックアップファイル-を-cloud-sql-for-sql-server-へインポート)
　- [2.7 インポートしたデータベースのステータスを ONLINE に変更](#27-インポートしたデータベースのステータスを-online-に変更)
[3. 制約事項](#3-制約事項)


# 1. ストライプ インポートの概要
ストライブ インポートは、SQL Server の完全バックアップを実施する際、**出力するバックアップデータを複数のファイルに分割**して作成後、複数に分割されたバックアップ ファイルを利用して、Cloud SQL へデータベースをインポートする方法になります。
<br/>

**【メリット : Cloud SQL for SQL Server 観点】**
- 完全バックアップの処理時間、データベースの復元時間 (インポート) の短縮が可能
- 大規模データベース (5TB以上) のインポートが可能
- バックアップファイルを分割することで並行して個々のファイルを Google Cloud Storage へアップロードすることが可能となり、アップロード時間の短縮が可能
- Google Cloud Storage へのバックアップファイルのアップロードが何らかの要因により失敗した場合、1つの大きなバックアップファイルを再アップロードする場合と比較すると、分割された一部のバックアップファイルのみ再アップロードすることが可能となり、再アップロード時間の短縮が可能

# 2. Cloud SQL への大規模データベース移行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/82b61e27-ad2e-a60d-2a82-89139169515c.png)

①. オンプレミス SQL Server 上の移行対象データベースの **完全バックアップ (.bak) を複数のファイルに分割して作成**

②. ①. で作成した作成したバックアップファイル (.bak) を移行のタイムフレーム内でデータ転送を完了させることができる方式 (Transfer Appliance を利用、Cloud Storage に直接アップロードなど) で Cloud Storage 上の同一フォルダ配下にアップロード

③. gcloud CLI を実行可能な環境 (Cloud Shell など) へログインし、②. で複数に分割されたバックアップファイル (.bak) をアップロードしたフォルダを指定して「**gcloud sql import bak ... --bak-type=FULL --no-recovery --striped** 」コマンドを実行し、**「NORECOVERY」 モード** でデータベースを Cloud SQL に インポート

④. オンプレミス SQL Server 上の移行対象データベースの **トランザクションログ (TLOG) バックアップ (LOG.bak) を単一ファイルで作成**

⑤. ④. で作成した作成した TLOG バックアップファイル (_LOG.bak) を Cloud Storage 上にアップロード

⑥. gcloud CLI を実行可能な環境 (Cloud Shell など) へログインし、⑤. でアップロードした TLOG バックアップファイル (_LOG.bak) のパスを指定した「**gcloud sql import bak ... --bak-type=TLOG --no-recovery** 」コマンドを実行し、**「NORECOVERY」 モード** で TLOG バックアップの情報を Cloud SQL データベース にインポート

⑦. 適用が必要な全てのバックアップのインポート処理が完了後、「**gcloud sql import bak ... --recovery-only** 」コマンドを実行し、**データベースのステータスを「ONLINE」に変更**


## 2.1. オンプレミス SQL Server 上で完全バックアップを複数ファイルに分割して作成
1\) SQL Server Management Studio (以下 SSMS) , SQLCMD などのツールからオンプレミス SQL Server に接続します。
2\) 移行対象の大規模データベースの完全バックアップを複数ファイルに分割して作成します。
```ruby:完全バックアップを複数ファイルに分割して作成するコマンド (例)
use master
go
backup database GCSDB to disk = 'E:\backup\GCSDB01.bak',
disk = 'F:\backup\GCSDB02.bak',
disk = 'G:\backup\GCSDB03.bak'
with compression, format,
  medianame = 'GCSDBStripedSet'
go
```

**【ポイント】**
- ディスク I/O によるボトルネックを軽減させるため、**バックアップ ファイル (.bak) の分割先のドライブを別ドライブに指定**
※ 複数の物理ディスクに紐づけられた論理ドライブをバックアップ ファイル (.bak) の分割先のドライブに指定する場合など、ディスク I/O によるボトルネックを最小にできる場合は、分割先のドライブを同一ドライブに指定しても問題ないと思います。
- **compression オプション**を指定
各バックアップ ファイル (.bak) サイズを圧縮することで、Google Cloud Storage へのアップロード時間を短縮できることが期待できます。 

※ オンプレミス SQL Server の backup コマンドの詳細については「[BACKUP (Transact-SQL)](https://learn.microsoft.com/ja-jp/sql/t-sql/statements/backup-transact-sql?wt.mc_id=DP-MVP-5004154&view=sql-server-ver16) 」を参照。

## 2.2 分割した各バックアップファイルを Cloud Storage バケット上の同一フォルダ配下にアップロード
ファイルサイズの大きいバックアップファイル (.bak) を Cloud Storage にアップロードする場合、**アップロード元環境から利用可能なネットワーク帯域より想定されるデータ転送時間を算出**し、移行のタイムフレーム内にアップロードを完了させることが可能な方式を選択します。
※ アップロード元環境から利用可能なネットワーク帯域が細い場合、[Transfer Appliance](https://cloud.google.com/transfer-appliance/docs/4.0/overview?hl=ja) を利用することも検討。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/34c77a23-0e1f-5ab6-7562-996256441ce7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/de90f14d-240d-7749-1e1b-e4982f84130a.png)

※ 引用:「[Google Cloud への移行: 大規模なデータセットの転送](https://cloud.google.com/architecture/migration-to-google-cloud-transferring-your-large-datasets?hl=ja) 」
<br/>

1\) gcloud CLI コマンド ([gcloud storage ](https://cloud.google.com/sdk/gcloud/reference/storage)), [Transfer Appliance](https://cloud.google.com/transfer-appliance/docs/4.0/overview?hl=ja), [Storage Transfer Service](https://cloud.google.com/storage-transfer/docs/overview?hl=ja) などを利用し、**Cloud Storage バケットの同一フォルダ配下に分割された各バックアップファイル (.bak) を転送**します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/8af8b60d-8966-1769-15c7-cceb07703cb4.png)


## 2.3 複数に分割して作成した完全バックアップファイルを ストライプ インポート コマンドで Cloud SQL for SQL Server へインポート

1\) **gcloud CLI コマンドを実行可能な環境**にログインします。
※ Google Cloud コンソール から Cloud Shell を起動、もしくは CLI ツールがインストールされた環境にログインします。
※ ストライプ インポートを実施するためには、**「gcloud sql import bak --striped」** コマンドを実行する必要があります。

2\) **「gcloud sql import bak ... --bak-type=FULL --no-recovery --striped 」** コマンドを実行し、複数に分割されたバックアップファイル (.bak) からデータベースを **「NORECOVERY」 モード** でインポートします。

```:ストライプ インポートを利用してデータベースを「NORECOVERY」 モードでインポートするコマンド (例)
gcloud sql import bak <インスタンス名> gs://<バックアップファイル (.bak) を配置したCloud Storageのフォルダパス> --database=<データベース名> --striped --bak-type=FULL --no-recovery 
```

```:
$ gcloud sql import bak gcsql01 gs://sql*******/striped-full-backup --database=GCSDB --striped --bak-type=FULL --no-recovery 
Data from [gs://sql*******/striped-full-backup] will be imported to [gcsql01].

Do you want to continue (Y/n)?  y

Importing data into Cloud SQL instance...done.                                                                                                                                                                                                    
Imported data from [gs://sql*******/striped-full-backup] into [https://*******.googleapis.com/sql/v1beta4/projects/te*******/instances/gcsql01].
                                                                                        
```

**【補足】**
- SQL Server 完全バックアップをインポート後、TLOG バックアップを適用する必要がある場合は、データベースを **「NORECOVERY」モード** でインポートする必要があります。


## 2.4 オンプレミス SQL Server 上で TLOG バックアップを作成
完全バックアップを Cloud SQL for SQL Server へインポートまでの間に発生した変更を反映させるため、オンプレミス SQL Server 上で TLOG バックアップを実施します。

1\) SQL Server Management Studio (以下 SSMS) , SQLCMD などのツールからオンプレミス SQL Server に接続します。
2\) 移行対象の大規模データベースの TLOG バックアップを作成します。
```ruby: TLOG バックアップ コマンド (例)
use master
go
backup log GCSDB to disk = 'c:\backup\GCSDB_LOG.bak'
go
```
**【補足】**
- **TLOG バックアップの適用では ストライプ インポート がサポートされていない (2024年7月時点)** ため、**単体ファイルとして TLOG バックアップ ファイルを作成**する必要があります。


## 2.5. TLOG バックアップファイルを Cloud Storage バケットにアップロード
1\) Google Cloud コンソールにログインします。
2\) Cloud Storage - バケット - 任意のバケットを選択します。
3\) 「**ファイルをアップロード**」を選択し、作成した **SQL Server TLOG バックアップファイル (_LOG.bak)** をアップロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/70497bac-53bb-0cf7-10ed-663f816ad2ea.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/5ec72f18-9379-131b-16df-dc9230540b23.png)

## 2.6. TLOG バックアップファイル を Cloud SQL for SQL Server へインポート
1\) **gcloud CLI コマンドを実行可能な環境**にログインします。
※ Google Cloud コンソール から Cloud Shell を起動、もしくは CLI ツールがインストールされた環境にログインします。

2\) **「gcloud sql import ... --bak-type=TLOG --no-recovery」** コマンドを実行し、TLOG バックアップファイル (_LOG.bak) からデータベースを **「NORECOVERY」** モードでインポートします。
```: TLOG バックアップからデータベースを「NORECOVERY」モードでインポートするコマンド (例)
gcloud sql import bak <インスタンス名> gs://<バックアップファイル (.bak) を配置したCloud Storageのパス> --database=<データベース名> --bak-type=TLOG --no-recovery
```
```:
$ gcloud sql import bak gcsql01 gs://sql*******/tlog-backup/GCSDB_LOG.bak --database=GCSDB --bak-type=TLOG --no-recovery
Data from [gs://sql*******/tlog-backup/GCSDB_LOG.bak] will be imported to [gcsql01].

Do you want to continue (Y/n)?  y

Importing data into Cloud SQL instance...done.                                                                            
Imported data from [gs://sql*******/tlog-backup/GCSDB_LOG.bak] into [https://*******.googleapis.com/sql/v1beta4/projects/te*******/instances/gcsql01].
```
</br>

**【補足】**
- データ移行完了までに、複数回 オンプレミス SQL Server TLOG バックアップを Cloud SQL for SQL Server へ適用する必要がある場合、**TLOG バックアップについても「NORECOVERY」モードでインポートする必要があります。**

参考URL:「[トランザクション ログのバックアップをインポートする](https://cloud.google.com/sql/docs/sqlserver/import-export/import-export-bak?hl=ja#import_transaction_log_backups) 」


## 2.7. インポートしたデータベースのステータスを ONLINE に変更
**データベースのステータスが「RESTORING」** の状態時には、データベース上のテーブルに対して SELECT/UPDATE/DELETE/INSERT などのクエリ処理を実施することができません。
そのため、クエリ処理を実施できるようにするため、適用が必要なバックアップのインポート処理がすべて完了した後、**「gcloud sql import bak ... --recovery-only」コマンド**を実行して、データベースのステータスを **「ONLINE」** に変更します。
</br>
```:データベースの状態を「ONLINE」に変更するコマンド (例)
gcloud sql import bak gcsql01 --database=GCSDB --recovery-only
```
```:
$ gcloud sql import bak gcsql01 --database=GCSDB --recovery-only
Bring database [GCSDB] online with recovery-only.

Do you want to continue (Y/n)?  y

Bring database online...done.
Bring database [GCSDB] online with recovery-only.
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/d9abfbca-acdc-e6b7-2657-afa19bde7d5c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/9c753709-a740-f53f-8b3c-586f09e682b2.png)


# 3. 制約事項
Cloud SQL for SQL Server インスタンスで **「ポイントインタイムリカバリを有効にする」** が有効な状態で、**バックアップからデータベースを「NORECOVERY」モードでインポート実施すると、以下のエラーで処理が失敗**します。

```
ERROR: (gcloud.sql.import.bak) HTTPError 400: This operation is not valid for this instance.
```
そのため、バックアップからデータベースを「NORECOVERY」モードでインポートを実施する必要がある場合、事前に **「ポイントインタイムリカバリを有効にする」** を無効にする必要があるようです。

<br/>

**※ 本ブログに記載した内容は個人の見解であり、所属する会社、組織とは全く関係ありません。**

**※ 2024年7月時点**