- リクエストごと(メモリーのみ)
- セッションごと(永続)
- ユーザーごと(永続)
アプリケーションに多数のページ・アイテムがあり、かつ、それらのセッション・ステートのストレージに永続が選択されていると、(程度の違いはありますが)APEXのページ表示のパフォーマンスに影響します。
原則として、ページ・アイテムの値をリクエストをまたがって保存する必要がなければ、セッション・ステートのストレージはリクエストごと(メモリーのみ)を選択します。
セッション・ステートのストレージに永続が設定された場合、ページ・アイテムの値はページが送信されたとき(一般に動作のアクションがページの送信であるボタンをブラウザ上でクリックしたとき = データベースがHTTPのPOSTリクエストに含まれるフォームのデータを受信したとき)にデータベースに保存されます。データベースに保存されているセッション・ステートは、ページのリクエストまたはAjaxのリクエストを受け付けたときに、データベースからメモリにロードされます。この際、リクエストを受け付けたページに含まれるページ・アイテムだけではなく、アプリケーションに含まれるすべてのページ・アイテムの値がメモリへロードされます。アプリケーションのページ数が多い、永続に設定されたページ・アイテムが多い、および、テキスト領域のページ・アイテムでデータ型にCLOBを設定していたりすると、リクエストごとに発生するデータベースからメモリへのセッションの復元コストが高くなります。特にデータ型がCLOBのページ・アイテムには注意が必要です。
セッション・ステートのストレージがリクエストごと(メモリーのみ)のページ・アイテムの値は、セッション・ステートとしてデータベースに保存されません。また、ページやAjaxコールバックの呼び出し時にデータベースからメモリにロードされることもありません。
セッション・ステートのストレージがリクエストごと(メモリーのみ)のページ・アイテムの値は、セッション・ステートとしてデータベースに保存されません。また、ページやAjaxコールバックの呼び出し時にデータベースからメモリにロードされることもありません。
動作確認のためのAPEXアプリケーションを作成し、セッション・ステートに関して説明します。
確認に使用するAPEXアプリケーションのエクスポートを以下に置きました。APEX 24.2で作成しています。
https://github.com/ujnak/apexapps/blob/master/exports/flow-data.zip
セッション・ステートのストレージに永続が設定されているページ・アイテムの値は、APEXのスキーマ以下の表WWV_FLOW_DATAに保存されます。APEX 24.2であれば、APEX_240200.WWV_FLOW_DATAになります。この表の内容をSQLclで接続して確認します。Autonomous DatabaseではAPEXのスキーマは保護されていてアクセスできない(管理者ADMINでもREAD権限はありません)ため、SYSDBAで接続できるローカルのAPEX環境で作業します。
確認に使用するアプリケーションについて簡単に説明します。アプリケーションは2枚のページを含みます。
ページ番号1の画面は以下になります。
ボタン同じセッションでページ2を開くには、ボタンのクリックで実行される動的アクションを設定しています。以下のJavaScriptコードを実行します。
apex.navigation.openInNewWindow(`${prefix}/flow-data/page-2?session=${apex.env.APP_SESSION}`);
prefixはページ・プロパティのJavaScriptのファンクションおよびグローバル変数の宣言で定義しています。アプリケーションをインストールした環境に合わせて設定を調整します。
const prefix = '/ords/r/apexdev';
ボタンクローンしたセッションでページ2を開くでは、以下のJavaScriptコードを実行します。引数requestにAPEX_CLONE_SESSIONを与えているので、セッションがクローンされます。
apex.navigation.openInNewWindow(`${prefix}/flow-data/page-2?session=${apex.env.APP_SESSION}&request=APEX_CLONE_SESSION`);
ページ・アイテムP1_SESSION_IDはAPEXのセッションIDを表示する、タイプが表示のみのページ・アイテムです。ソースのタイプにアイテムを選択し、アイテムとしてAPP_SESSIONを設定します。
表示のみのアイテムでもセッション・ステートのストレージの設定があります。セッション・ステートに保存する必要はないので、リクエストごと(メモリーのみ)にします。設定のページの送信時に送信がオフではなく、セッション・ステートのストレージに永続が設定されていると、タイプが表示のみのページ・アイテムの値もデータベースに保存されます。
ページ・アイテムP1_TEXTは、タイプがテキスト・フィールドのページ・アイテムです。セッション・ステートのストレージはセッションごと(永続)を設定します。
ソースの使用としてセッション・ステートの値がNULLの場合のみを指定します。この設定により、ページ・ロード時に設定されるP1_TEXTの値は、セッション・ステートに保存されている値が優先されます。セッション・ステートにP1_TEXTの値が保存されていない場合に、ソースに定義された値がP1_TEXTに設定されます。今回の例ではタイプはNULLなので、セッション・ステートにP1_TEXTの値が設定されていないときは、P1_TEXTの値はNULLになります。
よくある設定ミスですが、例えばP1_TEXTのソースのタイプとしてSQL問合せ(単一の値を返す)を選択し、SQL問合せに以下を記述します。
select to_char(sysdate, 'YYYY-MM-DD HH24:MI:SS') from dual
使用はセッション・ステートの値がNULLの場合のみとし、セッション・ステートのストレージはセッションごと(永続)とします。
ページ・アイテムP1_TEXTを空白にして、ページを送信します。
ページ・アイテムP1_TEXTの値はNULLとして送信され、セッション・ステートにNULLが保存されます。その後、ページが再描画されます。ページの再描画では、ページ・アイテムP1_TEXTのソースはセッション・ステートがNULLであるため、ソースに設定したSQLが評価されます。結果としてto_char(sysdate,'YYYY-MM-DD HH24:MI:SS')の値がP1_TEXTに設定されます。同時にこの値がセッション・ステートに保存されます。ページの再描画が完了すると、P1_TEXTにto_char(sysdate,'YYYY-MM-DD HH24:MI:SS')の値が表示されます。
この後はボタン送信を何度クリックしてもP1_TEXTの時刻は変わりません。ページ・アイテムP1_TEXTに設定されている時刻が送信され、セッション・ステートに保存されます。ページの再描画時は、セッション・ステートの値が優先され、ソースのSQL文は評価されません。
ページ・アイテムP1_CLOBは、タイプがテキスト領域のページ・アイテムです。セッション・ステートのデータ型にCLOB、ストレージはセッションごと(永続)を設定します。
ページ描画時に保存されているセッション・ステートの値を、静的コンテンツのリージョンに出力します。
ボタン送信 - セッション・ステートへの保存は、動作のアクションとしてページの送信を実行します。ページ・アイテムの値のセッション・ステートへの保存は、プロセスを設定しなくても行われます。
静的コンテンツのリージョンを作成し、ソースのHTMLコードとして以下を記述します。ページ2に作成したページ・アイテムの値も表示対象にします。
<b>ページ1のVARCHAR2:</b> &P1_TEXT.
<br>
<b>ページ1のCLOB:</b> &P1_CLOB.
<br>
<b>ページ2のVARCHAR2:</b> &P2_TEXT.
<br>
<b>ページ2のCLOB:</b> &P2_CLOB.
Ajaxコールバックを呼び出してセッション・ステートの値を取り出すボタンAjaxコールバックの呼び出しを作成します。ボタンをクリックしたときに、以下のサーバー側のコードを実行します。PL/SQLコードを実行し、出力をページ・アイテムP1_RESPONSEに出力します。
ページ1で実行しますが、ページ2に作成したページ・アイテムのセッション・ステートも取り出します。
declare
l_response clob;
begin
l_response := l_response || 'ページ1のVARCHAR2: ' || :P1_TEXT || apex_application.LF;
l_response := l_response || 'ページ1のCLOB: ' || :P1_CLOB || apex_application.LF;
l_response := l_response || 'ページ2のVARCHAR2: ' || :P2_TEXT || apex_application.LF;
l_response := l_response || 'ページ2のCLOB: ' || :P2_CLOB || apex_application.LF;
:P1_RESPONSE := l_response;
end;
Ajaxコールバックの出力を保持するページ・アイテムとしてP1_RESPONSEを作成します。タイプはテキスト領域、セッション・ステートのデータ型にCLOB、ストレージにリクエストごと(メモリーのみ)を設定します。ストレージが永続ではないため、このページ・アイテムの値はサーバーに送信されますが、セッション・ステートには保存されません。
ページ2はページ・アイテムの名前のプレフィックスがP1_からP2_に置き換えられているだけで、構成はページ1と同じです。ページ2なので、ページ2を開くボタンはありません。
動作確認に使用するアプリケーションの説明は以上です。
実際にアプリケーションを操作して、セッション・ストレージの永続設定について確認します。
ページ1を開き、VARCHAR2とCLOBに適当な文字列を入力して送信します。
データベースにSYSで接続し、カレント・スキーマをAPEX_240200に設定します。
alter session set current_schema = apex_240200;
SQL> alter session set current_schema = apex_240200;
Sessionが変更されました。
SQL>
表WWV_FLOW_DATAを検索します。FLOW_INSTANCEにセッションIDを指定して検索範囲を限定します。
select * from wwv_flow_data where flow_instance = [セッションID] and item_id > 0 order by item_name asc;
ページ・アイテムP1_TEXTとP1_CLOBの値が列ITEM_VALUE_VC2に保存されていることが確認できます。ページ・アイテムP1_CLOBのデータ型はCLOBですが、列
ITEM_VALUE_VC2に収まる長さであれば、列ITEM_VALUE_VC2に保存されます。
ページ・アイテムの値は列ITEM_VALUE_VC2に保存されています。IS_ENCRYPTがYなので、値は暗号化されています。
セッション・ステートの暗号化は、ページ・アイテムのセキュリティのセッション・ステートに暗号化された値を保存で制御します。デフォルトはオン(IS_ENCRYPT = Y)です。
同じセッションでページ2を開きます。
ページ2の静的コンテンツで、ページ1のページ・アイテムであるP1_TEXTおよびP1_CLOBの値が参照できています。アプリケーション全体のセッション・ステートがメモリにロードされているためです。
ページ・アイテムP2_CLOBの値が列ITEM_VALUE_CLOBに保存されていることが確認できます。SET LONGに80と設定されているため、CLOBとして保存されている値は先頭80文字までが表示されています。
ページ・アイテムP1_RESPONSEにページ・アイテムP1_TEXT、P1_CLOB、P2_TEXT、P2_CLOBの値が出力されます。
ページ2でも同様に、ページ・アイテムP2_RESPONSEにページ・アイテムP1_TEXT、P1_CLOB、P2_TEXT、P2_CLOBの値が出力されます。
最後にページ1から、クローンしたセッションでページ2を開きます。
ページ2が新しいタブで開きます。このとき、セッションをクローンしているため、新しいセッションIDが割り当たります。
元のセッションIDとクローン後のセッションIDの両方を検索対象として、表WWV_FLOW_DATAを検索します。
select * from wwv_flow_data where flow_instance in ([元のセッションId],[クローンしたセッションID]) and item_id > 0 order by flow_instance,item_name asc;
デバッグ・レベルを完全トレースまであげると、セッション・ステートがメモリにロードされる状況がログに出力されます。
ログには次のように出力されます。これはページ・ビューとAjaxコールバックで共通です。
ここまで、ページ・アイテムのセッション・ステートのストレージを永続にしたときの動作について説明してきました。
セッション・ステートはこのような動作をするため、以下のような状況について気を付ける必要があります。
突然、そのページは何も変更していないのに、ページの表示やAjaxコールバックが遅くなった。
以下を確認する必要があります。
- アプリケーションにページを大量に追加していませんか?
- 追加したページにセッション・ステートのストレージが永続になっているページ・アイテムが沢山ありませんか?
- セッション・ステートのストレージが永続になっている、データ型がCLOBになっているページ・アイテムはありませんか?
- 上記のCLOBのページ・アイテムに極端に大きいデータを保存していませんか?
- 上記の事象はユーザーの操作に依存します。そのユーザーが別のページで巨大なCLOBをセッション・ステートに保存していると発生します。同じページにアクセスしていて同じようなデータを操作していても、操作しているページだけに注目していると、原因が分かりません。
- セッションのクローンを多用していませんか?
- クローンするごとに表WWV_FLOW_DATAに保存されるデータは増加します。クローンを使わない場合は、ユーザーがログインした分のデータがセッション・ステートに保存されますが、セッションのクローンは注意深く実装していないと、表WWV_FLOW_DATAに保存するデータが極端に増加する可能性があります。表WWV_FLOW_DATAに保存されているセッション・ステートのデータはユーザーがログアウトしたりセッションがタイムアウトしたりした時点では削除されず、日時で実行されるメンテナンス・タスクが削除します。そのため、表WWV_FLOW_DATAに保存されているデータは、1日程度は維持されます。
- セッションをクローンするアプリケーションは、ページ数を少なくしたり、セッション・ステートのストレージに永続を設定したページ・アイテムを少なくする必要があります。
- 沢山のページを含む巨大なアプリケーションよりは、セッション共有を使って小さなアプリケーションを集めてひとつのアプリケーションに見せる方が、パフォーマンス面では有利です。
ページ作成ウィザードによる場合ではなく、ページ・アイテムを単独で作成したときのセッション・ステートのストレージのデフォルトの設定はセッションごと(永続)です。
そのため、ページ・アイテムを作成したときは、必ずセッション・ステートのストレージの設定を確認すべきです。
今回の記事は以上になります。
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完