RDS for Oracle で Data Pump によるエクスポート/インポート


はじめに

RDS 上の Oracle で Data Pump によるエクスポート/インポートを行う方法について説明します。

Data Pump では、対象のエクスポートファイルやインポートファイルをディレクトリオブジェクトを介してやり取りする必要があり、サーバOSを直接操作できない RDS ではファイルの連携が面倒でした。

旧来は、エクスポートファイルを他の DB に持っていく場合などは、DBLink を経由する必要がありました。ローカルの端末にファイルを持ってくる場合は、ローカルに DB サーバを立てて DBLink を張るなどの対応が必要でした。

しかし現在は、RDS で S3_INTEGRATION を利用することで、Amazon S3 との統合ができ、S3 を介したファイル連携ができるようになりました。

ここでは、RDS 上での Data Pump によるエクスポート/インポートと S3_INTEGRATION でのファイルのやり取りについて説明します。


RDS 上の DATA_PUMP_DIR の操作

Oracle にて Data Pump を使う際には、ディレクトリオブジェクトとして登録した、OS上のディレクトリを使います。

RDS で Data Pump を使う場合は予め登録されている DATA_PUMP_DIR を使うことになります。

DATA_PUMP_DIR は以下のようにして確認することができます。

SELECT *
FROM dba_directories
WHERE directory_name='DATA_PUMP_DIR';

DATA_PUMP_DIR 内のファイル一覧は以下のようにして確認することができます。

SELECT * 
FROM TABLE(RDSADMIN.RDS_FILE_UTIL.LISTDIR('DATA_PUMP_DIR'))
ORDER BY MTIME;


DATA_PUMP_DIR 内のファイルの内容(例えばログファイルexp.log)は以下のようにして確認することができます。

SELECT TEXT
FROM TABLE(RDSADMIN.RDS_FILE_UTIL.READ_TEXT_FILE('DATA_PUMP_DIR', 'exp.log'));

DATA_PUMP_DIR 内のファイルは以下のように削除することができます。

EXEC UTL_FILE.FREMOVE('DATA_PUMP_DIR', 'exp.log');


すこし話がそれますが、以下のようにディレクトリオブジェクトを作成し、

EXEC RDSADMIN.RDSADMIN_UTIL.CREATE_DIRECTORY(p_directory_name => 'ORCLFILES');

作成したディレクトリに対してファイル出力するといったことも可能です。

declare
  ltype utl_file.file_type;
  ldir  varchar2(100) := 'ORCLFILES';
  lfile  varchar2(100) := 'LIST.txt';
  s varchar2(10) := '|';
begin
  ltype := utl_file.fopen(ldir, lfile, 'w');
  for i in (select OWNER,STATUS from DUMMY)
     loop
       utl_file.putf(ltype, i.OWNER||s||i.STATUS);
       UTL_FILE.NEW_LINE(ltype);
    end loop;
   utl_file.fclose(ltype);
 end;
 /


Data Pump によるエクスポート

先ほど見た DATA_PUMP_DIR を使い、スキーマを指定したエクスポートは以下のようになります。

DECLARE
  v_hdnl NUMBER;
  status VARCHAR2(20);
BEGIN
  v_hdnl := DBMS_DATAPUMP.OPEN(
    operation => 'EXPORT',
    job_mode  => 'SCHEMA',
    job_name  => null);
  DBMS_DATAPUMP.ADD_FILE( 
    handle    => v_hdnl, 
    filename  => 'exp.dmp', 
    directory => 'DATA_PUMP_DIR', 
    filetype  => dbms_datapump.ku$_file_type_dump_file,
    reusefile => 1);
  DBMS_DATAPUMP.ADD_FILE( 
    handle    => v_hdnl, 
    filename  => 'exp.log', 
    directory => 'DATA_PUMP_DIR', 
    filetype  => dbms_datapump.ku$_file_type_log_file,
    reusefile => 1);
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => v_hdnl,
    name      => 'SCHEMA_EXPR',
    value     => 'IN (''SCHEMA_1'')');
  DBMS_DATAPUMP.START_JOB(handle => v_hdnl);
  DBMS_DATAPUMP.WAIT_FOR_JOB(handle => v_hdnl, job_state => status);
