2021年5月31日月曜日

アプリケーションごとにリソース・マネージャーのコンシューマー・グループを切り替えてみる

 Oracle DatabaseのEnterprise Edition限定ですが、リソース・マネージャーという機能があり、使用リソースの制御が可能になっています。Oracle Corporationが提供している無料で使える検証環境のapex.oracle.comでは、このリソース・マネージャーを使用して、リソースを有効活用しています。どのような設定を行なっているかは、こちらの記事が参考になります。

10年以上前の記事ですが、恐らく現在でも同様の制御を行なっていると思われます。

発行されたSQLは最初はコンシューマー・グループAPEX_HIGHで実行されます。CPUリソースの70%が使用可能ですが、CPU時間が10秒を過ぎると(つまり10秒以内に処理が終了しなければ)、コンシューマー・グループはAPEX_MEDIUMに切り替わります。

コンシューマー・グループAPEX_MEDIUMでは、CPUリソースの8%のみ使用可能で、CPU時間が120秒を過ぎると、コンシューマー・グループはAPEX_LOWに切り替わります。

コンシューマー・グループAPEX_LOWでは、CPUリソースの2%のみ使用可能で、CPU時間が1800秒(30分)を過ぎるとSQLはキャンセルされます。

その他、必須であるコンシューマー・グループOTHER_GROUPSやメンテナンス・タスクのための設定を含め、リソース・プランとしてAPEX_ORACLE_COM_PLANを作成して、初期化パラメーターresource_manager_planに設定しています。

データベース全体への設定は以上の様になりますが、今回はデータベース全体ではなく、アプリケーションごとに設定する方法を試してみます。

プラガブル・データベースPDB1にOracle APEXがインストールされていて、ワークスペースAPEXDEVにアプリケーションが作成されていることを前提とします。関連づけられているデータベースのスキーマはAPEXDEVです。

最初にリソース・プランMY_PDB_PLANを作成します。リソース・プランにはコンシューマー・グループRESTRICT_RANAWAYを含みます。このコンシューマー・グループでは、SQLの経過時間が30秒に達すると、SQLをキャンセルします。

begin

  dbms_resource_manager.clear_pending_area();

  dbms_resource_manager.create_pending_area();


  dbms_resource_manager.create_consumer_group(

    consumer_group => 'RESTRICT_RUNAWAY');


  dbms_resource_manager.create_plan(

    plan => 'MY_PDB_PLAN');


  dbms_resource_manager.create_plan_directive(

    plan => 'MY_PDB_PLAN',

    group_or_subplan => 'RESTRICT_RUNAWAY',

    switch_group => 'CANCEL_SQL',

    switch_for_call => TRUE,

    switch_elapsed_time => 30);


  dbms_resource_manager.create_plan_directive(

    plan => 'MY_PDB_PLAN',

    group_or_subplan => 'OTHER_GROUPS');


  dbms_resource_manager.validate_pending_area();

  dbms_resource_manager.submit_pending_area();

end;

/

PDB1にSYSで接続し、上記のスクリプトを実行します。

作成したプランはビューDBA_RSRC_PLANSから確認できます。

SQL> select * from dba_rsrc_plans where plan = 'MY_PDB_PLAN';


   PLAN_ID           PLAN    NUM_PLAN_DIRECTIVES    CPU_METHOD    MGMT_METHOD         ACTIVE_SESS_POOL_MTH         PARALLEL_DEGREE_LIMIT_MTH    QUEUEING_MTH    SUB_PLAN    COMMENTS    STATUS    MANDATORY 

__________ ______________ ______________________ _____________ ______________ ____________________________ _________________________________ _______________ ___________ ___________ _________ ____________ 

     80044 MY_PDB_PLAN                         2 EMPHASIS      EMPHASIS       ACTIVE_SESS_POOL_ABSOLUTE    PARALLEL_DEGREE_LIMIT_ABSOLUTE    FIFO_TIMEOUT    NO                                NO           


SQL> 

PDB1にリソース・プランを設定します。ALTER SYSTEMを発行します。

