Skip to content
Published on

異種DBエンジン移行 — OracleからPostgreSQLへ(そしてその先へ)

Authors

はじめに — なぜ急にみなエンジンを入れ替えるのか

データベースエンジンを変えることは、もともと珍しい出来事でした。一度Oracleの上にシステムを載せれば十数年はその上で暮らすのが普通で、誰もわざわざ問題なく回っているエンジンを引き剥がそうとはしませんでした。ところが2020年代に入って空気が変わりました。金融、通信、公共機関までもが「Oracleを取り除いてPostgreSQLへ移る」プロジェクトを次々と立ち上げ、2026年の今では異種DB移行はもはや特異な決断ではなく、ひとつの標準的な課題のように定着しています。

理由は大きく三つです。一つ目はライセンス費用です。コア数ベースのライセンスと毎年更新される保守料金は、システムが大きくなるほど指数関数的に膨らみます。二つ目はクラウドです。クラウドへ移りながらマネージドデータベースを使おうとすると、商用エンジンをクラウドでそのまま動かす費用のほうがかえって高くつく場合が多いのです。三つ目は開放性です。特定ベンダーに縛られるロックインから抜け出し、拡張とエコシステムの豊かなオープンソースへ移ろうとする流れです。

この記事ではOracleからPostgreSQLへ向かう道を、動機から互換性ギャップ、ツール、データ移行、アプリケーションの書き換え、検証、段階的移行、実際の事例、落とし穴まで実務の観点で整理します。「その先へ」という副題を添えた理由は、同じ方法論が結局ほかのあらゆる異種移行にもそのまま通じるからです。

移行の動機 — ライセンスとクラウド

まずなぜ移すのかを正確に整理しなければなりません。動機が曖昧だと、プロジェクトの中盤で「これをなぜやっているのか」という疑念が必ず訪れるからです。

  • ライセンス費用: 商用エンジンはコア単位のライセンスに加えて毎年保守費用を請求します。ノードを増やすたび、コアを追加するたびに費用も一緒に跳ね上がります。
  • クラウド適合: マネージドPostgreSQLサービスはクラウド事業者ごとによく磨かれています。自動バックアップ、リードレプリカ、自動フェイルオーバーを数クリックで手に入れられます。
  • エコシステムと拡張: PostgreSQLは拡張機能が豊富です。空間データ、時系列、全文検索、ベクトル検索まで拡張で吸収します。
  • 人材と採用: オープンソース経験を持つ開発者のプールが広く、採用と維持が比較的容易です。

逆に移すべきでない兆候もあります。中核の業務ロジックが数十万行のPL/SQLに埋め込まれていて、そのロジックが即ち会社の競争力であり、移行に伴うリスクに対して削減額が小さいなら、移行は費用だけを膨らませる決断になります。動機を数字に換算してから意思決定を下すべきです。

全体像 — 移行は五つの作業の流れである

異種移行を一つの塊として見ると途方に暮れます。実際には五つに分かれる別個の作業です。

+---------------------------------------------------------------+
|  異種DBエンジン移行の五つの流れ                                |
|                                                               |
|  1. スキーマ変換                                              |
|     - テーブル、インデックス、制約、ビュー、シーケンス        |
|     - データ型マッピング (NUMBER -> numeric など)             |
|                                                               |
|  2. オブジェクト(コード)変換                                  |
|     - プロシージャ、関数、トリガー、パッケージ                |
|     - PL/SQL -> PL/pgSQL                                       |
|                                                               |
|  3. データ移行                                                |
|     - 初期ロード + 変更同期(CDC)                              |
|                                                               |
|  4. アプリケーション変更                                      |
|     - SQL方言の違い、ドライバ、トランザクション処理          |
|                                                               |
|  5. 検証とカットオーバー                                      |
|     - データ整合性、性能リグレッション、切り替え              |
+---------------------------------------------------------------+

