Amazon Web Services ブログ

アプリケーションをオンプレミスの Oracle データベースから Amazon RDS for PostgreSQL に移行する方法

企業は長年、独自にローカルデータベースを設定し、ハードウェアを自社で管理する必要がありました。しかし、クラウドインフラストラクチャが向上し続けており、自社でハードウェアを所有・管理する必要が格段に少なくなっています。

Amazon では、所有している何百 (あるいは何千もの) オンプレミスのデータベースを、時間をかけてクラウドベースのソリューションに移行しました。このソリューションには、Amazon RDS のようなリレーショナルなものと、Amazon DynamoDB のような非リレーショナルなものの両方があります。リレーショナルなソリューションに移行したデータベースでは、無料で使用でき、Oracle データベースと類似の機能 (シーケンス、トリガー、パーティション) を持つデータベース技術を検討してコストを削減しようとも試みました。技術面でそれに最も近いものが、PostgreSQL です。

このブログ記事には 2 つのセクションがあります。まず、共通の Java アプリケーションと Hibernate のレイヤー (オブジェクトリレーショナルマッピング) における変更について述べます。その後、Java アプリケーションを Oracle から Amazon RDS for PostgreSQL に移行した際に明らかとなった、アプリケーション層を管理するためのベストプラクティスと方針について述べます。今回の提案と事例は、RDS および PostgreSQL と互換性のある Amazon Aurora の両方を基盤としたデータベースを対象とするものです。

Hibernate とアプリケーションレイヤーの変更およびベストプラクティス

まず、お使いのアプリケーションを分析することから始めます。移行を成功させるために、移行予定のアプリケーションについて深く理解することをおすすめします。以下は開始するにあたり、全体についての質問です。

  • お使いのアプリケーションは、Hibernate のようなオブジェクトリレーショナルマッピング (ORM) を使用していますか?
  • 使用している場合、アプリケーションでどの Hibernate データ型を使用していますか?
  • お使いのアプリケーションには変換を必要とする可能性のあるプレーン SQL が今もありますか?

この中からいくつかの質問を取り上げ、なぜそれが重要なのかを考えてみましょう。

お使いのアプリケーションは ORM を使用していますか?

アプリケーションが ORM を使用していれば、嬉しいことにアプリケーションコードをそれほど変更する必要はなさそうです。Hibernate は、最も支持されている ORM の 1 つです。以下のとおり Hibernate を使用したアプリケーションの移行に必要な変更について説明します。

以下に示す標準の Hibernate 接続設定を学び、各行の意味を理解しましょう。

1.	Map<String, String> config = new HashMap<>();  
2.	config.put("hibernate.connection.driver_class", "org.postgresql.Driver");  
3.	config.put("hibernate.connection.url", "jdbc:postgresql://test-db.apnlbg242xfw.us-west-2.rds.amazonaws.com:8192/test-db");  
4.	config.put("hibernate.connection.username", "Bob");  
5.	config.put("hibernate.connection.password", "Password");  
6.	config.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");  
7.	config.put("hibernate.jdbc.time_zone", "UTC");  
8.	config.put("hibernate.connection.provider_class", "org.hibernate.connection.C3P0ConnectionProvider");

2 行目では、接続を確立する際に Hibernate が試用する必要のあるドライバーのクラスを設定しています。

3 行目では、URL 接続を設定します。PostgreSQL ドライバーは、jdbc:postgresql または 類似のフォーム を探し、PostgreSQL ドライバーがこの URL を使うよう認識します。ドライバーは静的に Java の DriverManager に初期化されます。新たな接続が要求される場合、DriverManager は各ドライバーを介してスプールし、同じ種類のプレフィックスを確認することでその URL 接続を処理するかを判断します。

4 行目と 5 行目にはユーザー名とパスワードが含まれています。安全にパスワードを保存し、設定内のプレーンテキストに表示されないようにする方法を探すようお勧めします。

6 行目では Hibernate 向けの言語を設定してクエリを行います。