SQL> alter system set resource_manager_plan = 'MY_PDB_PLAN' scope=both;


System SETが変更されました。


SQL> show parameter resource_manager_plan

NAME                  TYPE   VALUE       

--------------------- ------ ----------- 

resource_manager_plan string MY_PDB_PLAN 

SQL> 

ユーザーAPEXDEVがコンシューマー・グループRESTRICT_RUNAWAYを使用できる様に、権限を与えます。SYSで実行します。

begin

    dbms_resource_manager_privs.grant_switch_consumer_group ( 

        grantee_name => 'APEXDEV', 

        consumer_group => 'RESTRICT_RUNAWAY', 

        grant_option => FALSE );

end;

/

SQLがキャンセルされるかどうか、ユーザーAPEXDEVでPDB1に接続して確認します。

接続したのち、以下のスクリプトを実行して、セッションにコンシューマー・グループRESTRICT_RUNAWAYを設定します。

declare

  old_group varchar2(30);

begin

  dbms_session.switch_current_consumer_group('RESTRICT_RUNAWAY', old_group, FALSE);

end;

/

確認のため、以下のSQLを実行します。

select count(*) from all_objects a, all_objects b;

30秒が経過するとORA-56735が発生し、SELECT文がキャンセルされます。

23:39:11 SQL> select count(*) from all_objects a, all_objects b;

select count(*) from all_objects a, all_objects b

                     *

ERROR at line 1:

ORA-56735: elapsed time limit exceeded - call aborted



23:39:47 SQL> 

これで、リソース・プランMY_PDB_PLANとコンシューマー・グループRESTRICT_RUNAWAYが適切に設定されていることが確認できました。

では、コンシューマー・グループの設定をアプリケーションに適用してみます。

確認に使用するアプリケーションを作成します。アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。名前リソースマネージャ確認とし、アプリケーションの作成を実行します。


アプリケーションが作成されたら、ページ・デザイナでホーム・ページ(ページ番号1)を開き、クラシック・レポートのリージョンを作成します。

リージョンの作成を実行します。識別タイトル高負荷SQLとし、タイプクラシック・レポートとします。ソース位置ローカル・データベースタイプとしてSQL問合せを選択します。SQL問合せには、以下を指定します。

select count(*) from all_objects a, all_objects b;



クラシック・レポートのリージョンのAttributesを開き、遅延ロードONにします。クラシック・レポートの遅延ロードはOracle APEX 21.1で追加された新機能です。


この状態でアプリケーションを実行します。サインインの後、ホーム・ページが表示されますが、レポートの表示は延々終了しません。


Oracle APEX 21.1以前で遅延ロードの設定ができない場合は、ホーム・ページの表示に延々と時間がかかり、表示されません。最終的にはエラーが発生します。


延々終了しないのも困るため、コンシューマー・グループRESTRICT_RUNAWAYで設定し、経過時間が30秒に達したらSQLの実行をキャンセルさせます。

コンシューマー・グループの設定は、アプリケーション定義セキュリティから行います。データベース・セッションのタブに含まれる設定から、コンシューマー・グループの切り替えを行います。


初期化PL/SQLコードとして、以下を記載します。dbms_session.switch_current_consumer_groupを呼び出して、コンシューマー・グループをRESTRICT_RUNAWAYに切り替えます。

declare
old_group varchar2(30);
begin
dbms_session.switch_current_consumer_group('RESTRICT_RUNAWAY', old_group, FALSE);
apex_debug.info('Previous Consumer Group is ' || nvl(old_group, 'not set.'));
end;

PL/SQLコードのクリーンアップとして、以下を記載します。コンシューマー・グループの設定を外します。

declare
old_group varchar2(30);
begin
dbms_session.switch_current_consumer_group('', old_group, FALSE);
apex_debug.info('Previous Consumer Group is ' || nvl(old_group, 'not set.'));
end;

上記の設定を行いアプリケーションを実行するとコンシューマー・グループによる制限が適用され、レポートの表示で「ORA-56735: 経過時間の制限を超えました - コールは中断されました」が発生します。