この五つの流れは難易度と自動化の可能性がそれぞれ異なります。スキーマとデータ移行はツールがよく支えてくれるので自動化率が高いです。一方でPL/SQLの変換とアプリケーションSQLの書き換えは人手が多くかかります。プロジェクト工程の大部分は後者で消費されます。

互換性ギャップ1 — データ型

最初にぶつかるギャップはデータ型です。OracleとPostgreSQLは似て見えますが、微妙に異なる型体系を持っています。

OraclePostgreSQL注意点
NUMBERnumeric または整数型精度なしのNUMBERは丸ごとnumericになり遅くなりうる
VARCHAR2varchar長さの意味がバイトと文字で異なりうる
CHARchar空白パディングは類似だが比較規則を確認
DATEtimestampOracle DATEは時分秒を含む、PG dateは日付のみ
CLOBtexttextは実質的に長さ制限がない
BLOBbytea大容量は別の保存戦略を検討
RAWbytea長さ制限の違いを確認
NUMBER(1)のブール代用booleanブールへ正規化する好機

最も多い落とし穴はNUMBERです。Oracle開発者は整数でも実数でもとりあえずNUMBERを使う習慣があり、それをそのままnumericへ移すとPostgreSQLでは整数演算より遅くなります。明らかに整数の列はintegerやbigintへ狭めるのがよいです。

-- Oracle 原本
CREATE TABLE orders (
  order_id    NUMBER(18)      NOT NULL,
  amount      NUMBER(12,2)    NOT NULL,
  is_paid     NUMBER(1)       DEFAULT 0 NOT NULL,
  created_at  DATE            DEFAULT SYSDATE
);

-- PostgreSQL 向けに整えた形
CREATE TABLE orders (
  order_id    bigint        NOT NULL,
  amount      numeric(12,2) NOT NULL,
  is_paid     boolean       DEFAULT false NOT NULL,
  created_at  timestamptz   DEFAULT now()
);

DATEも注意が必要です。OracleのDATEは日付と時刻を一緒に持ちますが、PostgreSQLのdateは日付だけを持ちます。時刻が必要な列はtimestampまたはtimestamptzへマッピングしなければデータ損失が起きません。タイムゾーンを扱うシステムなら、最初からtimestamptzへ統一するのが安全な選択です。

互換性ギャップ2 — シーケンスと自動採番

OracleのシーケンスはPostgreSQLにもほぼそのまま存在します。ただし使用する構文と慣用句が異なります。

-- Oracle 方式
CREATE SEQUENCE seq_order_id START WITH 1 INCREMENT BY 1;
INSERT INTO orders (order_id) VALUES (seq_order_id.NEXTVAL);

-- PostgreSQL 方式 1: 明示的シーケンス
CREATE SEQUENCE seq_order_id START WITH 1 INCREMENT BY 1;
INSERT INTO orders (order_id) VALUES (nextval('seq_order_id'));

-- PostgreSQL 方式 2: 標準 IDENTITY 列 (推奨)
CREATE TABLE orders (
  order_id bigint GENERATED ALWAYS AS IDENTITY,
  amount   numeric(12,2)
);

Oracle 11g以前でよく使われた「トリガーでNEXTVALを埋め込む」パターンは、PostgreSQLではIDENTITY列できれいに置き換えられます。ただしデータを移した直後には、シーケンスの現在値をデータの最大値に合わせて上げる作業を忘れてはいけません。これを抜かすと切り替え直後の最初のINSERTで主キー衝突が起きます。

-- 移行後は必ず: シーケンスをデータ最大値へ同期
SELECT setval('seq_order_id', (SELECT max(order_id) FROM orders));

互換性ギャップ3 — SQL方言とヒント

同じSQLでも方言の違いは少なくありません。アプリケーションコードを洗い出し、次のパターンを見つけて置き換える必要があります。

