Amazon Web Services ブログ

AWS SCT および AWS DMS を使用した移行後のデータベースオブジェクトの検証

データベースの移行は複雑なタスクになりかねません。移行には、ソフトウェアプラットフォームの変更、ソースデータの複雑性の把握、データ損失チェック、既存機能の詳細なテスト、アプリケーションパフォーマンスの比較、およびデータの検証といったあらゆる課題が伴います。

AWS では、移行前チェックリストと移行評価を提供するツールとサービスをいくつかご用意しています。AWS Schema Conversion Tool (AWS SCT) は、既存のデータベーススキーマをひとつのデータベースエンジンから別のデータベースエンジンに変換するために使用できます。AWS Database Migration Service (AWS DMS) は、リレーショナルデータベース、データウェアハウス、NoSQL データベース、およびその他のタイプのデータストアの移行を容易にしてくれます。AWS DMS は、AWS クラウドへのデータの移行、またはオンプレミスインスタンス間 (AWS クラウドセットアップ経由)、クラウドとオンプレミスセットアップとの組み合わせの間でのデータの移行に使用できます。

さらに、AWS はデータベース移行の全体を通じてユーザーをガイドする幅広いドキュメントも提供しています。詳細については、「Oracle データベースの PostgreSQL への移行」を参照してください。

AWS SCT は、スキーマオブジェクトを変換するために役立ち、AWS SCT が PostgreSQL に変換した Oracle コードの割合と、手動で変換する必要があるコードの量を示すレポートを構築します。データベースオブジェクトの移行中は常に、ターゲットデータベースでオブジェクトが欠落している、新しいオブジェクトを作成する、またはソースオブジェクト全体を意図的に無視する可能性のリスクがあります。検証は、移行対象のすべてが正常に移行されたことを顧客に証明するプロセスです。

この記事は、データベースオブジェクトの移行とコードの変換の完了後に、ソース Oracle データベースと PostgreSQL ターゲット間でオブジェクトを検証する方法の概要を説明します。

オブジェクトの検証

問題になるのは、何を検証するかということです。 これを理解するためには、Oracle データベースオブジェクトの異なるタイプと、それらに相当する PostgreSQL データベースオブジェクトのタイプを知っておく必要があります。

以下の表は、Oracle (ソース) データベースのオブジェクトと、対応する PostgreSQL (ターゲット) のオブジェクトのタイプを示しています。DB 変換が正常に行われたことを確認するには、これらのオブジェクトを詳細に検証しなければなりません。

Oracle オブジェクト PostgreSQL に以下として移行
TABLE TABLE
VIEW VIEW
MATERIALIZED VIEW MATERIALIZED VIEW
SEQUENCE SEQUENCE
SYNONYM PostgreSQL では使用されません。PostgreSQL では無視されますが、View を通じて部分的に変換できます。SET search_path はシノニムの回避策にすることもできます。
TYPE DOMAIN/ TYPE
FUNCTION FUNCTION
PROCEDURE PostgreSQL では使用されません。PostgreSQL 関数を通じて変換できます。
PACKAGE PostgreSQL では使用されません。関数をグループ別に分類する PostgreSQL スキーマを通じて変換できます。
TRIGGER TRIGGER

スキーマ検証の実行方法

データベース移行には数多くのタスク、ツール、およびプロセスが関与します。実行したい Oracle データベース移行の規模とタイプが、使用すべきツールを大きく左右します。例えば、Oracle データベースから AWS 上の異なるデータベースエンジンへの異種間移行の実行には、AWS DMS の使用が最も適しています。Oracle データベースから AWS 上の Oracle データベースへの同種間移行の実現には、ネイティブの Oracle ツールの使用が最も適しています。

