Amazon Web Services ブログ

AWS Database Migration Service で PostgreSQL のテーブルをグループ毎にタスク化

本投稿は、 Manojit Saha Sardar と Chirantan Pandya による記事 「Group database tables under AWS Database Migration Service tasks for PostgreSQL source engine」を翻訳したものです。

AWS Database Migration Service (AWS DMS) は、データベースやデータウェアハウスを含むデータリポジトリの転送と複製を容易にするために設計されたクラウドベースのサービスです。同種および異種のデータベースシステム間でデータを転送するソリューションを提供し、さまざまなデータプラットフォーム間の移行を可能にします。

データ転送プロセスは通常、大きく2 つのフェーズがあります:

  • 既存データの移行 (フルロード) – このフェーズでは、AWS DMS が全てのデータ転送を実行し、元のデータストアから転送先のデータストアへ既存のすべての情報を転送します。
  • 継続的な同期 (変更データキャプチャ) – 初期のフルロードの後、AWS DMS は変更データキャプチャ (CDC) と呼ばれている方法を通じて、データ変更の継続的な同期を可能にします。これらの変更はリアルタイムまたはニアリアルタイムで宛先のデータストアに継続的に転送され、適用されます。これにより、宛先のデータストアと元のデータストアが同期した状態を維持します。

AWS DMS は、リレーショナルデータベース、データウェアハウス、NoSQL データベースなど、幅広いソースおよびターゲットデータリポジトリに対応しています。転送を成功させるには、データベースとアプリケーションの前提条件に取り組むことが不可欠であり、初期段階でのベースとなる準備と計画がこの目標達成に重要な役割を果たします。

AWS DMS はデータベース移行のための堅牢なサービスを提供していますが、移行はプロジェクト毎にユニークであり、それぞれ異なる課題を抱えています。移行を成功させるために不可欠なことは適切な準備と設計プロセスです。パフォーマンスの最適化や潜在的な遅延問題への対処は特に重要です。

このブログ記事では、プロセスの早い段階でフルロードと CDC 遅延の潜在的な根本原因を把握するためのガイダンスを提供し、AWS DMS タスクで最高のパフォーマンスを達成するためにテーブルを適切にクラスタリングする方法を提示します。この記事で説明する戦略に従うことで、データベース移行をより効果的に計画し、タスクの規模を見積もり、潜在的な問題を最小限に抑え、パフォーマンスを最大化させるための効率的な転送プロセスを設計することができます。

データの把握

データ移行プロジェクトに着手するためには、最初に転送するデータを理解する必要があります。フルロードによる転送フェーズでは、ソース情報の完全なレプリカをターゲットのデータベースに転送することが含まれます。この手順により、正確なデータのコピーが、ソースデータベースからコピー先のデータベースに確実に入力されます。

CDC は、ソースのデータベースからのデータおよびデータ構造 (スキーマ) に対して発生した変更をリアルタイムまたはニアリアルタイムで記録する方法です。CDC は、これらの変更を他のデータベースやアプリケーションに配信し、簡単にソースのデータベースと同期し続けることができます。

必要な移行タスクの数やテーブルのグルーピングについて見積もるために、データベースのサイズ、運用負荷、およびハードウェア仕様を調べることで、データベース再配置のフルロードと CDC の要件全体を評価できます。

AWS DMS を利用する際、データ転送の速度に直接的または間接的に影響を与える要因はいくつかあります。以下は、フルロードと CDC の両方に共通する典型的な要因の一部です:

  • データベースオブジェクトの規模 – 巨大なテーブル( 2 TB を超えるもの)を専用の移行タスクで分離することで、移行効率を向上させることができます。大規模なデータセットの処理を特定のタスクや操作内で分離することで、転送プロセスがより効率的かつ効果的になる可能性があります。
  • セグメント化されたオブジェクトとされていないオブジェクト – 複数のテーブルを同時に読み込むことで、大きなセグメント化されたテーブルを転送できます。AWS DMS では、複数の並列スレッドを使用して単一の大規模テーブルを読み込むこともできます。これは、複数のセグメントとサブセグメントを持つ数十億レコードのテーブルに特に有効です。
  • 主キーとユニークキーがないオブジェクト – AWS DMS は、大規模なオブジェクトを持つソーステーブルを転送するために、主キーまたはユニークキーを必要とします。
  • ラージオブジェクト( LOB ) – AWS DMS は、レプリケーションインスタンスに適切なメモリを割り当てるために必要な、列行ごとの LOB サイズを特定することができないため、LOB 列には特別な処理が必要です。AWS DMS は、LOB データを移行するためのフル、制限付き、インラインの LOB モードを提供しています。LOB を別のタスクに保持することで、転送アクティビティを効率的に管理できます。
  • 変更量 – ワークロードに CDC の変更が大量に含まれる場合、同じレコードセットを繰り返し更新したり、同じレコードを挿入または更新して削除したりする場合は、バッチ適用を使用してターゲットへの適用スループットを向上させることができます。

ソリューションの概要

この投稿の目的は、ソースデータベースのデータディクショナリを分析し、その情報をハードウェアの詳細と組み合わせて、効率的なデータ移行タスクのための推奨事項を作成することです。この分析により、AWS DMS タスクの最適な数と、そのタスク内にあるテーブルのグループ分けを決定し、移行プロセス中の潜在的な遅延問題を軽減します。

このワークフローには、以下のステップが含まれます:

  1. ソースの PostgreSQL データベースにコントロールテーブルを作成します。
  2. データディクショナリテーブルとビューを使用して、テーブルサイズ、パーティション、インデックス、制約、データ型、LOB データを分析し、コントロールテーブルにデータを入力します。
  3. 受信する変更量をモニタリングして、テーブルの日次による増加量を把握します。
  4. ステップ番号でテーブルを分類します。
  5. テーブルをグループ化します。

以下の図は、本ソリューションのアーキテクチャを示しています。

前提条件

以下の知識があると、このブログ記事が理解しやすくなります:

  • AWS DMS
  • PostgreSQL
  • psql および plpgsql プロシージャ

1. ソースの PostgreSQL データベースでコントロールテーブル作成

この最初のステップでは、table_mapping という名前のコントロールテーブルを作成します。これにより、どのデータを移行しているかを一目で理解できるようになります。このテーブルは、システムカタログと統計ビューを参照して作成され、テーブルのサイズ、パーティション、パーティションサイズ (件数、平均、最小、最大)、LOB カラム、インデックス数、主キーまたはユニークキー制約、外部キー制約、およびテーブルに対する切り捨て、挿入、更新、削除操作の DDL (データ定義言語) / DML (データ操作言語) 操作回数に関する情報が含まれます。

このコントロールテーブルは、テーブルをグループ化する次のステップで使用するためのベースラインデータを提供します。

ソースの PostgreSQL データベースにコントロールテーブルを作成するには:

  1. ソースの PostgreSQL データベースに接続します。
  2. 以下の SQL ブロックを実行して、コントロールテーブルを作成します:
    CREATE TABLE TABLE_MAPPING (
     OWNER VARCHAR(30),
     OBJECT_NAME VARCHAR(30),
     OBJECT_TYPE VARCHAR(30),
     SIZE_IN_MB NUMERIC(12,4),
     STEP INTEGER,
     IGNORE CHAR(3),
     PARTITIONED CHAR(3),
     PART_NUM INTEGER,
     SPECIAL_HANDLING CHAR(3),
     PK_PRESENT CHAR(3),
     UK_PRESENT CHAR(3),
     LOB_COLUMN INTEGER,
     GROUPNUM INTEGER,
     TOTAL_DML INTEGER 
    );