7 行目では、興味深いことに、各クライアントが実行しているタイムゾーンで PostgreSQL がタイムスタンプをクライアントに返そうとします。UTC にすべての時間を設定する場合は、クライアント側でそのように指定する必要があり、UTC で実行するように JVM を設定するか、明示的に JDBC タイムゾーンを表示のとおりに設定すると指定できます。タイムスタンプを使用する際に、スキーマ内にタイムゾーンのコンポーネントがあるかどうかを必ず把握してください。Oracle の日付はタイムスタンプですが 、日付が単なる日付部分の場合、PostgreSQL は日付とタイムスタンプを区別します。

8 行目はベストプラクティスとして掲載しました。データベースに接続して接続プールを作成し、一連の接続を開いたままにしてお使いのサービスで再利用できるようにする際に便利です。これを行うと、データベースへのアクセスごとに接続を開くためのレイテンシーが短縮されます。また、お使いのデータベースが新たな接続を絶えず確立せずに済むようにできます。ここで使用されている接続プールは C3P0 です。Hibernate ドキュメントで設定や構成について、その他の詳細を確認いただけます。

お使いのアプリケーションはどのデータ型を使用していますか?

Oracle と PostgreSQL でデータ型が若干異なります。問題が発生した最初の型はブール型でした。(この問題が発生した当時は、Hibernate バージョン 3.6 を使用していました。) Oracle では、数値 (1)boolean Java 型を値 0 または 1 に適切に変換処理する Hibernate を使用します。しかし、Hibernate を切り替えてboolean 型またはsmallint 型を使用していた PostgreSQL データベースにアクセスする際に、数値からブール型に変換しようとすると smallint 型では Hibernate がエラーを返していました。しかし、Hibernate にはこの変換を行う org.hibernate.type.NumericBooleanType と呼ばれるデータ型があります。

さらに、日付を移行する際は、必ずクライアントを正しいタイムゾーンに設定し (前述)、保存しようとする日付オブジェクトまたはタイムスタンプオブジェクトの種類を慎重に選択するよう注意する必要があります。保存については、秒単位で解析したタイムスタンプ情報を Oracle の date が保持し、PostgreSQL の date は時間のない日付を保持します。Oracle の datetimestamp の型は、ともに PostgreSQL の timestamp に変換し、同水準の情報を保持する必要があります。今回は取り上げませんが、他にも留意すべきタイムゾーン要素があります。これらの差異が、コード内に存在する可能性がある未加工の SQL ステートメントに影響を与えることがあるため、アプリケーションを変換する際は入念に分析する必要があります。

Oracle の PostgreSQL スキーマへの変換に関する他の情報については、AWS データベースのブログ記事 Challenges When Migrating from Oracle to PostgreSQL—and How to Overcome Them をご覧ください。

お使いのアプリケーションには変換するプレーン SQL がありますか?

アプリケーションで ORM を使用しない場合、2 つのデータベース間で互換性のない未加工の SQL ステートメントに多くの修正を加える必要がありそうです。ORM を使用する場合でも、最適化または特定の DB 機能上の理由により、所定の操作がアプリケーションにハードコーディングされている可能性があります。

通常、両方のデータベースが受け入れる ANSI SQL 標準があるため、この標準に書き込むことで今後の移行作業が容易になる可能性があります。また、AWS ウェブサイトからダウンロードできる無料ツールの AWS Schema Conversion Tool (AWS SCT) を使用すると、変換に役立ちます。  AWS SCT は、ソースデータベースのテーブル、インデックス、ストアドプロシージャ、シーケンス、その他オブジェクトをターゲットデータベースに移行するために使用できる一般的なツールです。

ただし、今回のブログ記事の内容に関しては、未加工の SQL ステートメントを変換する AWS SCT のアプリケーション変換機能を使用できます。AWS SCT は埋め込み SQL ステートメントのアプリケーションコードを分析します。それから、この SQL ステートメントをターゲット構文に変換し、変換済みのコードを使用してアプリケーションファイルを保存します。

可能な場合、AWS SCT はソースとターゲットの SQL 言語を直接翻訳します。直接翻訳ができない場合は、ターゲット環境でソースの動作を模倣する (「拡張パック」と呼ばれる) 追加機能を AWS SCT が提供します。それ以外で AWS SCT が所与の SQL ステートメントを変換できない場合は、手動で変換ができるよう問題のあるコードにフラグを立てます。