以下のタスクリストは、移行プロセス中のどのタイミングでスキーマ検証を行うべきかを示しています。

  • タスク 1: Oracle ソースデータベースを設定する
  • タスク 2: PostgreSQL ターゲットデータベースを設定する
  • タスク 3: AWS SCT を使用して Oracle スキーマオブジェクトを PostgreSQL に変換する
  • タスク 4: AWS SCT が変換できなかった Oracle スキーマオブジェクトを手動で変換する
  • タスク 5: AWS DMS を使用して Oracle から PostgreSQL にデータを移行する
  • タスク 6: スキーマオブジェクト検証を実行する

スキーマ変換を検証するには、クエリエディタを使用して Oracle データベースと PostgreSQL データベースにあるオブジェクトを比較します。

標準的な検証方法では、ソースデータベース内のオブジェクト数とターゲットデータベース内のオブジェクト数を比較します。数の検証はどのスキーマオブジェクトでも実行できますが、数の検証だけではエンドユーザーを満足させることができるとは限りません。ユーザーは、オブジェクト定義レベルの検証を求めていることが多々あります。データベースからデータ定義言語 (DDL) を取得するカスタムクエリを作成して、それらを比較する必要があるのはこのためです。

スキーマオブジェクト検証クエリ

Oracle は、LONG データ型を使用して大きな文字セット (上限 2 GB の最大サイズ) を保存します。Oracle の LONG データ型は、多くの Oracle メタデータテーブルまたはビューに表示されます。このトピックの後半で説明する検証クエリで使用されたいくつかの Oracle メタデータテーブルには、LONG データ型が含まれています。他のデータ型とは異なり、Oracle データベースの LONG データ型は同じように動作せず、いくつかの制限の対象となります。

LONG 型に対する制限には以下が含まれます。

  • LONG 列は WHERE 句に使用できない。
  • 正規表現で LONG データを指定できない。
  • ストアド関数は LONG 値を返すことができない。
  • LONG データ型を使用して PL/SQL プログラムユニットの変数または引数を宣言することはできるが、その後 SQL からプログラムユニットを呼び出すことはできない。

このトピックで説明する Oracle 検証クエリは、Oracle LONG データ型を異なる方法で処理するために DBMS_XMLGEN パッケージを使用し、いくつかの制限を回避するために役立ちます。DBMS_XMLGEN パッケージは、入力として任意の SQL クエリを使用し、レコードセット全体を XML 形式に変換して、CLOB データ型として結果を返すため、データの処理と操作が簡単になります。

ステップ 1: Oracle パッケージの検証

PostgreSQL スキーマでは、Oracle でパッケージが関数をグループ化するのと同じ方法で、関連するすべての関数を保存することができます。Oracle パッケージを PostgreSQL スキーマとして移行することは、不必要なアプリケーション変更の回避に役立ちます。Oracle データベース関数にアクセスするアプリケーションが、同じ方法で PostgreSQL の個々の関数にアクセスできるからです。例えば、次のようになります。

  • 移行前のソース Oracle データベース: アプリケーションが PackageName.FunctionName としての Oracle パッケージ関数にアクセスします。
  • 移行後のターゲット PostgreSQL データベース: アプリケーションは SchemaName.FunctionName としての PostgreSQL 関数にアクセスできます。

10 個のパッケージとその他のオブジェクトが含まれる Oracle に 1 人のユーザーがいると仮定します。10 個すべてのパッケージが PostgreSQL スキーマとして移行され、Oracle ユーザー自身はその他すべてのオブジェクトを保存する PostgreSQL 内の別のスキーマになります。このため、移行後の PostgreSQL には、11 個 (10 個のパッケージ = 10 スキーマ、1 人のユーザー = 1 スキーマ) のスキーマがあることになります。

すべての Oracle パッケージが PostgreSQL 内のスキーマとして移行されかどうかを検証するには、以下のクエリを使用します。

Oracle

SELECT object_name AS package_name
  FROM all_objects 
 WHERE object_type = 'PACKAGE'
   AND owner = upper('your_schema')
ORDER BY upper(object_name);

PostgreSQL

SELECT upper(schema_name) AS package_name
  FROM information_schema.schemata
 WHERE schema_name not in('pg_catalog','information_schema', lower('your_schema'))