2. コントロールテーブルへの入力

コントロールテーブルが作成できたところで、PostgreSQLデータベース内のシステムカタログおよび統計ビューを用いてコントロールテーブルにデータを格納できます。これらはデータベースオブジェクトに関連するサイズ、タイプ、パーティショニング、制約、LOBデータに関する情報を提供します。具体的には、以下のデータディクショナリオブジェクトが挙げられます:

  • PG_TABLES – PostgreSQL データベース内の各テーブルに関する有用な情報へのアクセスを提供します。
  • PG_PARTITIONED_TABLE – このカタログテーブルは、テーブルがどのようにパーティション化されているかに関する情報を格納します。
  • PG_INHERITS – このカタログテーブルは、テーブルとインデックスの継承階層に関する情報を記録します。データベース内の各直接的な親子テーブルまたはインデックスの関係に対して 1 つのエントリが存在します。
  • PG_CLASS – このカタログテーブルは、列を持つテーブルやインデックス、シーケンス、ビュー、マテリアライズドビュー、複合型、TOAST テーブルなどのその他のオブジェクトについて説明します。
  • PG_NAMESPACE – このカタログテーブルは名前空間を格納します。名前空間は SQL スキーマの基礎となる構造です。各名前空間は、名前の競合なしに、リレーションと型の個別のコレクションを持つことができます。
  • INFORMATION_SCHEMA.COLUMNS – データベース内のテーブルとビューの列に関するメタデータを提供します。これは、異なるデータベースシステム間でテーブル構造に関する情報にアクセスするための標準的な方法です。

これらのカタログとビューオブジェクトをクエリすることで、データベースオブジェクトに関するメタデータを取得できます。これにより、データベース内のさまざまなオブジェクトの構造、サイズ、制約、および変更点を特定し分析することができます。

それでは、システムカタログと統計ビューからコントロールテーブルに読み込みます:

  1. 対象スキーマ (ここでは 'admin' を使用) にあるデータベーステーブルの詳細 (名前、サイズ、パーティションなど) をコントロールテーブルに挿入します:
    INSERT INTO admin.table_mapping(OWNER, OBJECT_NAME, OBJECT_TYPE, SIZE_IN_MB, PART_NUM, PARTITIONED)
     SELECT 
     schemaname,
     tablename,
    'TABLE',
     pg_total_relation_size(schemaname || '.' || tablename) / 1024.0 / 1024.0,
     CASE WHEN EXISTS (
     SELECT 1 FROM pg_partitioned_table pt 
     JOIN pg_class c ON c.oid = pt.partrelid 
     WHERE c.relname = tablename 
    ) THEN (
     SELECT count(*) FROM pg_inherits 
     WHERE inhparent = (schemaname || '.' || tablename)::regclass 
    ) ELSE 1 END,
     CASE WHEN EXISTS (
     SELECT 1 FROM pg_partitioned_table pt 
     JOIN pg_class c ON c.oid = pt.partrelid 
     WHERE c.relname = tablename 
    ) THEN 'YES' ELSE 'NO' END 
     FROM pg_tables 
     WHERE schemaname = 'admin';
    
  2. 'admin' スキーマのマッピングテーブルから子テーブルデータをクリーンアップします:
    DELETE FROM admin.table_mapping WHERE object_name IN (SELECT 
     child.relname AS child_table 
     FROM pg_inherits 
     JOIN pg_class parent ON pg_inherits.inhparent = parent.oid 
     JOIN pg_class child ON pg_inherits.inhrelid = child.oid 
     JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace 
     JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace 
     WHERE parent.relkind = 'p' and nmsp_child.nspname ='admin');
  3. 親テーブルのパーティションテーブルサイズを設定します:
    WITH partition_sizes AS (
     SELECT 
     parent_schema,
     parent_table,
     SUM(partition_size)/(1024*1024) as size_mb -- バイトを MB に変換
     FROM (
     SELECT 
     nmsp_parent.nspname AS parent_schema,
     parent.relname AS parent_table,
     pg_total_relation_size(child.oid) AS partition_size 
     FROM pg_inherits 
     JOIN pg_class parent ON pg_inherits.inhparent = parent.oid 
     JOIN pg_class child ON pg_inherits.inhrelid = child.oid 
     JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace 
     JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace 
     WHERE parent.relkind = 'p'
    ) subquery 
     GROUP BY parent_schema, parent_table 
    )
     UPDATE admin.table_mapping tm 
     SET SIZE_IN_MB = ps.size_mb 
     FROM partition_sizes ps 
     WHERE tm.OWNER = ps.parent_schema 
     AND tm.OBJECT_NAME = ps.parent_table ;
  4. 主キーが定義されているコントロールテーブルの PK_PRESENT フィールドを更新します:
    UPDATE admin.table_mapping 
     SET PK_PRESENT = CASE 
     WHEN EXISTS (
     SELECT 1 FROM information_schema.table_constraints 
     WHERE table_schema = OWNER 
     AND table_name = OBJECT_NAME 
     AND constraint_type = 'PRIMARY KEY'
    ) THEN 'YES' ELSE 'NO' END ;
  5. ユニークキーが定義されているコントロールテーブルの UK_PRESENT フィールドを更新します:
    UPDATE admin.table_mapping 
     SET UK_PRESENT = CASE 
     WHEN EXISTS (
     SELECT 1 FROM information_schema.table_constraints 
     WHERE table_schema = OWNER 
     AND table_name = OBJECT_NAME 
     AND constraint_type = 'UNIQUE'
    ) THEN 'YES' ELSE 'NO' END ;
  6. 少なくとも 1 つの LOB カラムを持つコントロールテーブルの LOB_COLUMN フィールドを更新します:
    UPDATE admin.table_mapping 
     SET LOB_COLUMN = (
     SELECT COUNT(*) 
     FROM information_schema.columns 
     WHERE table_schema = OWNER 
     AND table_name = OBJECT_NAME 
     AND data_type IN ('bytea', 'text', 'json', 'jsonb')
    );

3. 時間経過に伴う DML 変更量の把握

PostgreSQL データベースの pg_stat_user_tables システムビューには、最後に統計情報が収集されてから、データベース内のすべてのテーブルに対して行われた変更に関する情報が含まれています。PostgreSQL は pg_stat_user_tables を使用して、PostgreSQL 統計コレクターに基づくテーブルの DML と統計情報を収集します。最後の自動バキューム( autovacuum )のタイムスタンプは、タイミング情報を取得するために使用されます。定期的な ANALYZE 操作がテーブルに対して実行され、DML の変更に関する情報と最後の分析時間で pg_stat_user_tables を更新します。

毎日のデータを確認することで、テーブルに加えられた日々の変更についてのインサイトを得ることができます。この情報は、パフォーマンスに影響を与えたり、統計の収集や再編成などのメンテナンスタスクが必要になったりする可能性のある、多数の変更が加えられたテーブルを特定するのに役立ちます。

