LoginSignup
0
0

Alibaba Cloud DTSによってサービスダウンタイムなしのデータ移行

Last updated at Posted at 2024-02-19

1. DTSとは

DTS(Data Transmission Service)は、リレーショナルデータベース、NoSQL、OLAPなど様々なデータベース間でのデータ移行やリアルタイムデータ同期を実現するAlibaba Cloudのマネージドサービスです。
AWSのDMS(Database Migration Service)に相当しますが、DTSはより多様な機能を提供します。

DTSは、次のような主要機能を提供します。

  • Data migration: データソースにあるスキーマや既存のデータやキャプチャされた変更などをターゲットデータベースへ移行する機能
    • オンプレや他社のクラウド上のデータベースをAlibaba Cloudへ移行する時に利用
    • データ移行タスクは、作業完了後に削除することが一般的です。これは、一度きりのデータ移行に最適なアプローチです。
  • Data synchronization:リアルタイムにdata sourcesを双方向同期する機能、以下のシナリオで利用されことが多いです。(一回きりのタスクではなく、常時稼働なタスク)
    • DRサイトの構築(マインのサイトから継続的にデータをDRサイトへリアルタイム同期)
    • multi-site(DR用ではなく、2つのサイトは同時にサービスを提供し、end userからのリクエストは自動的に地理的に近いサイトに振り分けられる)間のデータのリアルタイム同期
    • 業務用データベース(OLTPのデータベース)からデータをdata warehouseへ同期
  • Change tracking: データソースのdataの変更をキャプチャし、publisher/subscriber streamとしてメッセージを第三者に送る
    • 複数のsubscriberに同時にメッセージを送れるのは特徴です。(AWSのSNSに似ているところがあります。)

今回はData migrationを中心として、説明します。

2. Data migrationの仕組み

image.png

  • ポイントとしては、初期データ(既存データ)の同期と変更されたデータの同期を別々のサブタスクとして実行します。厳密に言うと、「スキーマのマイグレーション」という選択肢もあります
    image.png

  • 実行順序としては、スキーマのマイグレーション → 初期データのマイグレーション → 増分データのマイグレーションという順に行います

  • 3つのサブタスクは単独で(つまり、スキーマのみ同期、または初期データのみ同期、または増分データのみの同期)実行も可能ですが、一般的には3つすべてを選択することをおすすめします

初期データのみを同期し、変更されたデータを同期しない方法を選ぶと、データ移行時にソースデータベース側のアプリケーションを完全に停止し、データの変更を行わないようにする必要があります。データ量が多い場合、移行には数十時間かかることがあり、その間はサービスが完全に停止してしまいます。そのため、可能な限り「増分データの同期」も選択することをおすすめします。

  • データをそのまま同期するのではなく、DTSのETL機能を通じてデータをフィルタリングや加工を行い、その後ターゲットデータベースに書き込むことも可能です。
  • フィルタリング:例えば、一定の条件を満たすデータしか同期しない
  • データ検証:データ同期プロセスにおけるレコードの欠損、特定カラムの値の漏れ、またはスキーマ(カラム)に関する問題を人手で確認することは非常に困難です。このため、データ検証(data verification)機能が役立ちます。この機能を活用すると、ソースデータベースとターゲットデータベースの各行のhash値を計算し、両者が一致しているかどうかをチェックします。全てが一致していれば、ソースデータベースとターゲットデータベースのデータが完全に同じであることが確認できます。
    image.png
  • 単純に両データベースの行数が一致しているかどうかの検証も可能です。ソースデータベースとターゲットデータベースが異なるエンジンを使用している場合(例:OracleからPolarDB for Oracleに移行)、データの一部が変換される可能性があるため、行数の一致のみを確認することが推奨されます。
  • 料金:
    • 行ごとのhash値の比較:有料
    • 行数のみ検証:無料
    • 既存の全文データおよび差分データの検証:有料
    • schema verification: 無料

