PL/SQLのデプロイをCI/CDへ組み込むとき、単純に CREATE OR REPLACE PACKAGE を流すだけでは、接続中セッション、依存オブジェクト、スキーマ変更、切り戻しの問題が残ります。OracleのEdition-Based Redefinition(EBR)を使うと、新旧のPL/SQLやビューをEdition単位で共存させ、既存セッションを止めずに段階切替できます。
この記事では、DBA初期設定と通常デプロイを分離し、Edition作成、Editioning View、Crossedition Trigger、SQLclランナー、品質ゲート、フェイルバックまでを実務の順序で整理します。EBR単体の基本は PL/SQLのEBR完全ガイド、INVALID再コンパイルは 依存オブジェクトとINVALID再コンパイルの制御 もあわせて確認してください。
- CI/CDで毎回実行する処理と、DBAが一度だけ行う初期設定を分ける
CREATE EDITION ... AS CHILD OF ...で新Editionを作る- Editioning Viewで表構造の変更を吸収する
- Crossedition Triggerで切替期間中のデータ整合を維持する
- SQLclでデプロイ順序を固定し、品質ゲートで失敗を検出する
- 問題発生時に旧Editionへフェイルバックする
DBA初期設定と通常デプロイを分離する
ALTER USER ... ENABLE EDITIONS は毎回のCI/CDで実行する処理ではありません。これはスキーマをEdition対応にする初期設定であり、通常はDBA作業として一度だけ実行します。CI/CDの通常デプロイでは、新Editionを作成し、そのEditionへアプリケーションオブジェクトをコンパイルします。
-- DBAまたは権限を持つ管理者が一度だけ実行する初期設定 ALTER USER app_user ENABLE EDITIONS; -- Edition利用を明示的に許可する運用の場合 GRANT USE ON EDITION ora$base TO app_user; -- 以後のデプロイでは、通常この初期設定を繰り返さない
初期設定をCIランナーに混ぜると、不要な権限をCIに渡すことになり、監査上も危険です。CIユーザーには、通常デプロイに必要な範囲だけを付与し、DBA権限や全体設定の変更権限は分離します。
新Editionを作成してセッションを切り替える
通常のデプロイでは、親Editionを指定して子Editionを作成し、以後のDDLをそのEdition上で実行します。Oracleの構文は CREATE EDITION app_v2 AS CHILD OF app_v1 です。既存記事やメモで UNDER のような表現を見かけても、そのまま使わないよう注意してください。
-- 例: app_v1 を親にして app_v2 を作る
CREATE EDITION app_v2 AS CHILD OF app_v1;
-- この接続で以後のコンパイル先を app_v2 にする
ALTER SESSION SET EDITION = app_v2;
-- 確認
SELECT SYS_CONTEXT('USERENV', 'CURRENT_EDITION_NAME') AS current_edition
FROM dual;
Edition名はリリース番号や日時を含めて機械的に作ると運用しやすくなります。ただし、アプリコードの中へEdition名を直接埋め込むと切替が硬くなるため、接続プール、サービス、デプロイ変数などで管理します。
Editioning Viewで表構造の変更を吸収する
表そのものはEdition化されないため、アプリやPL/SQLが実表を直接参照していると、列追加や意味変更の影響を受けやすくなります。公開インターフェースとしてEditioning Viewを置き、Editionごとに見せ方を変えることで、旧Editionと新Editionを並行稼働させやすくなります。
-- 実表は非Edition化オブジェクト CREATE TABLE t_order( id NUMBER PRIMARY KEY, amount NUMBER(12,2) NOT NULL, created_at TIMESTAMP NOT NULL ); -- app_v1 の公開インターフェース ALTER SESSION SET EDITION = app_v1; CREATE OR REPLACE EDITIONING VIEW v_order AS SELECT id, amount, created_at FROM t_order; -- 後方互換の実表拡張 ALTER TABLE t_order ADD ( currency CHAR(3) DEFAULT 'JPY' NOT NULL ); -- app_v2 では新列も公開する ALTER SESSION SET EDITION = app_v2; CREATE OR REPLACE EDITIONING VIEW v_order AS SELECT id, amount, created_at, currency FROM t_order;
アプリケーションやPL/SQLは t_order を直接参照せず、v_order を参照する方針に統一します。これにより、表の拡張とアプリの切替タイミングを分離できます。
Crossedition Triggerで切替期間中の整合を保つ
列の追加だけならEditioning Viewで吸収できますが、列の分割、正規化、意味変更を伴う場合は、旧EditionからのDMLと新Editionのデータ構造を橋渡しする必要があります。この期間だけ使うのがCrossedition Triggerです。長期間残すものではなく、切替が終わったら撤去する前提で設計します。
-- 例: app_v2で fee 列を追加し、旧 amount から補完する
ALTER TABLE t_order ADD (
fee NUMBER(12,2) DEFAULT 0 NOT NULL
);
-- 切替期間中、旧インターフェースからのDMLを新列へ補完する
CREATE OR REPLACE TRIGGER t_order_fee_fwd
BEFORE INSERT OR UPDATE OF amount ON t_order
FOR EACH ROW
FORWARD CROSSEDITION
BEGIN
IF :NEW.fee IS NULL OR :NEW.fee = 0 THEN
:NEW.fee := ROUND(:NEW.amount * 0.03, 2);
END IF;
END;
/
双方向に戻す必要がある期間は、forward/reverseの設計を明確にし、どちらのEditionから書かれたデータも整合するかを検証します。CETは通常のDMLパスに乗るため、負荷と実行期間を短くすることも重要です。
PL/SQLのコンパイル順序を固定する
CI/CDでは、ファイル名や実行順が毎回ぶれないようにします。表拡張、Editioning View、Crossedition Trigger、パッケージSPEC、パッケージBODY、再コンパイル、スモークテストの順に流すと、依存関係の破損を検出しやすくなります。コンパイルエラー検出は PL/SQLコンパイル時エラーと警告の対処、警告をCIで厳格化する方法は DBMS_WARNINGとPLSQL_WARNINGSの使い方 が関連します。
-- 01: 後方互換の表拡張 @01_tables_additive.sql -- 02: Editioning Viewを新Editionで更新 @02_editioning_views.sql -- 03: 必要な期間だけCrossedition Triggerを追加 @03_crossedition_triggers.sql -- 10: 公開仕様を先にコンパイル @10_package_specs.sql -- 11: 実装を後からコンパイル @11_package_bodies.sql -- 90: 再コンパイルと品質ゲート @90_quality_gate.sql
SQLclでCI/CDランナーを作る
CI/CDからSQLclを非対話で実行する場合は、エラー時に必ずプロセスを失敗させます。また、Edition有効化のようなDBA初期設定はここに含めず、通常デプロイに必要な処理だけを実行します。
-- deploy.sql WHENEVER SQLERROR EXIT SQL.SQLCODE SET DEFINE OFF SET SERVEROUTPUT ON ALTER SESSION SET EDITION = app_v2; @01_tables_additive.sql @02_editioning_views.sql @03_crossedition_triggers.sql @10_package_specs.sql @11_package_bodies.sql @90_quality_gate.sql EXIT 0
name: deploy-plsql
on:
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy PL/SQL to new edition
run: |
sql app_user/${DB_PASSWORD}@${DB_CONNECT} @deploy.sql
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_CONNECT: ${{ secrets.DB_CONNECT }}
本格運用では、DB接続情報をGitHub Actions Secretsなどに入れ、ログにパスワードが出ないようにします。ジョブの履歴や異常検知をDB側にも残す場合は DBMS_SCHEDULERとロギングによるジョブ実行履歴管理 が参考になります。
品質ゲートSQLで失敗を自動検出する
デプロイ後に「SQLclは成功したが、実はINVALIDが残っていた」という状態を避けるため、品質ゲートSQLを必ず置きます。標準出力の文字列解析だけに頼らず、Oracleのディクショナリビューを見て明示的に失敗させます。
SET SERVEROUTPUT ON
DECLARE
v_invalid_count NUMBER;
v_error_count NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_invalid_count
FROM user_objects
WHERE status = 'INVALID';
SELECT COUNT(*)
INTO v_error_count
FROM user_errors;
IF v_invalid_count > 0 OR v_error_count > 0 THEN
DBMS_OUTPUT.PUT_LINE('INVALID objects: ' || v_invalid_count);
DBMS_OUTPUT.PUT_LINE('USER_ERRORS: ' || v_error_count);
RAISE_APPLICATION_ERROR(-20001, 'PL/SQL deploy quality gate failed');
END IF;
END;
/
SELECT object_name, object_type, status
FROM user_objects
WHERE status = 'INVALID'
ORDER BY object_type, object_name;
SELECT name, type, line, position, text
FROM user_errors
ORDER BY name, sequence;
SELECT name, type, referenced_name, referenced_type
FROM user_dependencies
WHERE referenced_name IN ('V_ORDER', 'T_ORDER')
ORDER BY name, referenced_name;
実行時の観測性を高めるには、デプロイIDやリリース番号を DBMS_APPLICATION_INFO で載せるのも有効です。詳しくは DBMS_APPLICATION_INFOを使ったインストゥルメンテーション設計 を参照してください。
接続プールとEdition切替の扱い
新Editionを作っても、既存セッションが自動的に新Editionへ移るわけではありません。アプリケーションサーバの接続プール、接続時の初期化SQL、サービス設定などで、新規接続をどのEditionへ向けるかを制御します。既存セッションは旧Editionで完走させ、新規セッションから段階的に新Editionへ流すのが安全です。
-- 接続プールの初期化SQLなどで実行する例
ALTER SESSION SET EDITION = app_v2;
-- アプリ起動直後の確認SQL
SELECT SYS_CONTEXT('USERENV', 'CURRENT_EDITION_NAME') AS current_edition
FROM dual;
AFTER LOGON ON DATABASE トリガで強制的にEditionを切り替える方法もありますが、影響範囲が大きいため慎重に扱います。使う場合は対象ユーザーや接続元を厳密に絞り、管理者接続やメンテナンス処理を巻き込まないようにします。
CREATE OR REPLACE TRIGGER set_app_edition_on_login
AFTER LOGON ON DATABASE
BEGIN
IF SYS_CONTEXT('USERENV', 'SESSION_USER') = 'APP_USER'
AND SYS_CONTEXT('USERENV', 'MODULE') LIKE 'app-api%' THEN
EXECUTE IMMEDIATE 'ALTER SESSION SET EDITION = app_v2';
END IF;
END;
/
実務では、全DBログオントリガより、アプリ側の接続初期化やサービス単位の制御を優先する方が事故範囲を小さくできます。
スモークテストとウォームアップを入れる
Edition切替直後は、主要パッケージやビューを軽く実行して、コンパイル漏れや権限漏れを早期に検出します。高負荷な処理ではなく、代表的なAPIを最小入力で呼ぶスモークテストを用意します。
ALTER SESSION SET EDITION = app_v2;
BEGIN
DBMS_APPLICATION_INFO.SET_MODULE(
module_name => 'plsql-deploy',
action_name => 'smoke-test-app-v2'
);
-- 主要ビューを軽く参照
DECLARE
v_count NUMBER;
BEGIN
SELECT COUNT(*) INTO v_count FROM v_order WHERE ROWNUM <= 1;
END;
-- 主要パッケージの疎通確認
app_order_api.health_check;
END;
/
スモークテストで一時障害を扱う場合は、無制限リトライにせず、回数と対象例外を限定します。例外分類は PL/SQLの例外設計と再試行パターン と組み合わせると整理しやすくなります。
フェイルバックとロールバックを分けて考える
EBRでは、問題発生時に新規接続を旧Editionへ戻すだけで、コード面のフェイルバックができます。一方、データレイアウト変更を完全に戻すロールバックは別問題です。破壊的変更を行う場合は、CETで双方向整合を維持できる期間を明確にし、その期間内だけ安全に戻せるようにします。
-- アプリ接続の初期化SQLを旧Editionへ戻す ALTER SESSION SET EDITION = app_v1; -- 既存の新Editionセッションが残っていないか確認する SELECT username, edition_name, status, module FROM v$session WHERE username = 'APP_USER'; -- 問題切り分けが終わるまで、新EditionやCETは急いでDROPしない
フェイルバック後に、旧EditionからのDMLが新列や新構造と矛盾しないかを確認します。CETや追加列を撤去するのは、全セッション移行、監査ログ確認、データ整合確認が終わってからです。
よくある落とし穴と回避策
まとめ
PL/SQLのコードデプロイをCI/CD化するなら、EBRを軸にして、Editioning Viewで公開インターフェースを安定させ、必要な期間だけCrossedition Triggerでデータ整合を橋渡しするのが基本です。DBA初期設定と通常デプロイを分離し、CIでは新Editionへのコンパイル、品質ゲート、スモークテストまでを自動化します。
重要なのは、デプロイを「DDLを流す作業」ではなく、「新Editionを作り、依存整合を検証し、新規接続を段階的に切り替え、問題時は旧Editionへ戻せる運用」として設計することです。この形にしておけば、PL/SQLの変更もアプリケーションコードと同じように、監査可能で戻しやすいリリースプロセスへ近づけられます。