アプリケーションごとのコンシューマー・グループの切り替えは以上です。とはいえ、このままではページを初期化する時点でコンシューマー・グループが設定済みの場合に、クリーンアップのコードで元に戻すことができません。

初期化PL/SQLコードのヘルプ、PL/SQLコードのクリーンアップのヘルプに記載があるコンテキストを使用して、設定済みのコンシューマー・グループに戻す様にコードを拡張してみます。

PDB1にSYSで接続し、コンテキストの作成権限をユーザーAPEXDEVに与えます。

grant create any context to apexdev;


ユーザーAPEXDEVで接続し、コンテキストを作成します。この操作はOracle APEXのSQLワークショップSQLコマンドからも実行できます。

create context ctx_consumer_group using ctx_consumer_group_pkg;

コンテキストの操作はパッケージCTX_CONSUMER_GROUP_PKGに含まれるプロシージャから行われます。DBMS_SESSION.SET_CONTEXTやCLEAR_CONTEXTを直接呼び出すことはできません。

パッケージCTX_CONSUMER_GROUP_PKGを作成します。SETとCLEARのふたつのプロシージャーを定義します。

create or replace package ctx_consumer_group_pkg is
procedure set
(
p_group in varchar2
);
procedure clear;
end;
/

パッケージ本体を作成します。

create or replace package body ctx_consumer_group_pkg is
procedure set(
p_group in varchar2
)
is
l_old_group varchar2(30);
begin
apex_debug.info('consumer group is set to ' || p_group);
dbms_session.switch_current_consumer_group(p_group, l_old_group, FALSE);
apex_debug.info('preserve old consumer group ' || l_old_group);
dbms_session.set_context('ctx_consumer_group','name',l_old_group);
end set;
procedure clear
is
l_group varchar2(30);
l_old_group varchar2(30);
begin
l_group := sys_context('ctx_consumer_group','name');
apex_debug.info('recover old consumer group ' || l_group);
dbms_session.switch_current_consumer_group(l_group, l_old_group, FALSE);
apex_debug.info('previous consumer group ' || l_old_group);
dbms_session.clear_context('ctx_consumer_group');
end clear;
end ctx_consumer_group_pkg;
/

プロシージャSETでは指定されたコンシューマー・グループに切り替え、

dbms_session.switch_current_consumer_group(p_group, l_old_group, FALSE);

以前に設定されていたコンシューマー・グループをコンテキストctx_consumer_groupにnameとして保存します。

dbms_session.set_context('ctx_consumer_group','name',l_old_group);

プロシージャーCLEARでは、コンテキストctx_sonsumer_groupにnameとして保存されている値を取り出し、

l_group := sys_context('ctx_consumer_group','name');

コンシューマー・グループを以前のグループに戻し、

dbms_session.switch_current_consumer_group(l_group, l_old_group, FALSE);

コンテキストctx_consumer_groupをクリアしています。

dbms_session.clear_context('ctx_consumer_group');

アプリケーション定義のデータベース・セッションでは、それぞれ1行の記載に変更します。


初期化PL/SQLコードでは、以下を呼び出します。

ctx_consumer_group_pkg.set('RESTRICT_RUNAWAY');

PL/SQLコードのクリーンアップでは、以下を呼び出します。

ctx_consumer_group_pkg.clear;

以上で設定の改変は完了です。アプリケーションを実行してみます。


コンシューマー・グループの設定は変わっていないため、ORA-56735が発生します。

今回はコンシューマー・グループの設定にコンテキストを使用しました。

実際のところコンテキストは、仮想プライベート・データベース - Virutal Private Database(VPD) - とともに利用することが想定されている機能です。Oracle APEXのアプリケーションで仮想プライベート・データベースを活用する方法については、機会を改めて紹介したいと思います。

今回作成したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/resource-manager.sql

単にアプリケーションだけです。アプリケーションを動かすには、SYSで必要なコマンド(grant文など)を実行しておきます。

以上になります。Oracle APEXのアプリケーション作成の参考になれば幸いです。