3. データ移行の流れ

  1. DTSタスクを作成し、データをリアルタイムにsource databaseからtarget databaseに同期
    1. 移行タイプ:以下の全部を選択することがおすすめです
      • スキーマの移行
      • 既存データの移行
      • 増分データの移行
    2. データ検証モード:以下の全部を選択することがおすすめです
      • 既存データの検証
      • 増分データの検証
      • スキーマの検証
        image.png
  2. データのマイグレーションが完了したら、データ検証を行い、欠損や漏れがないかを自動チェック
    1. スキーマの移行が完了したら、スキーマ検証のsub taskが自動的に起動し、検証を行う
    2. 既存データの移行が完了したら、既存データに対するデータ検証のsub taskが自動的に起動し、検証を行う
    3. 増分データ同期しながら、内容のhash値を比較し、sourceとtargetのデータが一致しているかどうかを自動的にチェック
      image.png
  3. サービス(Application)を一時に停止し、source databaseにデータを書き込まないようにする
  4. 増分データの移行と増分データの検証を除くすべてが完了し、さらに増分データの遅延が約1秒程度になった場合、すべての同期対象が同期されたとみなします。
    image.png
  5. ソースデータベースにログインし、セッション情報を確認し、新しいセッションが書き込み操作を実行していないことを確認します
    MySQLの場合:
    show processlist;
    
  6. アプリケーション側のdatabase接続endpointをsource databaseからtarget databaseに変更
    image.png
  7. 新データベースでアプリケーションの動作確認
  8. 全て問題ないことが確認できた後、DTS taskの削除とsource databaseの停止などを行う
    image.png

4. Data migrationの利用パターン

  1. Alibaba Cloud内部のマネージドデータベース間のデータ同期
    例えば RDS → PolarDB あるいは PolarDB → RDS
    image.png

  2. Alibaba CloudのECS上のデータベースからマネージドデータベースへのデータ同期
    image.png

  3. オンプレからAlibaba Cloud上のマネージドデータベースへのデータ同期
    image.png

  4. 他のクラウド上のデータベースからAlibaba Cloud上のデータベースへのデータ同期
    image.png

  5. Alibaba Cloud以外のクラウド同士のデータベースのデータ同期
    image.png
    今回は利用パターンの中の一つ目の Alibaba Cloud内部のマネージドデータベース間のデータ同期について解説します。

5. RDSからPolarDBへのデータマイグレーション

5.1 前提

  • RDS MySQLとPolarDB MySQLのバージョンを統一することが推奨されます。これにより、互換性の問題を防ぎます
  • region, VPC, Alibaba Accountなどは同じにする必要がありません

5.2 事前準備

  1. RDS
  • ローカルPCのmysql clientからRDSとPolarDBにアクセスさせるため、 public endpointを作成しておく
    image.png
    image.png

  • whitelistにPCのpublic IPを追加
    image.png
    image.png
    image.png

  • 追加されたIP whitelist
    image.png

  • データの準備

    mysql> show databases;
    +--------------------+
    | Database           |
    +--------------------+
    | __recycle_bin__    |
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    +--------------------+
    
    mysql> create database dts_test;
    
    mysql> use dts_test;
    Database changed
    mysql> show tables;
    
    mysql> CREATE TABLE myguests (
        ->     id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        ->     firstname VARCHAR(30) NOT NULL,
        ->     lastname VARCHAR(30) NOT NULL,
        ->     email VARCHAR(50),
        ->     reg_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ->     );
    
    
    mysql> show tables;
    +--------------------+
    | Tables_in_dts_test |
    +--------------------+
    | myguests           |
    +--------------------+
    
    mysql> insert into myguests(firstname,lastname,email) values("user1","","user1@example.com");
    
    mysql> insert into myguests(firstname,lastname,email) values("user2","","user2@example.com");
    
    mysql> select * from myguests;
    +----+-----------+----------+-------------------+---------------------+
    | id | firstname | lastname | email             | reg_date            |
    +----+-----------+----------+-------------------+---------------------+
    |  1 | user1     |          | user1@example.com | 2024-02-19 13:49:25 |
    |  2 | user2     |          | user2@example.com | 2024-02-19 13:49:31 |
    +----+-----------+----------+-------------------+---------------------+
    
  1. PolarDB:
  • public endpoint作成
    image.png
    image.png

  • PCのpublic IPをwhitelistに追加
    image.png
    image.png

  • データベースの中身を確認(dts_testというデータベースが存在していない)

    mysql> show databases;
    +--------------------+
    | Database           |
    +--------------------+
    | __recycle_bin__    |
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    +--------------------+
    