END;
/

ダンプファイル exp.dmp、ログ・ファイル exp.log は各自好きな名前にすることができます。

上記では、対象スキーマを SCHEMA_1 としているので、各位の環境に合わせて変更する必要があります。なお、このスキーマ名は大文字で書く必要があります。 スキーマモードのエクスポートでは、スキーマは(IN句で指定しているにも関わらず)1つしか指定できませんので注意してください。

その他は基本的にそのままで大丈夫です。reusefile => 0 とすれば、すでにファイルが存在する場合にエラーになります。

エクスポート・ジョブはバックグラウンドジョブとして動作します。 上記では DBMS_DATAPUMP.WAIT_FOR_JOB でエクスポートジョブの終了を待機しています。

DBMS_DATAPUMP.WAIT_FOR_JOB により待機しなかった場合は、以下でジョブの状況を確認することができます。

SELECT * FROM DBA_DATAPUMP_JOBS;


テーブル単位でのエクスポート

テーブル単位でエクスポートする場合は、job_mode'TABLE' として実行します。

...
BEGIN
  v_hdnl := DBMS_DATAPUMP.OPEN(
    operation => 'EXPORT',
    job_mode  => 'TABLE',
    job_name  => null);
...
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => v_hdnl,
    name      => 'SCHEMA_EXPR',
    value     => 'IN (''SCHEMA_1'')');
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => v_hdnl,
    name      => 'NAME_EXPR',
    value     => 'IN (''TABLE_1'', ''TABLE_2'')');
...

上記例では、METADATA_FILTER により TABLE_1TABLE_2 を対象としています(SQL式により LIKE 抽出なども可能です)。

なお、Oracle では job_mode として FULL としてフルバックアップが可能ですが、RDS for Oracle の場合は利用すべきではありません。RDS では、管理ユーザー SYS や SYSDBA へのアクセスは許可されていないため、データディレクトリが損傷し、データベースの安定性に影響を及ぼす可能性があります。


バージョン指定

エクスポートしたファイルのインポート先が決まっている場合は、version によりバージョン番号を指定することができます(省略時はデフォルトの COMPATIBLE)。

v_hdnl := DBMS_DATAPUMP.OPEN(
  operation => 'EXPORT',
  job_mode  => 'SCHEMA',
  job_name  => null,
  version   => '10.0.0');

エクスポート時にはversion指定なしで、インポート時に指定することもできます。


Data Pump によるインポート

エクスポートデータからのインポート(スキーマ指定)は以下のようになります。

DECLARE
  v_hdnl NUMBER;
  status VARCHAR2(20);
BEGIN
  v_hdnl := DBMS_DATAPUMP.OPEN( 
    operation => 'IMPORT', 
    job_mode  => 'SCHEMA', 
    job_name  => null);
  DBMS_DATAPUMP.ADD_FILE( 
    handle    => v_hdnl, 
    filename  => 'exp.dmp', 
    directory => 'DATA_PUMP_DIR', 
    filetype  => dbms_datapump.ku$_file_type_dump_file);
  DBMS_DATAPUMP.ADD_FILE( 
    handle    => v_hdnl, 
    filename  => 'imp.log', 
    directory => 'DATA_PUMP_DIR', 
    filetype  => dbms_datapump.ku$_file_type_log_file,
    reusefile => 1);
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => v_hdnl,
    name      => 'SCHEMA_EXPR',
    value     => 'IN (''SCHEMA_1'')');
  DBMS_DATAPUMP.START_JOB(v_hdnl);
  DBMS_DATAPUMP.WAIT_FOR_JOB(v_hdnl, status);
END;
/