ORDER BY upper(schema_name);
  • クエリ例の「your_schema」は、Oracle ユーザー名に置き換えてください。
  • ソースデータベースでオブジェクトを移行から除外する、またはターゲットデータベースにオブジェクトを導入する場合は、この前、およびこの後のクエリに WHERE 句フィルターを追加することによって、これらのオブジェクトが計上されないようにします。

ステップ 2: テーブルの検証

AWS SCT は、適切な構造で Oracle テーブルを変換し、ターゲット PostgreSQL データベースにデプロイする、または変換されたコードを .sql ファイルとして保存するオプションを提供します。ソースとターゲット両方のデータベースで数をチェックすることによって、すべてのテーブルがターゲット PostgreSQL データベースに移行されたかどうかを検証する必要があります。以下のスクリプトは、全てのテーブルで数の検証を実行します。

 Oracle

SELECT count(1) AS tables_cnt
  FROM all_tables
 WHERE owner = upper('your_schema');

PostgreSQL

SELECT count(1) AS tables_cnt
  FROM pg_tables
 WHERE schemaname = lower('your_schema');

ステップ 3: View の検証

AWS SCT は、Oracle のビューコードの PostgreSQL 内のビューへの移行に役立ちます。移行後に、以下のスクリプトを使用して Oracle ソースデータベースと PostgreSQL ターゲットデータベースのビューの数を検証します。

Oracle

SELECT count(1) AS views_cnt
  FROM all_views
 WHERE owner = upper('your_schema');

PostgreSQL

SELECT count(1) AS views_cnt
  FROM pg_views
 WHERE schemaname = lower('your_schema');

ステップ 4: シーケンスの検証

移行がどのシーケンスも移行し損なっていないことを確実にするには、ソースとターゲット両方のシーケンス数が一致する必要があります。以下のスクリプトを使用して、PostgreSQL への移行後のシーケンス数を検証します。

Oracle

SELECT count(1) AS sequence_cnt
  FROM all_sequences
 WHERE sequence_owner = upper('your_schema');

PostgreSQL

SELECT count(1) AS sequence_cnt
  FROM information_schema.sequences
 WHERE sequence_schema = lower('your_schema');

ステップ 5: トリガーの検証

数の検証は、どのデータベースオブジェクトでも実行できます。データベースのトリガーが重要であることは明らかなので、トリガーのすべての属性を検証することが必要不可欠です。以下のスクリプトを使用して、PostgreSQL への移行後のトリガーを検証します。

Oracle

SELECT 
       owner AS schema_name,
       trigger_name,
       table_name,
       triggering_event,
       trigger_type
  FROM ALL_TRIGGERS
 WHERE owner = upper('your_schema')
ORDER BY trigger_name;

PostgreSQL

SELECT
       upper(trigger_schema)     AS schema_name,
       upper(trigger_name)       AS trigger_name,
       upper(event_object_table) AS table_name,
       string_agg(upper(event_manipulation), ' OR ' ORDER BY CASE WHEN  event_manipulation = 'INSERT' THEN 1 WHEN event_manipulation = 'UPDATE' THEN 2 ELSE 3 END) AS triggering_event,
       upper(action_timing) || ' ' || CASE WHEN action_orientation = 'ROW' THEN 'EACH ROW' ELSE action_orientation END AS trigger_type
  FROM information_schema.triggers
 WHERE trigger_schema = lower('your_schema')
GROUP BY trigger_schema, trigger_name, event_object_table, action_timing, action_orientation
ORDER BY upper(trigger_name);

ステップ 6: プライマリキーの検証

制約は、リレーショナルデータベースでのデータ整合性の維持に役立ちます。データベースの移行中は、制約を慎重に検証することが重要です。