時間経過に伴う DML の変更量を把握するには:

  1. pg_stat_user_tables から詳細を取得するために、MST_DBA_TAB_MOD というステージングテーブルを作成します:
    CREATE TABLE admin.mst_dba_tab_mod (
     DATA_DATE DATE,
     TABLE_OWNER VARCHAR(128),
     TABLE_NAME VARCHAR(128),
     PARTITION_NAME VARCHAR(128),
     SUBPARTITION_NAME VARCHAR(128),
     INSERTS BIGINT,
     UPDATES BIGINT,
     DELETES BIGINT,
     TIMESTAMP TIMESTAMP,
     TRUNCATED VARCHAR(3),
     TOTAL_DML BIGINT,
     DROP_SEGMENTS BIGINT 
    );
  2. PostgreSQL の pg_stat_user_tables システムビューから情報を収集し、テーブルの日次平均 DML カウントで MST_DBA_TAB_MOD テーブルを更新します。
    カウントは最後に分析された日付から記録され、平均化されるため、最新のテーブル統計情報を持つことでカウントの精度が向上します。

    INSERT INTO admin.mst_dba_tab_mod (
     TABLE_OWNER,
     TABLE_NAME,
     INSERTS,
     UPDATES,
     DELETES,
     TOTAL_DML,
     TIMESTAMP 
    )
     SELECT 
     schemaname AS TABLE_OWNER,
     relname AS TABLE_NAME,
     n_tup_ins AS INSERTS,
     n_tup_upd AS UPDATES,
     n_tup_del AS DELETES,
     ROUND((n_tup_ins + n_tup_upd + n_tup_del)::numeric /
     GREATEST(EXTRACT(EPOCH FROM (now() - last_autovacuum))::numeric / 86400, 1)) AS TOTAL_DML,
     now() AS TIMESTAMP 
     FROM pg_stat_user_tables 
     WHERE schemaname = 'admin';
  3. 次に、前のステップで埋めた MST_DBA_TAB_MOD からこの情報を収集する以下の UPDATE 文を実行して、コントロールテーブル table_mapping_mapping に DML 情報を入力します:
    UPDATE admin.table_mapping a 
     SET TOTAL_DML = b.TOTAL_DML 
     FROM admin.mst_dba_tab_mod b 
     WHERE a.OWNER = b.TABLE_OWNER 
     AND a.OBJECT_NAME = b.TABLE_NAME 
     AND b.TABLE_OWNER = 'admin';

4. テーブルのステップ番号による分類

以下のコードに示すように、テーブルはデータ特性に関するさまざまな要因を基にデータベース内で分類されます。これらの要因には、テーブルがパーティション化されているかどうか、LOB を含むかどうか、テーブルサイズ、テーブルに対して実行された DML 操作の数などが含まれます。LOB フィールドを持たない非パーティション化テーブルはステップ 1 に分類され、LOB フィールドを持つ非パーティション化テーブルはステップ 2 に、LOB フィールドを持たないパーティション化テーブルはステップ 3 に分類され、以下同様に分類されます。例えば、SIZE_IN_MB が 0 と報告されるすべてのテーブルは、移行の対象外として分類されます。ユースケースに応じて、このステップに追加のテーブルを加えることができます。

これらの属性に基づくテーブルの分類は、最適化を目的として行われます。

UPDATE admin.table_mapping SET STEP = 1 WHERE LOB_COLUMN = 0 AND PARTITIONED = 'NO' AND STEP IS NULL ;

 UPDATE admin.table_mapping SET STEP = 2 WHERE LOB_COLUMN > 0 AND PARTITIONED = 'NO' AND STEP IS NULL ;

 UPDATE admin.table_mapping SET STEP = 3 WHERE LOB_COLUMN = 0 AND PARTITIONED = 'YES' AND STEP IS NULL ;

 UPDATE admin.table_mapping SET STEP = 4 WHERE LOB_COLUMN > 0 AND PARTITIONED = 'YES' AND STEP IS NULL ;

 UPDATE admin.table_mapping SET STEP = 5 WHERE TOTAL_DML > 9999999 ;

 UPDATE admin.table_mapping SET STEP = 0 WHERE SIZE_IN_MB = 0 AND STEP IS NULL ;

5. データベーステーブルのグループ化

グループの推奨プロセスは、TABLE_MAPPING コントロールテーブルに格納された情報に基づいて TABLE_MAPPING_GROUPS テーブルを作成し、データを作成することから始まります。このプロセスは、3 つのパラメータを受け取るプロシージャによって開始されます。

  • マッピングテーブル (TABLE_MAPPING)
  • ソースの移行スキーマ (ADMIN)
  • タスクごとのデータベースオブジェクトのサイズ (600 GB)

タスクごとのデータベースオブジェクトのサイズ (600 GB) は、ソースおよびターゲットのレプリケーションインスタンスの CPU、ネットワーク、I/O 能力などの要因を考慮し、タスク間でテーブルを均等に分散するように選択されています。

シェルスクリプトを使用して admin.both ストアドプロシージャを呼び出し、テーブルをグループ化します。その後、プロシージャ内のセクションを使用して、パーティションテーブルと非パーティションテーブルの両方に対して stepgroupnum を使用してグループ化されたテーブルをリストします。

CREATE OR REPLACE PROCEDURE admin.both (
    IN p_n NUMERIC,
    IN p_schema_name VARCHAR,
    IN p_table_name VARCHAR 
)
 AS 
$BODY$
 DECLARE 
 vTab CHARACTER VARYING(30);
 vDumpSize DOUBLE PRECISION := 0 ;
 vSumBytes DOUBLE PRECISION := 0 ;
 vGroupNum DOUBLE PRECISION := 0 ;
 vPrevStep DOUBLE PRECISION := 1 ;
 reggrouptabs RECORD ;
 BEGIN 
 vDumpSize := 1024 * p_n ;

-- Dynamic SQL to truncate the table 
 EXECUTE format('TRUNCATE %I.%I_groups', p_schema_name, p_table_name);

-- Dynamic SQL to insert data 
 EXECUTE format('
    INSERT INTO %I.%I_groups 
    SELECT owner, object_name, object_type, size_in_mb, step, ignore, partitioned 
    FROM %I.%I', 
    p_schema_name, p_table_name, p_schema_name, p_table_name);

 FOR reggrouptabs IN 
 EXECUTE format('
    SELECT *
    FROM %I.%I_groups 
    ORDER BY step, size_in_mb', 
    p_schema_name, p_table_name)
 LOOP 
    IF (reggrouptabs.step != vPrevStep) THEN 
        vGroupNum := 0 ;
        vSumBytes := 0 ;
    END IF ;
    vSumBytes := vSumBytes + reggrouptabs.size_in_mb ;

    IF (vSumBytes >= vDumpSize) THEN 
        vGroupNum := vGroupNum + 1 ;
        vSumBytes := 0 ;
    END IF ;

    -- Dynamic SQL for UPDATE 
    EXECUTE format('
        UPDATE %I.%I_groups 
        SET groupnum = $1 
        WHERE owner = $2 AND object_name = $3', 
        p_schema_name, p_table_name)
    USING vGroupNum, reggrouptabs.owner, reggrouptabs.object_name ;

    vPrevStep := reggrouptabs.step ;
 END LOOP ;
 COMMIT ;
 END ;