翻訳済みの文字列を変更する

 Oracle APEXの標準コンポーネントが表示している文字列は、コンポーネントとしてはキーとなる名前を持っているだけで、実際に表示している文字列はWWV_FLOW_MESSAGES$から取り出されています。XLIFFファイルの翻訳アプリケーションを作成していると、以下のレポートを表示することができます。

例えば、対話グリッドのメッセージの名前は概ねAPEX.IG.から始まります。対話モード・レポートはAPEXIRから始まります。

これらのメッセージを、共有コンポーネントグローバリゼーションに含まれるテキスト・メッセージとして設定すると、アプリケーションに設定した文字列が、システムのデフォルトより優先されます。

実際に置き換えてみて確認してみます。以下の操作は、サンプル・データセットのEMP/DEPTがインストール済みであることを前提としています。しかし、例として使用する表EMPは、異なる表を使っても同様の操作は可能です。

最初にアプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。アプリケーションの名前翻訳の置き換えとします。言語(アプリケーションのプライマリ言語)が日本語(ja)であることを確認します。アプリケーションの作成を実行します。

アプリケーションが作成されたら、ホーム・ページをページ・デザイナで開き、対話グリッドのリージョンの作成を実行します。識別タイトル対話グリットとし、タイプとして対話グリッドを選択します。ソース表名EMP(EMP以外を選択してもかまいません)を選択します。

対話モード・レポートのリージョンも追加します。リージョンの作成を実行し、識別タイトル対話モード・レポートとします。タイプ対話モード・レポートを選択し、ソース表名としてEMPを選択します。

アプリケーションを実行すると、以下の画面が表示されます。今回は、これらのレポートで表示されているアクションという文字列をレポートの操作に変更してみます。

ある程度Oracle APEXの利用経験がある人たちを想定しているのであれば、標準の文字列を変更すると使い方がわからなくなる可能性はあります。逆に本当のエンドユーザーでAPEXを使った経験はなく、今後もAPEXのアプリケーションを意識して使うことはない人たちを対象としているのであれば、翻訳文字列を置き換えるのは効果があるかもしれません。

アプリケーションが想定している利用者を鑑みて判断するのがよいと思います。

対話グリッドに含まれる文字列は、名前が概ねAPEX.IG.から始まります。XIFF翻訳ファイルのアプリを使って、名前を見つけてみます。

フィルタの条件として、以下を指定します。

NameはAPEX.IG.で始まります。

Message Languageはjaと等しい。

Message Textはアクションと等しい。


NameとしてAPEX.IG.ACTIONSが見つかりました。

共有コンポーネントテキスト・メッセージを開きます。作成済みのテキスト・メッセージの一覧よりテキスト・メッセージの作成を実行します。

名前としてAPEX.IG.ACTIONSを指定します。言語には日本語(ja)を選択します。JavaScriptで使用はOFFのまま(ここで設定しても、システムとしてAPEX.IG.ACTIONSに定義されている設定に変更されます)、テキストレポートの操作に変更します。テキスト・メッセージの作成を実行します。

名前APEX.IG.ACTIONSがテキスト・メッセージとして作成されます。

同様にして名前APEXIR_ACTIONSテキストレポートの操作に変更します。

テキスト・メッセージとして作成されます。JavaScriptで使用の属性は、双方OFFを指定していますが、結果として、APEX.IG.ACTIONSはいAPEXIR_ACTIONSいいえになっていることが確認できます。

ホーム・ページをリフレッシュすると、アクションとなっていたメニューがレポートの操作に変わっていることが確認できます。


翻訳済みテキストを取得するPL/SQL APIは、APEX_LANG.MESSAGEです。このファンクションの実行結果も確認してみます。

タイプPL/SQL動的コンテンツのリージョンを作成し、以下のコードをソースPL/SQLコードに記述します。

declare
l_name varchar2(20);
begin
l_name := 'APEX.IG.ACTIONS';
htp.p(l_name || ' = ' || apex_lang.message(l_name));
htp.br;
l_name := 'APEXIR_ACTIONS';
htp.p(l_name || ' = ' || apex_lang.message(l_name));
end;