プライマリキーは、ターゲットテーブルに同様の「primary key」制約を作成したかどうかをチェックすることで検証できます。これは、ソース Oracle データベースとターゲット PostgreSQL データベースの両方で列と列の順序が同じでなくてはならないことを意味します。ここで説明する検証は、プライマリキーの列と列の順序をチェックしません。プライマリキーは、デフォルトで Oracle と PostgreSQL の両方で UNIQUE インデックスを作成するため、インデックス検証中に、プライマリキーの列と列の順序を検証できます。以下のスクリプトを使用して、PostgreSQL への移行後のプライマリキーを検証します。

Oracle

SELECT owner           AS schema_name,
       table_name,
       constraint_name AS object_name,
       'PRIMARY KEY'   AS object_type
  FROM all_constraints
 WHERE owner = upper('your_schema') 
   AND constraint_type = 'P';

PostgreSQL

SELECT upper(n.nspname)        AS schema_name,
       trim(upper(conrelid::regclass::varchar), '"') AS table_name,
       upper(conname::varchar) AS object_name,
       'PRIMARY KEY'           AS object_type
  FROM pg_constraint c
       JOIN pg_namespace n ON n.oid = c.connamespace
 WHERE contype in ('p') 
   AND n.nspname = lower('your_schema');

ステップ 7: インデックスの検証

インデックスはデータベースの検索を迅速化し、インデックスの移行が良好に行われない、またはインデックスの作成が行われない場合、ターゲットデータベースのクエリに大きな影響を及ぼします。インデックスの比較は、各インデックス内の列の順序が重要であるため、簡単ではありません。列の順序が変更されると、インデックスが異なる動作をし、完全に新しいインデックスと見なされます。

リレーショナルデータベースのプライマリキーは、インデックスを内部で作成します。インデックスを検証するときは、プライマリキー用に黙示的に作成されたインデックスと、CREATE INDEX 構文によって明示的に作成されたインデックスを含める必要があります。以下のクエリは、インデックスのタイプと、すべてのインデックスのテーブルすべての列順を検証します。

Oracle

WITH cols AS (
    SELECT idx.owner AS schema_name, idx.table_name, idx.index_name, cols.column_name, cols.column_position, idx.uniqueness, decode(cols.descend, 'ASC', '', ' '||cols.descend) descend
      FROM ALL_INDEXES idx, ALL_IND_COLUMNS cols
     WHERE idx.owner = cols.index_owner AND idx.table_name = cols.table_name AND idx.index_name = cols.index_name
       AND idx.owner = upper('your_schema')
),
expr AS (
   SELECT extractValue(xs.object_value, '/ROW/TABLE_NAME')         AS table_name
   ,      extractValue(xs.object_value, '/ROW/INDEX_NAME')         AS index_name
   ,      extractValue(xs.object_value, '/ROW/COLUMN_EXPRESSION')  AS column_expression
   ,      extractValue(xs.object_value, '/ROW/COLUMN_POSITION')    AS column_position
  FROM (
         SELECT XMLTYPE(
              DBMS_XMLGEN.GETXML('SELECT table_name, index_name, column_expression, column_position FROM ALL_IND_EXPRESSIONS WHERE index_owner = upper(''your_schema'') '
              )
           ) AS xml FROM DUAL
       ) x
   , TABLE(XMLSEQUENCE(EXTRACT(x.xml, '/ROWSET/ROW'))) xs
)
SELECT 
       cols.schema_name,
       cols.table_name,
       cols.index_name AS object_name,
       'INDEX'         AS object_type,
       'CREATE'|| decode(cols.uniqueness, 'UNIQUE', ' '||cols.uniqueness) || ' INDEX ' || cols.index_name || ' ON VESTEK.' || cols.table_name || ' USING BTREE (' ||
            listagg(CASE WHEN cols.column_name LIKE 'SYS_N%' THEN expr.column_expression || cols.descend ELSE cols.column_name || cols.descend END, ', ') within group(order by cols.column_position) || ')' AS condition_column
FROM cols
     LEFT OUTER JOIN expr ON cols.table_name = expr.table_name
           AND cols.index_name = expr.index_name
           AND cols.column_position = expr.column_position