$BODY$
 LANGUAGE plpgsql ;

以下の select ステートメントを実行して、table_mapping_groups を繰り返し処理し、それぞれのグループ名ごとにテーブル名をリストすることで、最終的な plpgsql プロシージャの出力を収集できます。これは、対応する AWS DMS タスクを作成するために使用できます:

SELECT partitioned,step, groupnum, count(1) table_in_group,sum(size_in_mb) Total_group_size 
 FROM admin.table_mapping_groups 
 group by partitioned,step, groupnum 
 ORDER BY 1,2,3 ;

デモンストレーション

この投稿のソリューションを実証するために、6.2 TB のデータ転送用の PostgreSQL ソースインスタンスを使用し、DMS タスクごとに 600 GB のサイズ内でテーブルをグループ化します。構成には、8 コア、16 vCPU、64 GiB のメモリを備えた db.m5.4xlarge インスタンスを使用しています。CPU 使用率、ネットワークスループット、および運用負荷の評価に基づき、600 GB 単位でタスクを作成することが、このワークロードに最適であると判断されました。ただし、各ソースには固有の特性があるため、適切なクラスターサイズを決定するには、特定のソースデータベースの規模、ワークロード、CPU、メモリ、およびネットワーク使用率を徹底的に分析する必要があります。

このプロセスは、ループテーブルからステップ情報を降順に整理し、その後、定義されたサイズパラメータに従って整理されたデータをクラスタリングする方法を実装しています。

個々のステップを手動で実行する必要がないように、以下の手順をシェルスクリプトに統合しました。

  1. ソースの PostgreSQL データベースにコントロールテーブルを作成します。
  2. データディクショナリのテーブルとビューを使用して、テーブルサイズ、パーティション、インデックス、制約、データ型、LOB データを分析し、コントロールテーブルにデータを投入します。
  3. 受信する変更の量をモニタリングすることで、テーブルの日次による増加量を把握します。
  4. ステップ番号でテーブルを分類します。

以下のコードは、対応するシェルスクリプトです:

#!/bin/bash 

# Collect database connection details 
 echo "Please enter database connection details:"
 read -p "Database name [postgres]: " DB_NAME 
 DB_NAME=${DB_NAME:-postgres}

 read -p "Database user [postgres]: " DB_USER 
 DB_USER=${DB_USER:-postgres}

 read -p "Database host [localhost]: " DB_HOST 
 DB_HOST=${DB_HOST:-localhost}

 read -p "Database port [5432]: " DB_PORT 
 DB_PORT=${DB_PORT:-5432}

 read -s -p "Database password: " password 
 DB_PASSWORD=$password 

# Export variables for psql to use 
 export PGPASSWORD="$DB_PASSWORD"

# Function to execute psql query and return result 
 execute_query() {
    psql -U "$DB_USER" \ 
         -h "$DB_HOST" \ 
         -p "$DB_PORT" \ 
         -d "$DB_NAME" \ 
         -t -A \ 
         -c "$1"
}

# Test connection 
 echo -e "\nTesting database connection..."
 if ! execute_query "SELECT 1 ;" > /dev/null 2>&1 ; then 
    echo "Error: Could not connect to the database. Please check your credentials."
    exit 1 
 fi 
 echo "Connection successful!"