リージョンの識別タイトルAPIとしました。

ページを実行すると、APIの呼び出しでもアプリケーションに作成したテキストが優先されることが確認できます。

翻訳文字列を扱うJavaScript APIはapex.lang.getMessageです。こちらも確認してみましょう。

先程作成したリージョンAPIにページ・アイテムの作成を行います。識別名前P1_MESSAGEタイプテキスト・フィールドとします。ラベル文字列とします。


動的アクション・ビューを開き、ページのロードで動的アクションの作成を行います。識別名前メッセージの設定とします。タイミングイベントページのロードになります。

Trueアクションとして値の設定を指定します。設定タイプの設定JavaScript Expressionを選択し、以下のJavaScript式を記述します。影響を受ける要素選択タイプアイテムとし、アイテムP1_MESSAGEを指定します。

apex.lang.getMessage("APEX.IG.ACTIONS")


以上で、JavaScript APIを使った確認ができるようになりました。ページを実行します。


引数をAPEX.IG.ACTIONSとしたapex.lang.getMessageの結果が、レポートの操作になっていることが確認できます。APEXIR_ACTIONSはJavaScriptで使用いいえなので、JavaScript APIでは参照できません(対話モード・レポートはJavaScriptでの実装ではないので、JavaScript APIから参照する必要がありません)。

確認に使用したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/translated-message.sql

Oracle APEXのアプリケーション作成の参考になれば幸いです。

2021年5月30日日曜日

RESAS APIを呼び出してデータを取得する

日本政府が公開しているデータを手入力する手間を省くために RESAS APIを呼び出すことにしました。RESASには、こちらからアクセスします。ヘルプにある紹介を抜粋します。

地域経済分析システム(RESAS:リーサス)は、地方創生の様々な取り組みを情報面から支援するために、経済産業省と内閣官房(まち・ひと・しごと創生本部事務局)が提供しています。
自治体職員の方や、地域の活性化に関心を持つ様々な分野の方によって、効果的な施策の立案・実行・検証のためなどに広く利用されています。

以下、Oracle APEXのアプリケーションから、RESAS APIを使って都道府県一覧を取り出し、データベースに保存するまでの作業ログです。

最初にRESAS APIの利用者登録を行ないます。RESAS APIのトップ・ページを開きます。


RESAS-API利用者登録のフォームが開くので、必要事項を入力してIDを取得します。


取得したIDでログインをすると、マイページにアクセスできるようになります。


マイページからAPIキーの取得/確認ができます。


Oracle APEXのRESTデータ・ソースを使って、RESAS APIを呼び出すことにします。受け皿となるアプリケーションを作成します。アプリケーションの作成を開始し、名前RESAS情報とします。他の設定はせず、そのままアプリケーションの作成をクリックします。


アプリケーションが作成されたら、RESTデータ・ソースを作成します。共有コンポーネントRESTデータ・ソースを開きます。


RESTデータ・ソースの一覧より、作成をクリックします。


RESTデータ・ソースの作成として最初からを選んで、へ進みます。


今回はRESAS APIを呼び出して都道府県一覧を取得します。参照したAPIの仕様はこちらです。

RESTデータ・ソース・タイプ簡易HTTPを選択します。名前RESAS - 都道府県一覧としました。URLエンドポイントhttps://opendata.resas-portal.go.jp/api/v1/prefecturesを入力し、へ進みます。


リモート・サーバー、ベースURLおよびサービスURLパスは変更不要です。確認だけを行い、へ進みます。


認証が必要です。ONにします。APIキーの設定方法はRESAS API詳細仕様に記載されている通り、HTTPのリクエスト・ヘッダーとしてX-API-KEYを追加し、値としてAPIキーを指定します。

資格証明- 新しい資格証明の入力 -を選択します。認証タイプHTTPヘッダー名前X-API-KEYです。に取得済みのAPIキーを入力します。検出をクリックします。


REST APIが発行され、取得されたデータが表示されます。データ・プロファイルも確認しておきます。