手動での変換が必要な場合には、Oracle Database to Amazon Aurora with PostgreSQL Compatibility Migration Playbook という素晴らしいリソースが別にあります。これも、AWS ウェブサイトから無料でダウンロードできます。  このプレイブックには、Oracle 言語から PostgreSQL に変換する上でのヒントが数多く掲載されていますが、以下に一般的な変換の一部をリストアップします。

タイムスタンプ

PostgreSQL には Oracle データベースでよく使用する SYSDATE 関数がありません。この関数の代わりに、両方のデータベースで動作する CURRENT_TIMESTAMP を使用できます。

ただし、考慮すべき事項がいくつかあります。まず、Oracle の CURRENT_TIMESTAMP はクライアントセッションのタイムゾーンに戻すのに対し、SYSDATE はサーバーのタイムゾーンに戻します。また、PostgreSQL では CURRENT_TIMESTAMP が全体のトランザクションと同じ結果を出しますが、SYSDATE はシステムクロックに従って長時間実行トランザクションのために変更します。この差異が、最初の行と最後の行の値が異なる一括読み込みのシナリオでは重要です。PostgreSQL では、CLOCK_TIMESTAMP は SYSDATE により近いものです。一般的な経験則として、以下の変換を行えば大半の目的を果たすことができます。

Oracle

select * from table where event_time > SYSDATE;

Oracle と PostgreSQL

select * from table where event_time > CURRENT_TIMESTAMP;

相対時間

PostgreSQL には例に挙げた Oracle データベースでよく使用する SYSDATE 関数がありません。この関数の代わりに、両方のデータベースで動作する CURRENT_TIMESTAMP を使用できます。

相対日付になるように日付から値を引くことができるため、Oracle の間隔は少し緩やかです。ただし、Oracle と PostgreSQL の両方でこれを行うためのより一般的な方法では間隔を利用します。

Oracle

select * from table where event_time > SYSDATE– 5;

Oracle と PostgreSQL

select * from table where event_time > CURRENT_TIMESTAMP – interval '5' day;

絶対時間

to_date 関数を使用して Oracle のインタラクションに特定の日付をハードコーディングすることがありますが、両方のデータベースが理解するさらに簡便な方法があることが分かりました。

Oracle

select * from table where event_time > to_date('2018/02/25 00:00:00');

Oracle と PostgreSQL

select * from table where event_time > '2018/02/25 00:00:00';

注意:クライアントが操作しているタイムゾーンを把握する必要があります。PostgreSQL タイムスタンプ (タイムゾーンの有無を問わない) と混同した前のコマンドが異常動作を引き起こす可能性があるためです。タイムスタンプにタイムゾーンがあるかどうかにかかわらず、PostgreSQL は必ずサーバーのタイムゾーンにタイムスタンプを保存します。Amazon RDS for PostgreSQL のタイムゾーンは常に UTC です。クライアントが UTC 以外のタイムゾーンにログインする場合、前のコマンドによりクライアントのタイムゾーンがキャプチャされる可能性があります。このキャプチャにより、入力とは別に保存された時間にずらされます。最も簡単な対処方法は、クライアントが UTC で実行するようにすることです。

FROM 句のサブステートメント

FROM 句内のステートメントには、PostgreSQL 内での別名を付ける必要があります (Oracle ではオプション)。

Oracle

select * from (select * from table) where x = y;  

Oracle と PostgreSQL

select * from (select * from table) as table where x = y;  

DUAL 表

Oracle でテーブルを使用していない場合にも FROM 句を持つ必要があるため、Oracle は DUAL の概念を導入しました。PostgreSQL では FROM 句をすべて省くことができます。

そのため、たとえば複雑でないコマンドを実行し、データベースに接続してクエリが実行できることを確認する場合、このコードは Oracle と PostgreSQL とで異なる可能性があります。

Oracle

select 1 from dual;

PostgreSQL

select 1;

Null と空の文字列との比較

Oracle のバージョンの中には、空の文字列を null 値と同様に扱うものがあるため、'IS NULL' on '' クエリを実行すると真を返します。PostgreSQL は空の文字列と null 値を区別するため、'IS NULL' on '' クエリは偽を返します。

DATE オブジェクト

Oracle で DATE は秒単位で解析する日付および時間オブジェクトです。一方、PostgreSQL は時間情報のない日付として DATE を扱います。