5.3 DTSタスク作成

  • Data Migrationタスクを作成
    image.png
  • source databaseのRDSとtarget databaseのPolarDBの情報を入力し、DTSからそれぞれ接続できるかどうかを確認(Test Connectivity and Proceedボタンを押下)
    image.png
  • DTSからアクセスできるために、DTS実行しているサーバー群のIPが自動的にRDSとPolarDBインスタンスのwhitelistに追加されました
    • RDS:
      image.png
    • PolarDB:
      image.png
  • デフォルト設定では、既存データの全量移行(Full Data Migration)のみが選択されています。しかし、スキーマ情報(テーブル構成)と差分データの同期も行いたい場合は、スキーマ移行(Schema Migration)と増分データ移行(Incremental Data Migration)も追加で選択することをお勧めします。

全量データ移行のみを選択した場合、サービスの切り替え前には、すべてのデータが同期されるまでサービスを完全に停止する必要があります。サービスのダウンタイムを最小限に抑えたい場合は、スキーマ移行、全量データ移行、増分データ移行の3つを選択することが推奨されます。

image.png

image.png

image.png

  • Advanced Configurationsで全部デフォルトのままにする
    image.png

  • data verificationの種類を選択:

image.png

  • DTSインスタンスのクラスを選択

DTSインスタンスのクラスは増分データ同期する時の最大RPS(record per second)に影響します。
例えばソースデータベースからターゲットテーブルに毎秒5000行のデータが増分同期される場合、RPSは5000です。
今回はテストのため、一番小さいsmallインスタンスタイプを選択します。

image.png

image.png

  • 生成されたタスク
    image.png

  • デフォルトでは終了した子タスクが表示されない。全体のプロセスを確認するため、Hide Completed Modulesのチェックを外します。
    image.png

  • 赤い部分はすでに完了している子タスクであり、青い子タスクは実行中になります。

増分データはいつでも来る可能性があるため、それに対応する子タスクは基本的に常時実行状態を維持します。

image.png

5.4 DTSの動作確認

5.4.1 既存データの同期

PolarDB:

mysql> show tables;
+--------------------+
| Tables_in_dts_test |
+--------------------+
| myguests           |
+--------------------+

mysql> select * from myguests;
+----+-----------+----------+-------------------+---------------------+
| id | firstname | lastname | email             | reg_date            |
+----+-----------+----------+-------------------+---------------------+
|  1 | user1     |          | user1@example.com | 2024-02-19 18:30:22 |
|  2 | user2     |          | user2@example.com | 2024-02-19 18:30:26 |
+----+-----------+----------+-------------------+---------------------+

5.4.2 差分データの同期