Oracleの慣用句PostgreSQLの対応備考
ダミーテーブル DUALただのSELECTのみFROM句自体が不要
NVL(a, b)COALESCE(a, b)多引数対応はCOALESCEが優れる
DECODE(...)CASE WHEN ...可読性も良くなる
外部結合 (+) 表記標準 LEFT JOIN(+) 構文は未対応
ROWNUM ベースのページングLIMIT と OFFSETまたはキーセットページング
CONNECT BY 階層クエリ再帰CTEWITH RECURSIVE を使用
オプティマイザヒント注釈多くは不要プランナパラメータで制御
SYSDATEnow() または current_timestamp戻り型の違いに注意

最も心を空にすべき部分はオプティマイザヒントです。Oracleでは注釈の形のヒントで実行計画を強制する文化が強いです。PostgreSQLは基本的にヒントを対応していません。代わりに統計をよく集め、必要な場合はセッション単位のプランナパラメータで動作を誘導します。

-- Oracle: ヒントでインデックスを強制
SELECT /*+ INDEX(o idx_orders_user) */ *
FROM orders o WHERE user_id = 42;

-- PostgreSQL: ヒントの代わりに統計とプランナ設定
ANALYZE orders;                          -- 統計更新が先
SET enable_seqscan = off;                -- 最後の手段、セッション限定
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 42; -- 計画を直接確認

ヒントが本当に必要なごく少数の状況にはpg_hint_plan拡張を導入できますが、ほとんどは統計とインデックス設計で解決します。「ヒントがないと生きていけない」という不安は、移行初期によくある誤解です。

互換性ギャップ4 — PL/SQLからPL/pgSQLへ

移行プロジェクトで最も人手がかかる領域です。PL/SQLとPL/pgSQLはどちらもAda系の構文を共有していて見た目は似ていますが、核心の構造が異なります。

最大の違いはパッケージです。OracleのPACKAGEは関連するプロシージャと変数を一つにまとめるモジュール単位ですが、PostgreSQLにはパッケージがありません。通常はスキーマをモジュールのように使い、パッケージ変数は別の設定テーブルやセッション変数へ解きほぐします。

-- Oracle PL/SQL プロシージャ
CREATE OR REPLACE PROCEDURE add_order(
  p_user_id IN NUMBER,
  p_amount  IN NUMBER
) AS
BEGIN
  INSERT INTO orders (user_id, amount, created_at)
  VALUES (p_user_id, p_amount, SYSDATE);
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    RAISE;
END;

これをPL/pgSQLへ移すと次のようになります。関数本体をドル引用で包む点、トランザクション制御を関数の中で直接扱いにくい点が核心の違いです。

-- PostgreSQL PL/pgSQL プロシージャ
CREATE OR REPLACE PROCEDURE add_order(
  p_user_id bigint,
  p_amount  numeric
)
LANGUAGE plpgsql
AS $$
BEGIN
  INSERT INTO orders (user_id, amount, created_at)
  VALUES (p_user_id, p_amount, now());
  -- プロシージャは内部でCOMMIT可能、関数は不可
  COMMIT;
EXCEPTION
  WHEN others THEN
    RAISE;
END;
$$;

ここで押さえるべき違いがいくつかあります。第一に、PostgreSQL 11からPROCEDUREができて内部でCOMMITとROLLBACKが可能になりましたが、FUNCTIONでは依然として不可能です。第二に、Oracleの暗黙コミット文化と違い、PostgreSQLはトランザクション境界を呼び出し側で取るほうが自然です。第三に、例外処理ブロックは費用が大きいです。乱発すると性能が落ちます。

カーソルを多用するPL/SQLは特に注意が必要です。PL/pgSQLでも明示カーソルを対応していますが、ほとんどの行単位ループは集合ベースのSQLへ書き直すほうが圧倒的に速いです。移行はコードをより良く書き直す好機でもあります。

-- PL/SQLの行単位ループ (遅いパターン)
-- FOR rec IN (SELECT id FROM staging) LOOP
--   UPDATE target SET flag = 1 WHERE id = rec.id;
-- END LOOP;

-- 集合ベースへ書き直す (速いパターン)
UPDATE target t
SET flag = 1
FROM staging s
WHERE t.id = s.id;

スキーマ変換ツール — ora2pgとAWS SCT