GROUP BY cols.schema_name, cols.table_name, cols.index_name, cols.uniqueness;

PostgreSQL

SELECT upper(schemaname) AS schema_name,
       upper(tablename)  AS table_name,
       upper(indexname)  AS object_name,
       'INDEX'           AS object_type,
       upper(indexdef)   AS condition_column
  FROM pg_indexes
 WHERE schemaname = lower('your_schema');

ステップ 8: Check 制約の検証

CHECK 制約の検証を実行するときは、条件付きで作成された制約のチェック、およびテーブル作成中に NOT NULL と宣言された列を含めるようにしてください。Check 制約は、それぞれのテーブル列で特定の条件を検証することによって検証できます。以下のスクリプトを使用して、PostgreSQL への移行後の check 制約を検証します。

Oracle

WITH ref AS (
 SELECT   extractValue(xs.object_value, '/ROW/OWNER')            AS schema_name
   ,      extractValue(xs.object_value, '/ROW/TABLE_NAME')       AS table_name
   ,      extractValue(xs.object_value, '/ROW/CONSTRAINT_NAME')  AS object_name
   ,      extractValue(xs.object_value, '/ROW/SEARCH_CONDITION') AS condition_column
   ,      extractValue(xs.object_value, '/ROW/COLUMN_NAME')      AS column_name
  FROM (
         SELECT XMLTYPE(
              DBMS_XMLGEN.GETXML('SELECT cons.owner, cons.table_name, cons.constraint_name, cons.search_condition, cols.column_name
                           FROM ALL_CONSTRAINTS cons, ALL_CONS_COLUMNS cols
                           WHERE cons.owner =  cols.owner AND cons.table_name =  cols.table_name AND cons.constraint_name = cols.constraint_name
                             AND cons.owner = upper(''your_schema'') AND cons.constraint_type = ''C'' '
                             )
           ) AS xml FROM DUAL
       ) x
   , TABLE(XMLSEQUENCE(EXTRACT(x.xml, '/ROWSET/ROW'))) xs
)
SELECT * FROM (
SELECT 
       schema_name,
       table_name,
       object_name,
       'CHECK'                AS object_type,
       condition_column       AS check_condition
FROM ref
UNION
SELECT 
       owner                  AS schema_name,
       table_name,
       'SYS_C0000'||column_id AS object_name,
       'CHECK'                AS object_type,
       '"'||column_name||'" IS NOT NULL' AS check_condition
FROM all_tab_columns tcols where owner = upper('your_schema') and nullable = 'N'
AND NOT EXISTS ( SELECT 1 FROM ref WHERE ref.table_name = tcols.table_name
                          AND ref.column_name = tcols.column_name
                          AND ref.condition_column = '"'||tcols.column_name||'" IS NOT NULL')
);

PostgreSQL

SELECT * FROM (
    SELECT 
           upper(n.nspname)                       AS schema_name,
           upper(c.relname)                       AS table_name,
           'SYS_C0000'||attnum                    AS object_name,
           'CHECK'                                AS constraint_type,
           '"'||upper(a.attname)||'" IS NOT NULL' AS check_condition
      FROM pg_attribute a, pg_class c, pg_namespace n
     WHERE a.attrelid = c.oid and c.relnamespace = n.oid AND n.nspname = 'vestek'
       AND attnotnull AND attstattarget <> 0
     UNION
    SELECT 
           upper(n.nspname),
           upper(conrelid::regclass::varchar)     AS table_name,
           upper(conname::varchar)                AS object_name,
           'CHECK'                                AS object_type,
           upper(pg_get_constraintdef(c.oid))     AS check_condition
      FROM pg_constraint c
           JOIN pg_namespace n ON n.oid = c.connamespace
     WHERE  contype in ('c')
       AND conrelid::regclass::varchar <> '-'
) a
WHERE schema_name = lower('your_schema');

ステップ 9: 外部キーの検証