PREFCODEがNUMBER型、PREFNAMEがVARCHAR2(4000)として認識されていることが確認できます。RESTデータ・ソースの作成を実行します。


RESTデータ・ソースが作成されました。


作成されたRESTデータ・ソースの内容を確認するため、レポートのページを作成します。ページ作成ウィザードを開始し、レポートをクリックします。


対話モード・レポートをクリックします。


ページ名都道府県一覧とします。へ進みます。


ナビゲーションのプリファレンスとして、新規ナビゲーション・メニュー・エントリの作成を選択し、へ進みます。


データ・ソースとしてRESTデータ・ソースを選択し、RESTデータ・ソースに先程作成したRESAS - 都道府県一覧を指定します。作成をクリックします。


ページが作成されたら、それを実行して結果を確認します。


47都道府県の情報がリストされます。


RESAS APIを呼び出すことで、都道府県一覧を取得できることが確認できました。

今まで作成した設定を再利用しやすくするために、設定内容を調整します。

最初に共有コンポーネントWeb資格証明を開きます。


作成済みのWeb資格証明が一覧されます。作成されたWeb資格証明はワークスペースで共有されるため、実際にはワークスペース・ユーティリティWeb資格証明が開いています。また、このアプリケーションだけではなく、ワークスペース内で作成済みのWeb資格証明が一覧されます。作成したWeb資格証明を開きます。


作成されている資格証明は都道府県一覧だけではなく、RESAS API全般で有効であるため、名前RESASの資格証明に変更します。また、静的識別子RESAS_API_KEYとします。この資格証明はURLに対して有効に設定されているhttps://opendata.resas-portal.go.jp/api/v1/以下へのアクセスに使うことができます。変更の適用をクリックします。


Web資格証明の参照は内部IDに基づいているため、名前や静的識別子の変更の影響はありません。PL/SQLコード内でWeb資格証明の静的識別子を参照している場合(APEX_WEB_SERVICE.MAKE_REST_REQESTなどの呼び出しの引数として)は、参照しているコードの変更が必要になります。

今回は都道府県のコードと名前をAPI経由で取得していますが、これは変更が発生しない情報です。ですので、ローカルの表にコピーし、そちらを参照することによりREST API呼び出しを行わない様にします。

共有コンポーネントRESTデータ・ソースを開き、作成したRESTデータ・ソースの同期化を設定します。同期化の管理を実行します。


同期先新規表とし、表名としてRESAS_PREFECTURESを指定します。保存をクリックします。


ここで指定した同期化表を新たに作成します。表の作成をクリックします。

表はRESTデータ・ソースとして検出されたデータ・プロファイルをもとに定義されます。SQLの表示をクリックすると、作成される表のDDLを確認できます。主キー制約やNOT NULL制約の追加など、あらかじめ設定した表を先に作成して、同期化表として指定することもできます。RESTデータ・ソースの同期に影響のない範囲であれば、ここで新規に作成された表の定義を後から変更することもできます。

create table "RESAS_PREFECTURES"(
"PREFCODE" NUMBER
,"PREFNAME" VARCHAR2(4000)
,"APEX$SYNC_STEP_STATIC_ID" VARCHAR2(255)
,"APEX$ROW_SYNC_TIMESTAMP" TIMESTAMP WITH TIME ZONE
)
/


表が作成されたら、保存して実行をクリックします。都道府県一覧が変わることはないため、定期的な同期処理は不要です。保存と実行で行われる一回だけの処理でデータのコピーは完了します。


同期化された内容を確認するため、先程作成した都道府県一覧のレポートのページを開きます。対話モード・レポートのソース同期化表の使用ONに変更します。


ページを実行し、レポートの内容に変化が無いことを確認します。



RESAS APIを呼び出してデータを取得する方法の紹介は以上になります。

今回作成したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/resas-api.sql

Oracle APEXのアプリケーション作成の参考になれば幸いです。

ちなみに登録したWeb資格証明の資格証明シークレットはエクスポートの対象からは除外されます。ですので、アプリケーションをインポートした後に自分自身で取得したRESAS APIのAPIキーを登録する必要があります。