幸いスキーマとコードの変換を手作業だけでやるわけではありません。二つの代表的なツールがあります。

ora2pgはPerlで書かれたオープンソースツールで、OracleのスキーマとデータをPostgreSQLの形へ抽出し変換します。最大の利点は移行評価レポートです。変換難易度を自動採点し、どこに人手が多くかかるかを前もって教えてくれます。

# 設定ファイルから評価レポートを生成
ora2pg -t SHOW_REPORT --estimate_cost -c ora2pg.conf

# スキーマのみ抽出 (テーブル、制約、インデックス)
ora2pg -t TABLE -o schema.sql -c ora2pg.conf

# プロシージャと関数の抽出 (手動レビュー必須)
ora2pg -t PROCEDURE -o procedures.sql -c ora2pg.conf
ora2pg -t FUNCTION  -o functions.sql  -c ora2pg.conf

AWS SCTはAWSのスキーマ変換ツールで、変換可能な項目と手動変換が必要な項目をグラフィカルなインターフェース上で色分けして見せてくれます。変換結果をAWS DMSと連携してデータ移行まで一つの流れにつなげられる点が強みです。クラウドへ向かう移行なら、この組み合わせが滑らかです。

両ツールとも「自動変換されたものをそのまま信じないこと」という原則は同じです。ツールは九割を処理し、残り一割の厄介なロジックがプロジェクト時間の大部分を食います。ツールの評価レポートは、その一割の場所を教える地図の役割を果たします。

データ移行 — 初期ロードと変更同期

スキーマが整えばデータを移します。データ移行は二つの段階に分かれます。初期の全量ロードと、その間に発生した変更分を追いつく同期です。

+------------------------------------------------------------+
|  データ移行の二つの段階                                     |
|                                                            |
|  [1] 初期全量ロード (full load)                            |
|       Oracle ----(抽出/ロード)----> PostgreSQL             |
|       大容量なので時間が長くかかる                          |
|                                                            |
|  [2] 変更分同期 (CDC)                                       |
|       Oracle の redo log -> 変更キャプチャ -> PostgreSQL    |
|       初期ロード中に発生した変更を追いつく                  |
|                                                            |
|  [3] カットオーバー                                         |
|       遅延(lag)が0に収束したら短い停止後に切り替え          |
+------------------------------------------------------------+

小さなシステムなら、点検時間を取って一度に移すビッグバン方式が単純です。しかし無停止を求めるシステムでは変更データキャプチャが必須です。AWS DMSは初期ロードとCDCの両方を対応していて、二つのデータベースをしばらく並行運用してから遅延が0に近づいたときに短く切り替えられるようにしてくれます。

# ora2pgでデータまで抽出する単純な方式 (小規模)
ora2pg -t COPY -o data.sql -c ora2pg.conf

# または直接 PostgreSQL へロード (並列処理)
ora2pg -t COPY -J 4 -c ora2pg.conf

大容量ではインデックスをすべて外してロードし、最後に作り直すほうが速いです。またロード中は自動VACUUMと同期コミットをしばらく緩めておくとスループットが大きく上がります。ただしこの設定はロードが終わったら必ず元に戻さなければなりません。

アプリケーションSQLの書き換え

データベースだけ変えれば終わりではありません。アプリケーションが投げるSQLも手を入れる必要があります。この作業の規模は、コードベースがSQLをどう扱うかに大きく左右されます。

ORMの上にきれいに抽象化されたアプリケーションなら、方言設定を変えるだけで相当部分が自動で解決します。一方、文字列で直接SQLを組み立てるコード、特にOracle専用関数が随所に埋め込まれたコードは、一つひとつ見つけて直さなければなりません。

書き換え作業量のスペクトル

低い  [ORM抽象化が良い] ---- [部分的ネイティブSQL] ---- [全部動的SQL] 高い
        方言設定の変更        混在対応              全数検査が必要