アプリケーション層の移行を管理するヒント

アプリケーションにおける Hibernate のインタラクションから一歩引いてアプリケーションを全体的に見てみると、他にも確認すべき質問があります。

どのようにアプリケーションを移行したらよいでしょうか?

お使いのアプリケーションの移行方針を確認する際は、アプリケーションが使用するテーブルとデータを使用することを検討してください。最も簡単な移行方法は AWS Database Migration Service (AWS DMS) のようなツールを使用して、まず一括読み込みでデータをレプリケートしてから変更データキャプチャ (CDC) を使用してライブトランザクションを移行するというものです。CDC の使用について、詳細は AWS データベースブログ記事 Migrate PostgreSQL Databases and Perform Ongoing Replication with the AWS Database Migration Service をご覧ください。

AWS DMS の一括読み込みと CDC との間で、すべてのデータ移行が処理されます。お客様はサービスレベルの移行のみ行い、別のデータベースにアプリケーションを切り替えます。しかし、データの依存性や稼働時間要件により、デュアル書き込み移行を行う必要が生じることがあります。両方のストアに読み込みと書き込みを行い、トランザクションと論理エラーの管理を自分で行うために、このタイプの移行ではアプリケーションに書き込みコードが必要です。このソリューションを使用しても、AWS DMS を使用してデュアル書き込みを始めた時点のタイムスタンプまで古いデータをすべて一括読み込みできる場合があります。

以下の質問の一部はこの方法を採用すべきか、またどの選択肢がお客様に最適かについて検討しています。

アプリケーションには、使用するテーブルへの排他的な読み込み/書き込みアクセスがありますか?

データ依存性を示す最も分かりやすい最初の例は、データの読み込みまたは書き込みのためにお使いのサービスのテーブルに直接アクセスする別のサービスです。大半のケースでは、この直接的なデータ依存性を絶ち、他のサービスが読み込みや書き込みを行うサービスを呼び出せるようにすることをお勧めします。つまりこの手法で、あるサービスをそのデータのマスターにできるということです。

または、2 つのうち 1 つのサービスのみが他のデータベースに移行するとします。この場合、移行しないサービスは異なるデータでデータベース間に存在するデータレプリケーションの遅延を確認します。アプリケーションによってはこれが問題にならないこともあります。しかし、早めに整合性を取る必要がある場合、このデータ依存性を絶てなければデュアル書き込み移行を検討する必要があるかもしれません。

アプリケーションに必要な書き込みとその後の読み込みには、どの程度の整合性が必要ですか?

似たような状況で、お使いのアプリケーション内の整合性を考えてみてください。同じデータベースに接続して同じテーブルを読み込んでいる複数のサービスがある場合、サービスプレーン全体に整合性が必要ですか? AWS DMS を使用してデータ移行を行い、サービスを一つずつ別のデータベースに切り替えているとします。このケースでは、あるサービスが新しいデータベースでテーブルに書き込み、古いデータベースに接続したサービスが古いデータを読み込む場合にどのような影響があるかを理解する必要があります。新しいデータベースから古いデータベースに同期しない限り、新しいデータベースにサービスを移行するまでサービスは古いデータを読み込み続けます。

このため、この問題を最小限に抑えるためにアプリケーションの移行方法を指示する必要があります。ライターが古いデータベースにありリーダーが新しいデータベースにある場合は短期的な整合性の問題が残るものの、少なくとも AWS DMS がその記録を移行すれば解決されます。アプリケーションが分散サービスの場合、似たような問題が 1 つのアプリケーション内にあります。デプロイ中に古いデータベースを指し示すホストと、新しいデータベースを指し示すホストがあるためです。このため、データベース切り替えのデプロイ方法について慎重に検討する必要があります。

サービスのテーブル間をまたぐ外部キー制約はありますか?

2 つのアプリケーションがともに自分のテーブルに読み込みと書き込みを排他的に行っている場合でも、異なるサービスのテーブル間に外部キー制約がある場合は問題がある可能性があります。古いデータベースに値が書き込まれていながら、新しいデータベースにレプリケートされていない場合にこの問題が起こることがあります。このケースでは、その値を参照する新しいデータベースへの書き込みで外部キー制約違反が生じます。通常、最も簡単な修正方法は、移行中に外部キー制約を外すことです。ただし、お使いのアプリケーションの移行に合った順序が見つかることがあるため、これを実行する必要がない場合もあります。