# Example 1: Simple SELECT query 
#echo -e "\nExample 1: List of users"
#users=$(execute_query "SELECT usename FROM pg_catalog.pg_user ;")
#echo "$users"

 echo -e "\nCreating the control table"
 table_ddl=$(execute_query "CREATE TABLE TABLE_MAPPING (
 OWNER VARCHAR(30),
 OBJECT_NAME VARCHAR(30),
 OBJECT_TYPE VARCHAR(30),
 SIZE_IN_MB NUMERIC(12,4),
 STEP INTEGER,
 IGNORE CHAR(3),
 PARTITIONED CHAR(3),
 PART_NUM INTEGER,
 SPECIAL_HANDLING CHAR(3),
 PK_PRESENT CHAR(3),
 UK_PRESENT CHAR(3),
 LOB_COLUMN INTEGER,
 GROUPNUM INTEGER,
 TOTAL_DML INTEGER 
);")
 echo "$table_ddl"

 echo -e "\nCleaning up the old control table"
 clean_ddl=$(execute_query "TRUNCATE TABLE admin.table_mapping ;")
 echo "$clean_ddl"

 echo -e "\nInsert into the control table the database table details "
 table_insert=$(execute_query "INSERT INTO admin.table_mapping(OWNER, OBJECT_NAME, OBJECT_TYPE, SIZE_IN_MB, PART_NUM, PARTITIONED)
 SELECT 
 schemaname,
 tablename,
'TABLE',
 pg_total_relation_size(schemaname || '.' || tablename) / 1024.0 / 1024.0,
 CASE WHEN EXISTS (
 SELECT 1 FROM pg_partitioned_table pt 
 JOIN pg_class c ON c.oid = pt.partrelid 
 WHERE c.relname = tablename 
) THEN (
 SELECT count(*) FROM pg_inherits 
 WHERE inhparent = (schemaname || '.' || tablename)::regclass 
) ELSE 1 END,
 CASE WHEN EXISTS (
 SELECT 1 FROM pg_partitioned_table pt 
 JOIN pg_class c ON c.oid = pt.partrelid 
 WHERE c.relname = tablename 
) THEN 'YES' ELSE 'NO' END 
 FROM pg_tables 
 WHERE schemaname = 'admin';")
 echo "$table_insert"


 echo -e "\nCleaning up child table data"
 clean_dml=$(execute_query "DELETE FROM admin.table_mapping WHERE object_name IN (SELECT 
 child.relname AS child_table 
 FROM pg_inherits 
 JOIN pg_class parent ON pg_inherits.inhparent = parent.oid 
 JOIN pg_class child ON pg_inherits.inhrelid = child.oid 
 JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace 
 JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace 
 WHERE parent.relkind = 'p' and nmsp_child.nspname ='admin') ;
;")
 echo "$clean_dml"

 echo -e "\nPopulate partition table size"
 pop_part=$(execute_query "WITH partition_sizes AS (
 SELECT 
 parent_schema,
 parent_table,
 SUM(partition_size)/(1024*1024) as size_mb -- Convert bytes to MB 
 FROM (
 SELECT 
 nmsp_parent.nspname AS parent_schema,
 parent.relname AS parent_table,
 pg_total_relation_size(child.oid) AS partition_size 
 FROM pg_inherits 
 JOIN pg_class parent ON pg_inherits.inhparent = parent.oid 
 JOIN pg_class child ON pg_inherits.inhrelid = child.oid 
 JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace 
 JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace 
 WHERE parent.relkind = 'p'
) subquery 
 GROUP BY parent_schema, parent_table 
)
 UPDATE admin.table_mapping tm 
 SET SIZE_IN_MB = ps.size_mb 
 FROM partition_sizes ps 
 WHERE tm.OWNER = ps.parent_schema 
 AND tm.OBJECT_NAME = ps.parent_table ;")
 echo "$pop_part"


 echo -e "\nUpdate the PK_PRESENT field of the control table"
 pk_present=$(execute_query "UPDATE admin.table_mapping 
 SET PK_PRESENT = CASE 
 WHEN EXISTS (
 SELECT 1 FROM information_schema.table_constraints 
 WHERE table_schema = OWNER 
 AND table_name = OBJECT_NAME 
 AND constraint_type = 'PRIMARY KEY'
) THEN 'YES' ELSE 'NO' END ;")
 echo "$pk_present"

 echo -e "\nUpdate the UK_PRESENT field of the control table "
 uk_present=$(execute_query "UPDATE admin.table_mapping 
 SET UK_PRESENT = CASE 
 WHEN EXISTS (
 SELECT 1 FROM information_schema.table_constraints 
 WHERE table_schema = OWNER 
 AND table_name = OBJECT_NAME 
 AND constraint_type = 'UNIQUE'
) THEN 'YES' ELSE 'NO' END ;")
 echo "$uk_present"

 echo -e "\nUpdate the LOB_COLUMN field of the control table  "
 lob_column=$(execute_query "UPDATE admin.table_mapping 
 SET LOB_COLUMN = (
 SELECT COUNT(*) 
 FROM information_schema.columns 
 WHERE table_schema = OWNER 
 AND table_name = OBJECT_NAME 
 AND data_type IN ('bytea', 'text', 'json', 'jsonb')
);")
 echo "$lob_column"

 echo -e "\nCreate a staging table called MST_DBA_TAB_MOD to get the details from pg_stat_user_tables"
 table_ddl=$(execute_query "CREATE TABLE admin.mst_dba_tab_mod (
 DATA_DATE DATE,
 TABLE_OWNER VARCHAR(128),
 TABLE_NAME VARCHAR(128),
 PARTITION_NAME VARCHAR(128),
 SUBPARTITION_NAME VARCHAR(128),
 INSERTS BIGINT,
 UPDATES BIGINT,
 DELETES BIGINT,
 TIMESTAMP TIMESTAMP,
 TRUNCATED VARCHAR(3),
 TOTAL_DML BIGINT,
 DROP_SEGMENTS BIGINT 
);")
 echo "$table_ddl"

 echo -e "\nPopulate the MST_DBA_TAB_MOD table with the daily average DML count for the tables "
 table_dml=$(execute_query "INSERT INTO admin.mst_dba_tab_mod (
 TABLE_OWNER,
 TABLE_NAME,
 INSERTS,
 UPDATES,
 DELETES,
 TOTAL_DML,
 TIMESTAMP 
)
 SELECT 
 schemaname AS TABLE_OWNER,
 relname AS TABLE_NAME,
 n_tup_ins AS INSERTS,
 n_tup_upd AS UPDATES,
 n_tup_del AS DELETES,
 ROUND((n_tup_ins + n_tup_upd + n_tup_del)::numeric /
 GREATEST(EXTRACT(EPOCH FROM (now() - last_autovacuum))::numeric / 86400, 1)) AS TOTAL_DML,
 now() AS TIMESTAMP 
 FROM pg_stat_user_tables 
 WHERE schemaname = 'admin';")
 echo "$table_dml"

 echo -e "\nPopulate the DML information in the control table_mapping_mapping table "
 load_dml=$(execute_query "UPDATE admin.table_mapping a 
 SET TOTAL_DML = b.TOTAL_DML 
 FROM admin.mst_dba_tab_mod b 
 WHERE a.OWNER = b.TABLE_OWNER 
 AND a.OBJECT_NAME = b.TABLE_NAME 
 AND b.TABLE_OWNER = 'admin';
")
 echo "$load_dml"

 echo -e "\nCategorize the tables by step number"
 update_dml=$(execute_query "UPDATE admin.table_mapping SET STEP = 1 WHERE LOB_COLUMN = 0 AND PARTITIONED = 'NO' AND STEP IS NULL ;")
 echo "$update_dml"
 update_dml=$(execute_query "UPDATE admin.table_mapping SET STEP = 2 WHERE LOB_COLUMN > 0 AND PARTITIONED = 'NO' AND STEP IS NULL ;")
 echo "$update_dml"
 update_dml=$(execute_query "UPDATE admin.table_mapping SET STEP = 3 WHERE LOB_COLUMN = 0 AND PARTITIONED = 'YES' AND STEP IS NULL ;")
 echo "$update_dml"
 update_dml=$(execute_query "UPDATE admin.table_mapping SET STEP = 4 WHERE LOB_COLUMN > 0 AND PARTITIONED = 'YES' AND STEP IS NULL ;")
 echo "$update_dml"
 update_dml=$(execute_query "UPDATE admin.table_mapping SET STEP = 5 WHERE TOTAL_DML > 9999999 ;")
 echo "$update_dml"
 update_dml=$(execute_query "UPDATE admin.table_mapping SET STEP = 0 WHERE SIZE_IN_MB = 0 AND STEP IS NULL ;")
 echo "$update_dml"

# Clear password from environment 
 unset PGPASSWORD 

 echo -e "\nScript execution completed"

以下はスクリプトを実行した結果で、簡単に参照できるように実行のさまざまなステップを示しています。

[ec2-user@ip-10-0-0-40 ~ ]$ ./pg_table_grouping.sh 
 Please enter database connection details:
 Database name [postgres]: 
 Database user [postgres]: 
 Database host [localhost]: pg-dms-target.caub0zqkqtdt.us-east-1.rds.amazonaws.com 
 Database port [5432]: 
 Database password: 
 Testing database connection...
 Connection successful!

 Creating the control table 
 ERROR:  relation "table_mapping" already exists 


 Cleaning up the old control table 
 TRUNCATE TABLE 

 Insert into the control table the database table details 
 INSERT 0 287 

 Cleaning up child table data 
 DELETE 192 

 Populate partition table size 
 UPDATE 19 

 Update the PK_PRESENT field of the control table 
 UPDATE 95 

 Update the UK_PRESENT field of the control table 
 UPDATE 95 

 Update the LOB_COLUMN field of the control table  
 UPDATE 95 

 Create a staging table called MST_DBA_TAB_MOD to get the details from pg_stat_user_tables 
 ERROR:  relation "mst_dba_tab_mod" already exists 

 Populate the MST_DBA_TAB_MOD table with the daily average DML count for the tables 
 INSERT 0 287 

 Populate the DML information in the control table_mapping_mapping table 
 UPDATE 95 

 Categorize the tables by step number 
 UPDATE 56 
 UPDATE 20 
 UPDATE 6 
 UPDATE 13 
 UPDATE 0 
 UPDATE 0 

 Script execution completed

以下のコードは、コントロールテーブル table_mapping のスニペットです。このテーブルは、このセクションで先ほど示したシェルスクリプトを実行した後、データディクショナリから入力されます。

owner |              object_name               | object_type | size_in_mb | step | ignore | partitioned | part_num | special_handling 
| pk_present | uk_present | lob_column | groupnum | total_dml 
-------+----------------------------------------+-------------+------------+------+--------+-------------+----------+------------------
+------------+------------+------------+----------+-----------
 admin | bonus_1                                | TABLE       |     0.0000 |    1 |        | NO          |        1 |                  
| NO         | NO         |          0 |          |         0 
 admin | bonus                                  | TABLE       |     0.0000 |    1 |        | NO          |        1 |                  
| NO         | NO         |          0 |          |         0 
 admin | bonus_cap                              | TABLE       |     0.0000 |    1 |        | NO          |        1 |                  
| NO         | NO         |          0 |          |         0 
 admin | bonus_2                                | TABLE       |     0.0000 |    1 |        | NO          |        1 |                  
| NO         | NO         |          0 |          |         0 
 admin | bonus_3                                | TABLE       |     0.0000 |    1 |        | NO          |        1 |                  
| NO         | NO         |          0 |          |         0 
 admin | canada                                 | TABLE       |     0.0156 |    1 |        | NO          |        1 |                  
| YES        | NO         |          0 |          |         0 
 admin | apple                                  | TABLE       |     0.0156 |    1 |        | NO          |        1 |                  
| YES        | NO         |          0 |          |         0 
 admin | austin                                 | TABLE       |     0.0156 |    1 |        | NO          |        1 |                  
| YES        | NO         |          0 |          |         0 
 admin | dept_2                                 | TABLE       |     0.0078 |    1 |        | NO          |        1 |                  
| YES        | NO         |          0 |          |         0 
 admin | dept_1                                 | TABLE       |     0.0078 |    1 |        | NO          |        1 |                  
| YES        | NO         |          0 |          |         0 
 admin | dept_3                                 | TABLE       |     0.0078 |    1 |        | NO          |        1 |                  
| YES        | NO         |          0 |          |         0 
 admin | dept_cap                               | TABLE       |     0.0078 |    1 |        | NO          |        1 |                  
| YES        | NO         |          0 |          |         0 
 admin | dummy                                  | TABLE       |     0.0078 |    1 |        | NO          |        1 |                  
| NO         | NO         |          0 |          |         0 
 admin | mango                                  | TABLE       |     0.0078 |    1 |        | NO          |        1 |                  
| YES        | NO         |          0 |          |         0 
 admin | lob_col_info                           | TABLE       |     0.0078 |    1 |        | NO          |        1 |                  
| NO         | NO         |          0 |          |         0 
 admin | emp                                    | TABLE       |     0.0000 |    1 |        | NO          |        1 |                  
| NO         | NO         |          0 |          |         0 
 admin | emp_1                                  | TABLE       |     0.0078 |    1 |        | NO          |        1 |                  
| YES        | NO         |          0 |          |         0 
 admin | emp_3                                  | TABLE       |     0.0078 |    1 |        | NO          |        1 |

以下は、table_mappingを出力したスクリーンショットです。:

テーブルグループ化スクリプトを実行すると、最終的な出力は6つの初期ステップ番号、0 – 5 で構成されます:

  • 0 は特別な処理を行うテーブルを無視またはスキップする場合
  • 1 は LOB フィールドのない非パーティション化テーブルの場合
  • 2 は LOB フィールドのある非パーティション化テーブルの場合
  • 3 は LOB フィールドのないパーティション化テーブルの場合
  • 4 は LOB フィールドのあるパーティション化テーブルの場合
  • 5 は DML 操作が多いテーブルの場合

次に、テーブルグループ化シェルスクリプトを実行して、最終的なテーブルごとのグループを作成します。グループの一部であるテーブルは、単一の AWS DMS タスクに含まれます。このコードの詳細については、このブログ投稿の前のセクションを参照してください。

以下のシェルスクリプトは、テーブルの最終的なグループ化を作成します:

#!/bin/bash 

# Collect database connection details 
 echo "Please enter database connection details:"
 read -p "Database name [postgres]: " DB_NAME 
 DB_NAME=${DB_NAME:-postgres}

 read -p "Database user [postgres]: " DB_USER 
 DB_USER=${DB_USER:-postgres}

 read -p "Database host [localhost]: " DB_HOST 
 DB_HOST=${DB_HOST:-localhost}

 read -p "Database port [5432]: " DB_PORT 
 DB_PORT=${DB_PORT:-5432}

 read -s -p "Database password: " password 
 DB_PASSWORD=$password 

# Export variables for psql to use 
 export PGPASSWORD="$DB_PASSWORD"

# Function to execute psql query and return result 
 execute_query() {
    psql -U "$DB_USER" \ 
         -h "$DB_HOST" \ 
         -p "$DB_PORT" \ 
         -d "$DB_NAME" \ 
         -t -A \ 
         -c "$1"
}

# Test connection 
 echo -e "\nTesting database connection..."
 if ! execute_query "SELECT 1 ;" > /dev/null 2>&1 ; then 
    echo "Error: Could not connect to the database. Please check your credentials."
    exit 1 
 fi 
 echo "Connection successful!"

# Step 1: Create the plpgsql procedure 
 echo -e "\nCreating plpgsql procedure..."
 create_procedure_query=$(cat << EOF CREATE OR REPLACE PROCEDURE admin.both ( IN p_n NUMERIC, IN p_schema_name VARCHAR, IN p_table_name VARCHAR ) AS \$BODY\$ DECLARE vTab CHARACTER VARYING(30); vDumpSize DOUBLE PRECISION := 0 ; vSumBytes DOUBLE PRECISION := 0 ; vGroupNum DOUBLE PRECISION := 0 ; vPrevStep DOUBLE PRECISION := 1 ; reggrouptabs RECORD ; BEGIN vDumpSize := 1024 * p_n ; -- Dynamic SQL to truncate the table EXECUTE format('TRUNCATE %I.%I_groups', p_schema_name, p_table_name); -- Dynamic SQL to insert data EXECUTE format(' INSERT INTO %I.%I_groups SELECT owner, object_name, object_type, size_in_mb, step, ignore, partitioned FROM %I.%I', p_schema_name, p_table_name, p_schema_name, p_table_name); FOR reggrouptabs IN EXECUTE format(' SELECT * FROM %I.%I_groups ORDER BY step, size_in_mb', p_schema_name, p_table_name) LOOP IF (reggrouptabs.step != vPrevStep) THEN vGroupNum := 0 ; vSumBytes := 0 ; END IF ; vSumBytes := vSumBytes + reggrouptabs.size_in_mb ; IF (vSumBytes >= vDumpSize) THEN 
        vGroupNum := vGroupNum + 1 ;
        vSumBytes := 0 ;
    END IF ;

    -- Dynamic SQL for UPDATE 
    EXECUTE format('
        UPDATE %I.%I_groups 
        SET groupnum = \$1 
        WHERE owner = \$2 AND object_name = \$3', 
        p_schema_name, p_table_name)
    USING vGroupNum, reggrouptabs.owner, reggrouptabs.object_name ;

    vPrevStep := reggrouptabs.step ;
 END LOOP ;
 COMMIT ;
 END ;
 \$BODY\$
 LANGUAGE plpgsql ;
 EOF 
)

 execute_query "$create_procedure_query"
 echo "Procedure created successfully!"

# Step 2: Execute the plpgsql procedure 
 echo -e "\nExecuting the procedure..."
 read -p "Enter p_n value: " p_n 
 read -p "Enter p_schema_name: " p_schema_name 
 read -p "Enter p_table_name: " p_table_name 

 execute_procedure_query="CALL admin.both($p_n, '$p_schema_name', '$p_table_name');"
 execute_query "$execute_procedure_query"
 echo "Procedure executed successfully!"

# Step 3: Execute the SELECT query and display results 
 echo -e "\nExecuting SELECT query..."
 select_query="
 SELECT partitioned, step, groupnum as substep, 
       count(1) as table_in_group,
       round(sum(size_in_mb)/1024, 2) as Total_size_GB 
 FROM admin.table_mapping_groups 
 GROUP BY partitioned, step, groupnum 
 ORDER BY 1,2,3 ;
"

 echo "
*********************************************************
 Tables are grouped based on ${p_n}GB size 
*********************************************************
 1(10,11,...) --> Non Partition Table.
 2(20,21....) --> Non Partition Table(LOB)
 3(30,31...)  --> Partition Table.
 4(40,41...)  --> Partition Table(LOB).
 5(50,51...)  --> High DML Table.
 0(0,99)     --> Ignore or Skip/Special Handling table.
**********************************************************
"

# First display the traditional output 
 result=$(execute_query "$select_query")
 echo -e "\nQuery Results:"
 echo -e "partitioned\tstep\tsubstep\ttable_in_group\tTotal_size_GB"
 echo "$result" | sed 's/|/\t/g'

# Now generate the formatted output 
 echo -e "\nDetailed Group Summary:"
 formatted_query="
 WITH grouped_data AS (
    SELECT 
        concat(step, groupnum) as group_number,
        count(1) as table_count,
        round(sum(size_in_mb)/1024, 2) as size_gb,
        CASE 
            WHEN step = 0 THEN ' comprising of ignored objects'
	    WHEN step = 1 THEN ' with no partitions'
            WHEN step = 2 THEN ' with no partitions but containing lob fields'
            WHEN step = 3 THEN ' with partitions'
            WHEN step = 4 THEN ' with partitions and containing lob fields'
            WHEN step = 5 THEN ' with high DML change'
            ELSE ''
        END as description 
    FROM admin.table_mapping_groups 
    GROUP BY step, groupnum 
    ORDER BY concat(step, groupnum)
)
 SELECT format('Group number %s will have %s table%s with size of %s GB%s.',
              group_number,
              table_count,
              CASE WHEN table_count = 1 THEN '' ELSE 's' END,
              to_char(size_gb, 'FM999,999'),
              description)
 FROM grouped_data 
 WHERE group_number != '099';
"

 execute_query "$formatted_query" | while read -r line ; do 
    echo "$line"
 done 

# Clear password from environment 
 unset PGPASSWORD 

 echo -e "\nScript execution completed"

この plpgsql プロシージャは、データテーブルの移行に関する最終的な推奨事項を生成します。これには、以下のステップを含む plpgsql ブロックの実行が含まれます:

  1. 既存の table_mapping_groups という名前のテーブルを確認し、削除して新しいテーブルを作成します。
  2. TABLE_MAPPING から新しい table_mapping_groups テーブルを作成します。
  3. LOB 列と非 LOB 列を持つパーティション化されたテーブルを、600 GB のグループサイズに基づいてグループ化し、10、11、12、13 などのグループを作成します。
  4. 同様の手順で、LOB 列と非 LOB 列を持つ非パーティション化テーブルをグループ化します。

このプロセスでは、特別な処理が必要なテーブルを個別のグループに分けたり、特定の要件に基づいて移行から除外したりすることで対応します。ここで説明する移行プロセスはユニークであり、ケースの要件によって異なる場合があります。以下のコード例では、テーブルを ${groupsize} の 600 GB に基づいてグループ化しています:

[ec2-user@ip-10-0-0-40 ~ ]$ ./pg_both.sh 
 Please enter database connection details:
 Database name [postgres]: 
 Database user [postgres]: 
 Database host [localhost]: pg-dms-target.caub0zqkqtdt.us-east-1.rds.amazonaws.com 
 Database port [5432]: 
 Database password: 
 Testing database connection...
 Connection successful!

 Creating plpgsql procedure...
 CREATE PROCEDURE 
 Procedure created successfully!

 Executing the procedure...
 Enter p_n value: 600 
 Enter p_schema_name: admin 
 Enter p_table_name: table_mapping 
 CALL 
 Procedure executed successfully!

 Executing SELECT query...

*********************************************************
 Tables are grouped based on 600GB size 
*********************************************************
 1(10,11,...) --> Nonpartitioned table 
 2(20,21....) --> Nonpartitioned table(LOB)
 3(30,31...)  --> Partitioned table 
 4(40,41...)  --> Partitioned table(LOB)
 5(50,51...)  --> High DML table 
 0(0,99)     --> Ignore or skip/special handling table 
**********************************************************

 Query Results:
 partitioned step    substep table_in_group  Total_size_GB 
 NO  0   0   2   0.00 
 NO  1   0   52  332.71 
 NO  1   1   2   837.89 
 NO  2   0   13  413.36 
 NO  2   1   2   467.77 
 NO  2   2   1   408.13 
 NO  2   3   1   648.07 
 NO  2   4   1   1440.51 
 NO  2   5   1   1620.33 
 NO  5   0   1   7.57 
 YES 3   0   6   0.62 
 YES 4   0   12  147.91 
 YES 5   0   1   28.99 

 Detailed Group Summary:
 Group number 00 will have 2 tables with size of 0 GB comprising ignored objects.
 Group number 10 will have 52 tables with size of 333 GB with no partitions.
 Group number 11 will have 2 tables with size of 838 GB with no partitions.
 Group number 20 will have 13 tables with size of 413 GB with no partitions but containing lob fields.
 Group number 21 will have 2 tables with size of 468 GB with no partitions but containing lob fields.
 Group number 22 will have 1 table with size of 408 GB with no partitions but containing lob fields.
 Group number 23 will have 1 table with size of 648 GB with no partitions but containing lob fields.
 Group number 24 will have 1 table with size of 1,441 GB with no partitions but containing lob fields.
 Group number 25 will have 1 table with size of 1,620 GB with no partitions but containing lob fields.
 Group number 30 will have 6 tables with size of 1 GB with partitions.
 Group number 40 will have 12 tables with size of 148 GB with partitions and containing lob fields.
 Group number 50 will have 2 tables with size of 37 GB with high DML change.

plpgsql プロシージャが異なるステップとグループ(substep) 毎にテーブルをグループ化したので、次に各グループに含まれるテーブルのリストを生成する必要があります。これは AWS DMS タスクを作成するために使用できます。最終的なグループ番号は、stepsubstep の 2 つの列を連結して生成されます。例えば、ステップ 1 とサブステップ 0 を組み合わせるとグループ番号 10 となり、52 個のテーブルと 340 GB のサイズを持ちます。

同様に、以下のグループも作成されます:

Group number 00 will have 2 tables with size of 0 GB comprising of ignored objects.
 Group number 10 will have 52 tables with size of 333 GB with no partitions.
 Group number 11 will have 2 tables with size of 838 GB with no partitions.
 Group number 20 will have 13 tables with size of 413 GB with no partitions but containing lob fields.
 Group number 21 will have 2 tables with size of 468 GB with no partitions but containing lob fields.
 Group number 22 will have 1 table with size of 408 GB with no partitions but containing lob fields.
 Group number 23 will have 1 table with size of 648 GB with no partitions but containing lob fields.
 Group number 24 will have 1 table with size of 1,441 GB with no partitions but containing lob fields.
 Group number 25 will have 1 table with size of 1,620 GB with no partitions but containing lob fields.
 Group number 30 will have 6 tables with size of 1 GB with partitions.
 Group number 40 will have 12 tables with size of 148 GB with partitions and containing lob fields.

前述の出力では、テーブルは可能な限り希望のグループサイズである 600 GB に近くなるようにグループ化されています。例えば、グループ 10 には 52 個のテーブルが含まれており、合計サイズは 333 GB です。838 GB のサイズの 2 つの巨大なテーブルは、単一のテーブルを分割しないように、グループ 11 として別の AWS DMS タスクに保持されています。テーブルの数とサイズに応じて、一部のグループは希望のグループサイズである 600 GB よりも小さくなったり大きくなったりします。したがって、データベースはパーティション、非パーティション LOB、および DML ボリュームに基づいてグループ化することで 11 のタスクを持つことになります。

次に、以下のスクリプトを使用して、異なるグループの下にあるテーブルをそれぞれのファイルに抽出します:

#!/bin/bash 

# Collect database connection details 
 echo "Please enter database connection details:"
 read -p "Database name [postgres]: " DB_NAME 
 DB_NAME=${DB_NAME:-postgres}

 read -p "Database user [postgres]: " DB_USER 
 DB_USER=${DB_USER:-postgres}

 read -p "Database host [localhost]: " DB_HOST 
 DB_HOST=${DB_HOST:-localhost}

 read -p "Database port [5432]: " DB_PORT 
 DB_PORT=${DB_PORT:-5432}

 read -p "Schema name for table_mapping_groups: " usr 
 usr=${usr:-admin}

 read -s -p "Database password: " password 
 DB_PASSWORD=$password 

# Export variables for psql to use 
 export PGPASSWORD="$DB_PASSWORD"

# Function to execute psql query and return result 
 execute_query() {
    psql -U "$DB_USER" \ 
         -h "$DB_HOST" \ 
         -p "$DB_PORT" \ 
         -d "$DB_NAME" \ 
         -t -A \ 
         -c "$1"
}

# Test connection 
 echo -e "\nTesting database connection..."
 if ! execute_query "SELECT 1 ;" > /dev/null 2>&1 ; then 
    echo "Error: Could not connect to the database. Please check your credentials."
    exit 1 
 fi 
 echo "Connection successful!"


# Generate parfile equivalent 
 echo "Generating step/group combinations..."
 execute_query "SELECT DISTINCT concat(step, groupnum) 
              FROM ${usr}.table_mapping_groups 
              WHERE owner = '${usr}' 
              AND step IS NOT NULL" > parfile.lst 

# Remove any existing files 
 rm -rf ADMIN_TAB*

# Process each combination 
 while IFS= read -r combination 
 do 
    echo "Generating table list for step and group ${combination}"

    execute_query "
    SELECT object_name 
    FROM ${usr}.table_mapping_groups 
    WHERE concat(step, groupnum) = '${combination}'
    " > "${usr}_TAB_${combination}.lst"

 done < parfile.lst 

# Clear password from environment 
 unset PGPASSWORD 

 echo -e "\nScript execution completed"

スクリプトの実行後、テーブル名を含む以下のファイルが生成されます:

-rw-rw-r--  1 ec2-user ec2-user       16 Jul 22 00:04 admin_TAB_21.lst 
-rw-rw-r--  1 ec2-user ec2-user      584 Jul 22 00:04 admin_TAB_10.lst 
-rw-rw-r--  1 ec2-user ec2-user       43 Jul 22 00:04 admin_TAB_11.lst 
-rw-rw-r--  1 ec2-user ec2-user       13 Jul 22 00:04 admin_TAB_00.lst 
-rw-rw-r--  1 ec2-user ec2-user       20 Jul 22 00:04 admin_TAB_50.lst 
-rw-rw-r--  1 ec2-user ec2-user        9 Jul 22 00:04 admin_TAB_24.lst 
-rw-rw-r--  1 ec2-user ec2-user      115 Jul 22 00:04 admin_TAB_30.lst 
-rw-rw-r--  1 ec2-user ec2-user      232 Jul 22 00:04 admin_TAB_40.lst 
-rw-rw-r--  1 ec2-user ec2-user        8 Jul 22 00:04 admin_TAB_23.lst 
-rw-rw-r--  1 ec2-user ec2-user      165 Jul 22 00:04 admin_TAB_20.lst 
-rw-rw-r--  1 ec2-user ec2-user        9 Jul 22 00:04 admin_TAB_25.lst 
-rw-rw-r--  1 ec2-user ec2-user        9 Jul 22 00:04 admin_TAB_22.lst

次のコードに示すように、同じファイルの一部であるテーブル名は、単一のタスクにグループ化する必要があります:

[ec2-user@ip-10-0-0-40 ~ ]$ cat admin_TAB_00.lst 
 canada 
 usail 

 [ec2-user@ip-10-0-0-40 ~ ]$ cat admin_TAB_22.lst 
 montreal 

 [ec2-user@ip-10-0-0-40 ~ ]$ cat admin_TAB_25.lst 
 edmonton 

 [ec2-user@ip-10-0-0-40 ~ ]$ cat admin_TAB_20.lst 
 print_media_hash_cap_a 
 d_storage 
 t 
 print_media_cap_dal 
 print_media 
 print_media_cap 
 tbl_clob 
 print_media_cap_a 
 document_storage 
 doc_storage 
 doc_store 
 storeage 
 quebec 

クリーンアップ

このデモンストレーションの一環として、ソースの PostgreSQL データベースにいくつかのテーブルと 1 つのプロシージャを作成しました。タスクのグループ化が記録された後、以下の DROP ステートメントを使用して、これらのテーブルを削除し、プロシージャをドロップすることができます:

DROP TABLE TABLE_MAPPING ;

 DROP PROCEDURE admin.both ;

 DROP TABLE MST_DBA_TAB_MOD ;

 DROP TABLE TABLE_MAPPING_GROUPS ;

まとめ

この投稿では、システムカタログと統計ビューを活用して、データベースの規模、運用負荷、ハードウェア構成を調査する方法について説明しました。これらの方法は、AWS DMS を使用した効果的なデータベース転送に必要なタスク数とテーブルクラスターの理想的な数を決定するのに役立ちます。データベースオブジェクト情報とソースデータベースのハードウェア仕様を統合することで、移行設計フェーズで十分な情報に基づいた選択を行うことができます。移行タスクを最適化するために、AWS Database Migration Service ユーザーガイドのベストプラクティスも併せて確認することをお勧めします。

著者について

Manojit Saha Sardar

Manojit Saha Sardar

Manojit はAWSのシニアデータベースエンジニアで、AWS DMS、Amazon RDS、および Amazon RDS for PostgreSQL 分野のエキスパートです。AWS での職務は、クライアントと協力してさまざまなデータ移行シナリオに取り組み、Amazon RDS for Oracle および Amazon RDS for PostgreSQL に関連する課題の解決をサポートしています。

Chirantan Pandya

Chirantan Pandya

Chirantan はデータベースエンジニア(AWS カウントダウンプレミアム)であり、AWS DMS と Amazon RDS for PostgreSQL 分野のエキスパートです。AWS では、顧客と緊密に連携してデータベース移行プロジェクトや Amazon RDS for PostgreSQL、Oracle に関するガイダンスや技術支援を提供しています。