実務で効果が大きいアプローチは、地図を作っておくことです。コードベース全体からOracle固有の関数と慣用句を正規表現で拾い、どこに何回出るかの一覧を作ります。NVL、DECODE、SYSDATE、ROWNUM、CONNECT BY、ダミーテーブルのようなパターンを優先順位にして一つずつ片付けていきます。最も危険なのは「見つけたと思ったが動的に生成されて静的検索に引っかからない」SQLなので、統合テストで実際の実行経路を覆うのが安全です。

検証 — データ整合性と性能リグレッション

移したデータが正しいか、移したシステムが十分に速いかを証明できなければ、移行は終わったことになりません。検証は二つの軸で見ます。

第一はデータ整合性です。両方のデータベースの行数、集計値、標本行を比較します。最も単純でありながら強力な方法は、テーブルごとの行数と主要列のチェックサムを両方から取り出して合わせてみることです。

-- PostgreSQL 側: テーブルごとの行数と金額合計
SELECT 'orders' AS tbl, count(*) AS rows, sum(amount) AS sum_amount
FROM orders;

-- Oracle 側でも同じ値を取り出して比較
-- SELECT 'orders', COUNT(*), SUM(amount) FROM orders;

第二は性能リグレッションです。移行後に遅くなったクエリを見つけ出さなければなりません。PostgreSQLのpg_stat_statements拡張は、どのクエリが総時間を多く食うかを集計してくれます。運用負荷を再現した環境でこの統計を比較すれば、リグレッションが発生したクエリを正確に指せます。

-- 遅いクエリの上位一覧 (総実行時間ベース)
SELECT
  substr(query, 1, 60) AS q,
  calls,
  round(total_exec_time::numeric, 1) AS total_ms,
  round(mean_exec_time::numeric, 2)  AS mean_ms
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

性能リグレッションのよくある原因は、統計不足、インデックス欠落、NUMBERをnumericへ移して生じた遅い算術、そして行単位ループをそのまま移したPL/pgSQLです。EXPLAIN ANALYZEで計画を直接覗き込む習慣が、結局最も速い診断になります。

段階的移行 — 一度にすべて移さない

規模の大きなシステムを一度のビッグバンで移すのは危険です。段階的移行が標準です。よく使う戦略は三つです。

第一は読み取り優先移行です。まず読み取りトラフィックだけをPostgreSQLのレプリカへ回して負荷と結果を検証し、十分に安定したら書き込みまで移します。

第二はドメイン単位の分割です。システムを業務ドメインへ割って、結合度の低いドメインから一つずつ移します。一つのドメインを移して運用しながら方法論を練り、その経験を次のドメインへ適用します。

第三は並行運用とシャドウ比較です。しばらく二つのデータベースに同じ書き込みを流し、読み取り結果を両方から受け取って比較します。差が出れば警報を鳴らしつつ、利用者には既存システムの結果を返します。十分に信頼が積み上がったら主力をPostgreSQLへ変えます。

段階的移行のタイムライン例

段階1  スキーマ/コード変換 + テスト環境構築
段階2  データ初期ロード + CDC で並行運用開始
段階3  読み取りトラフィックの一部を PostgreSQL へ転換 (シャドウ比較)
段階4  ドメイン単位で書き込みトラフィックを漸進転換
段階5  Oracle を読み取り専用バックアップへ降格、一定後に廃棄

どの戦略でも核心は、戻せなければならないという点です。各段階ごとに問題が起きたら即座に以前の状態へ戻る道を前もって作っておく必要があります。ロールバック経路のない移行は賭けです。

事例研究 — 決済精算システムの移行

仮想の事例で流れをまとめてみます。ある決済精算システムがOracleの上で十年以上回っていました。テーブル約400個、PL/SQLパッケージ約6万行、夜間バッチが精算の核心でした。ライセンス費用削減とクラウド移行が動機でした。

チームはまずora2pgで評価レポートを取り出しました。スキーマは自動変換率が高かったものの、パッケージ6万行のうち精算の核心ロジック約1万5千行が手動変換の対象として表示されました。ここで工程の大部分が取られました。