エクスポート時と同様に、ログ・ファイル imp.log は各自好きな名前にすることができます。 対象とするダンプファイル exp.dmp は、DATA_PUMP_DIR に配備したダンプファイル名を指定します(S3を介したダンプファルのやり取りは後述します)。


テーブル単位でのインポート

インポート対象のテーブルを絞る場合は、METADATA_FILTER を使います。

  v_hdnl := DBMS_DATAPUMP.OPEN( 
    operation => 'IMPORT', 
    job_mode  => 'SCHEMA', 
    job_name  => null);
...
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => v_hdnl,
    name      => 'SCHEMA_EXPR',
    value     => 'IN (''SCHEMA_1'')');
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => v_hdnl,
    name      => 'NAME_EXPR',
    value     => 'IN (''TABLE_1'', ''TABLE_2'')');
...

指定方法はエクスポート時と同様です。


統計情報を除いたインポート

統計情報が不要な場合は METADATA_FILTER として以下を付与します。

...
DBMS_DATAPUMP.METADATA_FILTER(
    handle    => hdnl,
    name      => 'EXCLUDE_PATH_EXPR',
    value     =>  'IN (''TABLE_STATISTICS'')');
...


既存テーブルの更新/追加

既存テーブルが存在する場合は、TABLE_EXISTS_ACTION で動作を変更できます。

...
DBMS_DATAPUMP.SET_PARAMETER(
    handle    => hdnl,
    name      => 'TABLE_EXISTS_ACTION',
    value     =>  'TRUNCATE');
...

デフォルトは APPENDで、表の既存の行に新規の行が追加されます。 TRUNCATE を指定すると、インポートによって行が挿入される前に、既存の表から行が削除されます。 REPLACE を指定すると、新しい表が作成される前に、既存の表が削除されます。


スキーマのリマップ

エクスポートしたスキーマとは別のスキーマにインポートするには、スキーマ名のリマップを指定します。

...
DBMS_DATAPUMP.METADATA_REMAP(
    handle    => v_hdnl,
    name      => 'REMAP_SCHEMA',
    old_value => 'SCHEMA_1',
    value     => 'SCHEMA_2');
...

上記では、エクスポートした SCHEMA_1SCHEMA_2 としてインポートする例となります。


Data Pump ジョブのクリーニング

DataPumb ジョブの途中でエラーが発生した場合、ジョブが残る場合があります。

DataPumb は処理中に管理テーブルを作成して処理を行うため、この管理テーブルを削除することでジョブをクリーンアップすることができます。

ジョブの状況は以下で確認できます。

SELECT * FROM DBA_DATAPUMP_JOBS;

ここで表示される OWNER_NAME のスキーマに、JOB_NAME の名前のテーブルが作成されているので、以下のように削除します。

DROP TABLE <OWNER_NAME>.<JOB_NAME>;

JOB_NAME は大抵 SYS_IMPORT_SCHEMA_01 といった名前になるので、実際は以下のような感じになります。

DROP TABLE SCOTT.SYS_IMPORT_SCHEMA_01;

末尾の数字は、実行したジョブに応じてカウントアップされたテーブルとなります。


S3_INTEGRATION の設定

先ほどまでは、Data Pump によるエクスポート/インポートでしたが、S3_INTEGRATION により、ダンプファイルをS3に転送したり、S3から取得したりすることができます。

S3_INTEGRATION を使うには、オプショングループに S3_INTEGRATION を設定し、RDS のインスタンスに紐付ける必要があります。

デフォルトのオプショングループは変更することができないため、新規オプショングループを作成します。

オプショングループから「グループの作成」で対象のエンジンとバージョンを指定します。

f:id:Naotsugu:20210610210502p:plain

名前は適当なものを設定します。

作成したオプショングループを選択し、「オプションを追加」を選択します。

f:id:Naotsugu:20210610210553p:plain

オプションとして S3_INTEGRATION を選択して追加します。

f:id:Naotsugu:20210610210621p:plain

作成したオプショングループは、RDSインスタンスの [変更] により、新しく作成したオプショングループを設定します。