アプリケーションのダウンタイムやメンテナンスは、どの程度までが許容範囲でしょうか?

ダウンタイムとメンテナンスの許容範囲は、お使いのサービスにどのような移行を行うべきかを判断する際に役立ちます。ダウンタイムが許容できず、速やかにすべてのレコードで整合性を取る必要がある場合は、デュアル書き込みソリューションが適している可能性があります。ただし、このソリューションにも短所があります。あるデータベースへの書き込みが正常に行われると、別のデータベースへの書き込みエラーを処理する必要があるというものです。多少のダウンタイムを許容できる場合は、(前述のとおり) アプリケーションの切り替え方法を指示するか、新たな設定をデプロイするかが最適である可能性があります。この方法を使用すれば、大変なデータ移行作業を AWS DMS に一任できます。

どのようにアプリケーションをテストしたらよいでしょうか?

どのような方法で移行を実装しても、アプリケーションの互換性とパフォーマンスのテストは移行をスムーズに進めるために非常に重要です。全面的に Hibernate を使用している場合、Hibernate は多くの非互換性を非表示にしますが、それでも前述の不具合がいくつかあります。事例では Hibernate と未加工の SQL ステートメントを混同して使用していましたが、テスト時のみ非互換性を引き起こしていました。

機能のテストの他に、実行時に想定しうるデータベースのパフォーマンスの差異をテストすることをお勧めします。開始にあたり、最も一般的でリソースを集中的に使用するクエリを実行するプランを実現し、PostgreSQL が同じような段階を踏んでクエリの結果を得ているかを確認できます。

Oracle:

EXPLAIN PLAN FOR SELECT * FROM test_table, test_table2 
 WHERE test_table.join_id = test_table2.join_id AND test_table.id = 'value';  
SELECT plan_table_output FROM TABLE(DBMS_XPLAN.DISPLAY());

PostgreSQL:

EXPLAIN SELECT * FROM test_table, test_table2
  WHERE test_table.join_id = test_table2.join_id AND test_table.id  =  'value';

これらはそれぞれ実行プランをレンダリングし、各クエリの想定される実行方法を比較するのに使用できます。これを行う際に、PostgreSQL と Oracle が比較的類似したパスでクエリを実行するかを判断できます。実行プランではすべて、この 2 つが類似のインデックスを使用するかを確認してください。同様に、次に示すとおり時間の計測が有効なクエリを実行し、結果を返すのにかかった時間を確認します。

Oracle:

sqlplus > set timing on;

PostgreSQL:

psql > \timing;  

上級レベルでは、リモート呼び出し周辺にメトリクスを加えると、お使いのアプリケーションをインストルメント化できます。前述の機能テストのように、さまざまなコードパスを呼び出すとお使いのアプリケーションレイヤーでレイテンシーを監視できます。まだ統合テストセットをお持ちでなければ、この機会にぜひ設定してください。アプリケーションの機能とレイテンシー両方の回帰をモニタリングできます。統合テストセットを設定すると今後何年もお役立ていただけます。アプリケーションで悪影響をもたらす変化を察知し、お客様のチームがよりデプロイを続けやすくなります。

結論

このブログ記事では、オンプレミスの Oracle データベースから Amazon RDS for PostgreSQL データベースへのアプリケーションの移行について述べました。この移行では、クラウドベースのソリューションへの変更によるインフラストラクチャやオペレーションのメンテナンス削減だけでなく、ライセンス費用も削減されます。3 つのメリットを享受できるということです。

アプリケーションのデータストアを変更するには、慎重にプランを立ててお使いのアプリケーションにとって正しい道筋を理解する必要があります。徹底したテストを行い、機能やレイテンシーの回帰を防ぐことも必要です。Amazon では、独自の AWS ツールセットを使用して多くのオンプレミスデータベースを Amazon RDS for PostgreSQL インスタンスに移行してきました。そして、毎日さらにデータベースを移行し続けています。


今回のブログ投稿者について

Ryan Zauber は Amazon のシニアソフトウェア開発エンジニアです。