次にデータ型を整理しました。無分別に使われたNUMBERを整数とnumericへ区分し、DATEをtimestamptzへ統一しました。精算金額の列はnumericを維持しつつ、識別子とカウントはbigintへ狭めました。この整理だけでも夜間バッチの算術演算が目に見えて速くなりました。

PL/SQL変換では行単位のカーソルループを集合ベースのSQLへ書き直すことに集中しました。元は一件ずつ回るループで二時間かかっていた精算バッチを、集合演算で書き直すと二十分に縮みました。移行がむしろ性能を改善したわけです。

データはAWS DMSで初期ロード後、CDCで二か月間並行運用しました。毎晩両方の精算結果をシャドウ比較し、差が0で維持されることを確認してからカットオーバーしました。カットオーバー自体は週末の未明に一時間以内で終わりました。最も恐ろしい段階が最も短く静かに過ぎたのは、その前の二か月が充実していたからです。

よくある落とし穴

移行プロジェクトで繰り返し人々をつまずかせる落とし穴を集めました。

第一に、空文字列とNULLの違いです。Oracleは空文字列をNULLとして扱いますが、PostgreSQLは空文字列とNULLを厳密に区別します。この違いは条件節とユニーク制約で微妙なバグを生みます。

第二に、大文字小文字の識別子です。Oracleは引用符なしの識別子を大文字へ畳み込み、PostgreSQLは小文字へ畳み込みます。引用符で包んだ識別子が混ざっていると、両方で異なって解釈されます。

第三に、暗黙の型変換です。Oracleは文字と数値をかなり寛容に自動変換しますが、PostgreSQLはより厳格です。数値列に文字列を比較していたコードが移行後にエラーを出します。

第四に、トランザクション分離とロック動作の違いです。動作が異なると並行性バグが運用でのみまれに起きます。負荷テストで並行性経路を必ず覆わなければなりません。

第五に、整列順序です。ロケールによって文字列の整列結果が変わります。整列に依存するページングや比較が、移行後に異なる順序を出すことがあります。

第六に、ツールの自動変換を盲信することです。ツールは出発点であってゴールではありません。自動変換されたコードは必ず人が読み、テストで検証しなければなりません。

移行チェックリスト

最後に、プロジェクトの間ずっとそばに置いて点検するチェックリストを整理します。

  • 動機を数字に換算して意思決定の根拠を残したか
  • ora2pg または AWS SCT の評価レポートで難易度と工程を推定したか
  • NUMBER や DATE などのデータ型を意味に合わせて整理したか
  • シーケンスを移行後にデータ最大値へ同期したか
  • 空文字列とNULL、識別子の大文字小文字、暗黙の型変換を点検したか
  • オプティマイザヒントへの依存を統計とインデックス設計へ置き換えたか
  • 行単位のカーソルループを集合ベースのSQLへ書き直したか
  • アプリケーションの動的SQLまで統合テストで覆ったか
  • データ整合性を行数とチェックサムで両方比較したか
  • pg_stat_statements で性能リグレッションを追跡したか
  • 各段階ごとにロールバック経路を用意したか
  • カットオーバー前に十分な期間の並行運用とシャドウ比較をしたか

おわりに

異種DBエンジン移行は華やかな新技術の導入ではなく、地味で綿密な土木工事に近いです。しかしその土木工事の果てには、ライセンス費用からの解放、クラウド適合、そしてベンダーロックインから抜け出した自由が待っています。核心は、移行を五つの別個の作業へ割り、ツールで自動化できる部分と人が書き直すべき部分を区別し、戻せる段階へ分けて進めることです。

そして一つの慰めがあります。OracleからPostgreSQLへ向かう道で身につけた方法論は、MySQLからPostgreSQLへ行くにせよ、SQL Serverから別のところへ行くにせよ、ほぼそのまま通じます。データ型マッピング、方言の違い、コード変換、データ同期、段階的カットオーバーという骨格は、あらゆる異種移行で同一だからです。一度きちんとやっておけば、その先の移行はずっと怖くなくなります。

参考資料