外部キーは、別のテーブルのプライマリまたはユニークキーを参照することによって、リレーショナルテーブル間のリンクを維持するために役立ちます。以下のクエリは、外部キーの検証中に親テーブルと子テーブルの両方を考慮します。

Oracle

SELECT 
       c.owner           AS schema_name,
       c.table_name,
       c.constraint_name AS object_name,
       'FOREIGN KEY'     AS object_type,
       'FOREIGN KEY ('|| cc.fk_column || ') REFERENCES ' || p.table_name || '('|| pc.ref_column ||') NOT VALID' AS condition_column
FROM ( SELECT owner, table_name, constraint_name, r_constraint_name FROM ALL_CONSTRAINTS WHERE owner = upper('your_schema') AND constraint_type = 'R') c,
     ( SELECT table_name, constraint_name FROM ALL_CONSTRAINTS WHERE owner = upper('your_schema') AND constraint_type IN('P', 'U') ) p,
     ( SELECT owner, table_name, constraint_name, listagg(column_name, ', ') WITHIN group(ORDER BY position) fk_column
         FROM ALL_CONS_COLUMNS WHERE owner = upper('your_schema') GROUP BY owner, table_name, constraint_name ) cc,
     ( SELECT owner, table_name, constraint_name, listagg(column_name, ', ') WITHIN group(ORDER BY position) ref_column
         FROM ALL_CONS_COLUMNS WHERE owner = upper('your_schema') GROUP BY owner, table_name, constraint_name ) pc
WHERE c.r_constraint_name = p.constraint_name
  AND c.table_name = cc.table_name AND c.constraint_name = cc.constraint_name
  AND p.table_name = pc.table_name AND p.constraint_name = pc.constraint_name;

PostgreSQL

SELECT n.nspname::text     AS schema_name,
       trim(upper(conrelid::regclass::varchar), '"') AS table_name, upper(conname::varchar)    AS object_name,
       'FOREIGN KEY'       AS object_type,
       CASE contype WHEN 'f' THEN upper(pg_get_constraintdef(c.oid)) END AS  condition_column
  FROM pg_constraint c
       JOIN pg_namespace n ON n.oid = c.connamespace
 WHERE contype in ('f')
   AND c.connamespace::text = lower('your_schema');

便利な PostgreSQL カタログテーブル

以下の PostgreSQL カタログテーブルとビューは、カスタムクエリの構築に役立ちます。

ソース Oracle メタデータ ターゲット PostgreSQL メタデータ
all_tables pg_tables
information_schema.tables
all_views pg_views
information_schema.views
all_sequences information_schema.sequences
all_triggers information_schema.triggers
all_indexes pg_indexes
all_constraints pg_constraint join to pg_namespace
all_procedures information_schema.routines

まとめ

データベースオブジェクトの検証は、基本的にデータベース移行の正確性の見解で、すべてのオブジェクトが適切な属性で正常に移行されたことを確認します。様々な制約を検証することは、ターゲットデータベースの整合性を二重にチェックし、データベースから不良データを排除するために役立ちます。インデックスはデータ取得操作を迅速化し、インデックスの検証は、既存のクエリがターゲットデータベースを遅くすることを防ぎます。

この記事のクエリは、Oracle と PostgreSQL の各データベースからレコードセットを取得するために、どのプログラミング言語でもそのまま使用できます。要件に基づいてカスタム検証クエリを作成することもできますが、この記事では移行後のオブジェクト検証を実行するために使用できる 1 つの方法をご紹介しました。

以下の関連リソースは、データベース移行をより良く理解するために役立ちます。

この記事に関する質問または提案などがありましたら、ぜひコメントを残してください。

 


著者について

 

Sashikanta Pattanayak はアマゾン ウェブ サービスのアソシエイトコンサルタントです。Sashikanta は、AWS クラウドでスケーラブルかつ可用性に優れたセキュアなソリューションを構築するためにお客様と連携し、主にデータベースの同種間および異種間移行に携わっています。