オプショングループの変更が完了したら、RDS for Oracleの IAM ロールを設定します。

対象のインスタンスを選択し、[IAM ロールの管理]から S3_INTEGRATION に適用するロールを追加します。

f:id:Naotsugu:20210610210644p:plain

ロールの選択では、s3:GetObjects3:ListBuckets3:PutObject の権限を付与したIAMロールを選択します。ここで設定した IAM ロールにて S3 へのアクセスが行われることになります。


なお、ロールの作成は、CLI では以下のようになります(Windows の場合は \ -> ^)。

最初にポリシーを作成します。

aws iam create-policy \
   --policy-name rds-s3-integration-policy \
   --policy-document '{
     "Version": "2012-10-17",
     "Statement": [
       {
         "Sid": "s3integration",
         "Action": [
           "s3:GetObject",
           "s3:ListBucket",
           "s3:PutObject"
         ],
         "Effect": "Allow",
         "Resource": [
           "arn:aws:s3:::<your-s3-bucket-arn>", 
           "arn:aws:s3:::<your-s3-bucket-arn/*>"
         ]
       }
     ]
   }'

<your-s3-bucket-arn> は S3 に作成したバケット名となります。

ロールを作成します。

aws iam create-role \
   --role-name rds-s3-integration-role \
   --assume-role-policy-document '{
     "Version": "2012-10-17",
     "Statement": [
       {
         "Effect": "Allow",
         "Principal": {
            "Service": "rds.amazonaws.com"
          },
         "Action": "sts:AssumeRole"
       }
     ]
   }'

ロールにポリシーを割り当てます。

aws iam attach-role-policy \
   --policy-arn <your-policy-arn> \
   --role-name rds-s3-integration-role

<your-policy-arn> はポリシー作成時のAmazon リソースネームです。


S3_INTEGRATION によるファイル転送

S3_INTEGRATION の設定が終われば、SQL により S3 とのファイルのやり取りが可能となります。


S3 へのアップロード

SELECT rdsadmin.rdsadmin_s3_tasks.upload_to_s3(
  p_bucket_name    =>  'myBucket',
  p_directory_name =>  'DATA_PUMP_DIR',
  p_s3_prefix      => '',
  p_prefix         => 'exp.dmp') 
AS TASK_ID FROM DUAL;

DATA_PUMP_DIR 内の exp.dmp を、S3 バケット myBucket のルートにアップロードしています。

パラメータは以下の通りです。

パラメータ 説明
p_bucket_name S3 バケット名
p_directory_name ファイルのアップロード元のディレクトリオブジェクト名
p_s3_prefix アップロードするファイル名のプレフィックス。空の場合はバケットのルートにアップロードされる。プレフィックスが folder_1/ の場合、S3バケットの folder_1/ にアップロードされる。folder_1/oradb の場合、folder_1/ フォルダ内にoradb というプレフィックスを付与してアップロードされる
p_prefix アップロードするファイル名が一致する必要があるファイル名のプレフィックス。プレフィックスが空の場合は、ディレクトリ内のファイルがすべてアップロードされる


S3 からのダウンロード

SELECT rdsadmin.rdsadmin_s3_tasks.download_from_s3(
  p_bucket_name    =>  'myBucket',
  p_directory_name =>  'DATA_PUMP_DIR',
  p_s3_prefix      =>  'exp.dmp') 
AS TASK_ID FROM DUAL;

パラメータは以下の通りです。

パラメータ 説明
p_bucket_name S3 バケット名
p_directory_name ファイルのダウンロード先のディレクトリオブジェクト名
p_s3_prefix S3からダウンロードするファイル名のプレフィックス。プレフィックスを空にすると、バケットのルートにあるファイルがすべてダウンロードされる(ディレクトリは対象外)。folder_1/ と指定するとfolder_1/フォルダ内のファイルが全てダウンロードされる。folder_1/oradb の場合、folder_1/フォルダ内の oradb というプレフィックスに合致するガイルがダウンロードされる