差分データの範囲:
image.png

  1. DML: INSERT文

    RDS:
    mysql> insert into myguests(firstname,lastname,email) values("user3","","user3@example.com");
    
    mysql> select * from myguests;
    +----+-----------+----------+-------------------+---------------------+
    | id | firstname | lastname | email             | reg_date            |
    +----+-----------+----------+-------------------+---------------------+
    |  1 | user1     |          | user1@example.com | 2024-02-19 18:30:22 |
    |  2 | user2     |          | user2@example.com | 2024-02-19 18:30:26 |
    |  3 | user3     |          | user3@example.com | 2024-02-19 19:17:54 |
    +----+-----------+----------+-------------------+---------------------+
    
    PolarDB: `user3`のデータ自動的に同期された
    mysql> select * from myguests;
    +----+-----------+----------+-------------------+---------------------+
    | id | firstname | lastname | email             | reg_date            |
    +----+-----------+----------+-------------------+---------------------+
    |  1 | user1     |          | user1@example.com | 2024-02-19 18:30:22 |
    |  2 | user2     |          | user2@example.com | 2024-02-19 18:30:26 |
    |  3 | user3     |          | user3@example.com | 2024-02-19 19:17:54 |
    +----+-----------+----------+-------------------+---------------------+
    
  2. DML: UPDATE文

    RDS:
    mysql> update myguests set firstname="user1_new" where id = 1;
    
    mysql> select * from myguests;
    +----+-----------+----------+-------------------+---------------------+
    | id | firstname | lastname | email             | reg_date            |
    +----+-----------+----------+-------------------+---------------------+
    |  1 | user1_new |          | user1@example.com | 2024-02-19 19:21:01 |
    |  2 | user2     |          | user2@example.com | 2024-02-19 18:30:26 |
    |  3 | user3     |          | user3@example.com | 2024-02-19 19:17:54 |
    +----+-----------+----------+-------------------+---------------------+
    
    PolarDB: id=1のデータが更新されたました
    mysql> select * from myguests;
    +----+-----------+----------+-------------------+---------------------+
    | id | firstname | lastname | email             | reg_date            |
    +----+-----------+----------+-------------------+---------------------+
    |  1 | user1_new |          | user1@example.com | 2024-02-19 19:21:01 |
    |  2 | user2     |          | user2@example.com | 2024-02-19 18:30:26 |
    |  3 | user3     |          | user3@example.com | 2024-02-19 19:17:54 |
    +----+-----------+----------+-------------------+---------------------+
    
  3. DML: DELETE文

    mysql> delete from myguests where id = 3;
    
    mysql> select * from myguests;
    +----+-----------+----------+-------------------+---------------------+
    | id | firstname | lastname | email             | reg_date            |
    +----+-----------+----------+-------------------+---------------------+
    |  1 | user1_new |          | user1@example.com | 2024-02-19 19:21:01 |
    |  2 | user2     |          | user2@example.com | 2024-02-19 18:30:26 |
    +----+-----------+----------+-------------------+---------------------+
    
    mysql> select * from myguests;
    +----+-----------+----------+-------------------+---------------------+
    | id | firstname | lastname | email             | reg_date            |
    +----+-----------+----------+-------------------+---------------------+
    |  1 | user1_new |          | user1@example.com | 2024-02-19 19:21:01 |
    |  2 | user2     |          | user2@example.com | 2024-02-19 18:30:26 |
    +----+-----------+----------+-------------------+---------------------+
    
  4. DDL: CREATE TABLE

    RDS:
    mysql>  CREATE TABLE items(id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(30) NOT NULL, price VARCHAR(30) NOT NULL);
    
    mysql> show tables;
    +--------------------+
    | Tables_in_dts_test |
    +--------------------+
    | items              |
    | myguests           |
    +--------------------+
    
    PolarDB:
    mysql> show create table items;
    +-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Table | Create Table                                                                                                                                                                                    |
    +-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | items | CREATE TABLE `items` (
      `id` int(6) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(30) NOT NULL,
      `price` varchar(30) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
    +-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    
    RDS:
    mysql>  insert items(id, name, price) values(1, "macbook", 200000);
    
    mysql> select * from items;
    +----+---------+--------+
    | id | name    | price  |
    +----+---------+--------+
    |  1 | macbook | 200000 |
    +----+---------+--------+
    
    PolarDB:
    mysql> select * from items;
    +----+---------+--------+
    | id | name    | price  |
    +----+---------+--------+
    |  1 | macbook | 200000 |
    +----+---------+--------+
    
  5. DDL: ALTER TABLE

    RDS:
    mysql> alter table items add (category VARCHAR(30));
    
    PolarDB:
    mysql> show create table items;
    +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Table | Create Table                                                                                                                                                                                                                                            |
    +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | items | CREATE TABLE `items` (
      `id` int(6) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(30) NOT NULL,
      `price` varchar(30) NOT NULL,
      `category` varchar(30) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 |
    +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    
    RDS:
    mysql> insert items(id, name, price, category) values(2, "HP ProBook", 150000, "PC");
    
    PolarDB: 新しく追加された`category`カラムにデータも入りました
    mysql> select * from items;
    +----+------------+--------+----------+
    | id | name       | price  | category |
    +----+------------+--------+----------+
    |  1 | macbook    | 200000 | NULL     |
    |  2 | HP ProBook | 150000 | PC       |
    +----+------------+--------+----------+
    

5.4.3 data verification

5.4.3.1 一致しているケース

  • 既存のデータ(Full Data)のverification
    image.png

task起動した最初はmyguestsテーブルに2件のデータしかなかったため、既存データの部分は2件がチェックされ、一致していないレコードがありません。(一致していないレコードがある場合はActions欄に内容が表示されます)

  • schemaのverification:
    image.png

tableやviewやFunctionなどがチェックの対象になります。今回はテーブルしかありません。しかもチェックした結果は一致していないテーブルがありません。

  • 増分データのverrification:
    image.png
  • mygustsテーブル: insert一件、update一件、delete一件 合計3件のデータがチャックされて、一致していないレコードが存在していません。
  • itemsテーブル: insert 二件のデータがチェックされて、一致していないレコードが存在していません。

5.4.3.2 一致していないケース

上記では、全てが一致する状況を説明しました。次に、データが一致しない場合、data verification機能がそれを正確に検出できるかどうかを検証します。
通常、ターゲットデータベース(PolarDB)のデータを直接変更することは推奨されませんが、不一致の状況を模倣するためには、このような直接的な変更を行うことにします。

  • PolarDBのデータを直接変更
    PolarDB:
    mysql> update items set category = "PC" where id = 1;
    
    mysql> select * from items;
    +----+------------+--------+----------+
    | id | name       | price  | category |
    +----+------------+--------+----------+
    |  1 | macbook    | 200000 | PC       |
    |  2 | HP ProBook | 150000 | PC       |
    +----+------------+--------+----------+
    
    RDS:
    mysql> select * from items;
    +----+------------+--------+----------+
    | id | name       | price  | category |
    +----+------------+--------+----------+
    |  1 | macbook    | 200000 | NULL     |
    |  2 | HP ProBook | 150000 | PC       |
    +----+------------+--------+----------+
    
  • Full Data Verificationの子タスクを再実行(Restart)させる
    image.png
  • itemsテーブルに一件一致していないデータが存在している。view reportリンクをクリック
    image.png
  • id=1のデータのcategoryというカラムにもとデータがNULLであり、targetデータはPCになっていることが分かります。
    image.png
  • 再度PolarDBのデータを直接変更し、もとの状態に戻させる
    PolarDB:
    mysql> update items set category = null where id = 1;
    
    mysql> select * from items;
    +----+------------+--------+----------+
    | id | name       | price  | category |
    +----+------------+--------+----------+
    |  1 | macbook    | 200000 | NULL     |
    |  2 | HP ProBook | 150000 | PC       |
    +----+------------+--------+----------+
    
    RDS:
    mysql> select * from items;
    +----+------------+--------+----------+
    | id | name       | price  | category |
    +----+------------+--------+----------+
    |  1 | macbook    | 200000 | NULL     |
    |  2 | HP ProBook | 150000 | PC       |
    +----+------------+--------+----------+
    
  • 再度Full Data Verificationの子タスクを再実行(Restart)させる。すべてのデータが一致になりました。
    image.png

5.4.4 ETLでデータを加工してからターゲットデータベースに書き込む

5.4.4.1 ETLの動き

今度はソースデータベースのitemテーブルのcategoryカラムにNULLをotherという文字列に変更したうえで、ターゲットデータベースに書き込みます。

  • DTSタスクを修正し、ETL設定を変更する
    image.png
  • ETLに以下の変更内容を入力
e_if(op_is_null(`category`), e_set(`category`,"other"))

image.png

  • ソースデータベースに新しいレコードを作成します
mysql> select * from items;
+----+------------+--------+----------+
| id | name       | price  | category |
+----+------------+--------+----------+
|  1 | macbook    | 200000 | NULL     |
|  2 | HP ProBook | 150000 | PC       |

mysql> insert items(id, name, price) values(3, "chromebook", 32800);
Query OK, 1 row affected (0.01 sec)

mysql> select * from items;
+----+------------+--------+----------+
| id | name       | price  | category |
+----+------------+--------+----------+
|  1 | macbook    | 200000 | NULL     |
|  2 | HP ProBook | 150000 | PC       |
|  3 | chromebook | 32800  | NULL     |
+----+------------+--------+----------+
  • ターゲットデータベースにNULLがotherという文字列に変更されました(既存のデータは変更の対象外なので、id=1のデータは変わっていません)
mysql> select * from items;
+----+------------+--------+----------+
| id | name       | price  | category |
+----+------------+--------+----------+
|  1 | macbook    | 200000 | NULL     |
|  2 | HP ProBook | 150000 | PC       |
|  3 | chromebook | 32800  | other    |
+----+------------+--------+----------+

5.4.4.2 データ検証

  • 増分データのチェックで新規挿入されたid=3のレコードでデータが一致していないことが分かります
    image.png
    image.png

やはりETLでデータ変換された場合は、hash値での比較方法はかならず不一致になってしまうため、hash値の比較の代わりに、行数の一致のみを確認することがおすすめです。

  • Full Data VerificationIncremental Data Verificationの子タスクを削除して、新しい子タスクを作り直します
    image.png
    image.png
    image.png

  • データ検証の種類を選択し、「行数を検証」を選択

増分データは常に増加しているため、増分データの行数が一致するかどうかを検証することは不可能(そもそも意味がない)です。ETLでデータを加工する場合、行数の検証に頼るしかありません。しかし、増分データの行数が一致するかどうかを確認すること自体が元々不可能であるため、増分データの検証は省略すべきです。省略しない場合、増分データの検証はハッシュ値の比較になり、結果は必ず不一致となってしまいます。

image.png

  • 結果としては不一致のレコードが存在していない(つまりレコード数が同じです)
    image.png

行数一致していないパターンの確認:

  • target database (PolarDB):

    mysql> delete from items where id = 1;
    Query OK, 1 row affected (0.01 sec)
    
    mysql> select * from items;
    +----+------------+--------+----------+
    | id | name       | price  | category |
    +----+------------+--------+----------+
    |  2 | HP ProBook | 150000 | PC       |
    |  3 | chromebook | 32800  | other    |
    +----+------------+--------+----------+
    
  • source database (RDS):

    mysql> select * from items;
    +----+------------+--------+----------+
    | id | name       | price  | category |
    +----+------------+--------+----------+
    |  1 | macbook    | 200000 | NULL     |
    |  2 | HP ProBook | 150000 | PC       |
    |  3 | chromebook | 32800  | NULL     |
    +----+------------+--------+----------+
    
  • 既存データの検証の子taskを再起動させる
    image.png

  • source databaseに3件のデータがあり、target databaseに2件のデータしかなくて、1件の差があることになります
    image.png

6. まとめ

  • DTSを使用する際、既存データと増分データの両方を選択することで、サービスのダウンタイムを最小化しつつ効率的にデータ移行が可能です。
  • 増分データを選択した場合、ソースデータベースで新規に作成されたデータ(INSERT)、更新されたデータ(UPDATE)、削除されたデータ(DELETE)、さらにはCREATE TABLEALTER TABLECREATE INDEXなどのDDL文も自動的に同期されます。
  • DTSのdata verification機能を利用すると、データの漏れや差異が自動的に検出されるため、非常に便利です。
  • DTSのETL機能を利用すると、一部のデータをフィルタリングや加工を行い、その後ターゲットデータベースに書き込むことも可能です。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0