この記事は、私が Claude(Claude Code)と一緒に検証した内容です。監査記録・SQL トレース・OCI の Monitoring と課金明細から読み取った実測に加えて、そこからの推定(「〜と考えられます」と書いた箇所)を含みます。公式ドキュメントに無い挙動は今後変わる可能性があります。
1. はじめに
前回の記事1では、Select AI Agent が DB の中でどう動いているかを、V$ ビューと SQL トレースで確かめました。そのときの LLM は Gemini で、Google の API キーを DB の資格証明に登録して呼んでいました。
その後、OCI の大阪リージョンをサブスクライブして、OCI Generative AI が使えるようになりました。Select AI から OCI Generative AI を呼ぶときは、API キーの代わりにリソースプリンシパル(OCI のリソース自身に IAM の権限を与える仕組み)を使えます。DB に API キーを登録しなくてよいのは利点ですが、OCI の記録上の呼び出し元は「ADB インスタンス 1つ」になります。
では、1つの ADB を複数の利用者で使っているとき、LLM を「誰が」「どれだけ」使ったかは分けられるのでしょうか。DB 側で見る手段には Select AI の会話ビューがありますが、公式ドキュメントには、会話ビュー USER_CLOUD_AI_CONVERSATION_PROMPTS について次の一文があります2。
You cannot grant access to other users.
会話ビューは本人しか参照できず、DBA に参照させることもできないということです。そこで、OCI 側と DB 側のそれぞれで、何がどこまで記録されるかを実際に呼び出しながら確かめました。
1.1. 結論(先出し)
- 課金量は、利用者ごとに分けられない。OCI 側で分かるのは「1 時間 × モデル × コンパートメント」までで、OCI Audit には推論の記録が見つからず、Monitoring と課金明細にも呼び出し元の ADB や DB ユーザーを示す項目は見つからなかった。課金されるのは、Select AI が表の定義・指示文・質問文から組み立てて LLM に送る本文(以下、送信本文)と応答で、調べた DB 側の 4か所で、送った内容のうち記録されていたのは質問文だけ。質問文は送信本文の約 0.7% にあたる
- 回数は、アプリが
DBMS_SESSION.SET_IDENTIFIERでエンドユーザー名を渡せば、統一監査と会話ビューでエンドユーザー別に数えられる。ただし DB 側の記録は、3つの場面で実際の LLM 呼び出し回数と一致しない。1つ目は翻訳の再利用で、同じ DB ユーザーが同じselect ai文をもう一度実行すると、前回の翻訳(質問文から SQL への変換)の結果が使われて LLM を呼ばない。2つ目はnarrateとexplainsqlが 1回の実行で LLM を 2回呼ぶこと、3つ目は会話タイトルの生成である - 使える量の上限は、2026 年 9 月時点で DB 側にも OCI 側にも見つからなかった。あるのは DB ユーザー単位の「使える/使えない」の切り替えと、リージョン・コンパートメント単位の毎分リクエスト数の制限だけ
- ADB のデフォルト監査は、公式ドキュメントには
UTL_HTTPの実行が対象として書かれているが、ADMIN から参照できる監査記録には、Select AI の呼び出し分もUTL_HTTPを直接呼んだ分も 1件も見つからなかった。DBA が全ユーザー分を確認するには、DBMS_CLOUD_AIの実行を監査するポリシーを自分で作る必要がある
1.2. 検証ゴール
| # | 確かめること | 確認できれば OK の条件 |
|---|---|---|
| 1 | OCI 側で、呼び出し元の ADB や DB ユーザー別に利用量を分けられるか | OCI Audit・Monitoring・課金明細(Usage API)のそれぞれで、呼び出し元を示す項目の有無が確かめられる |
| 2 | Langfuse のような外部のツールで、Select AI の LLM 呼び出しを記録できるか | LLM 呼び出しがどこから出ているかを経路として示せる |
| 3 | 誰が・どれだけ使ったかが DB 側に記録されるか | 会話ビュー・監査・トレースのそれぞれで、DB ユーザー・エンドユーザー名・質問文・課金量が取れるかどうかを確かめられる |
| 4 | LLM 呼び出し回数を正確に数えられるか | DB 側の記録と実際の呼び出し回数の差を、アクション別に説明できる |
| 5 | ユーザー別に使える量の上限をかけられるか | 探した先ごとに、上限をかける手段の有無がはっきりする |
2. 検証環境
| 項目 | 値 |
|---|---|
| データベース | Oracle Autonomous AI Database Serverless(以下 ADB。26ai、Oracle AI Database 26ai EE 23.26.3.2.0)、2 ECPU、ap-tokyo-1、インスタンス名 adbtest02
|
| LLM | OCI Generative AI、ap-osaka-1、オンデマンド、meta.llama-3.3-70b-instruct
|
| 資格証明 |
OCI$RESOURCE_PRINCIPAL(リソースプリンシパル。API キーは作らない) |
| DB ユーザー |
SAI_APP(アプリのサービスアカウント役。エンドユーザー alice・bob が 1つの DB ユーザーを共有する)、USER_A・USER_B(業務ユーザーが個別の DB ユーザーを持つ構成の役)。権限付与と監査記録の確認は ADMIN
|
| AI プロファイル | 3ユーザーそれぞれに OCI_OSAKA(対象の表は SH.CUSTOMERS と SH.COUNTRIES) |
2026 年 9 月時点で、OCI Generative AI のモデルを使えるリージョンの一覧に東京(Japan East)は無く、日本で使えるのは大阪(Japan Central)だけです3。そのため今回は、既存の東京の ADB から大阪の LLM を呼ぶ、リージョンをまたぐ構成になっています。LLM の呼び出し先はプロファイルの region で指定するので、ADB も大阪に作れば同じリージョンの中で呼び出せます。
モデルは、Select AI で OCI を選んだときのデフォルトの meta.llama-3.3-70b-instruct4にしました。理由は課金の単位です。OCI Generative AI のオンデマンド課金には、10,000 transactions(1文字 = 1 transaction)単位のモデルと、1,000,000 tokens 単位のモデルがあります5。今回のモデルは課金明細の単位が 10,000 Transactions で、文字数で課金されます(5.2 章)。文字数で課金されるモデルなら、DB に記録された文字数と課金された文字数をそのまま比べられます。
サービスアカウント役と個別ユーザー役を分けたのは、アプリがサービスアカウント 1つで DB に接続する構成を主に想定したためです。この構成では、エンドユーザーの情報はアプリだけが持っています。個別の DB ユーザーを持つ構成は、業務ユーザーが直接 SQL を書くセルフサービス型の使い方の対照として用意しました。
3. 構成
3.1. LLM 呼び出しの経路
アプリから直接 LLM を呼ぶ構成では、API キーはアプリが持ち、呼び出しはアプリのプロセスから出ます。エンドユーザーの情報もアプリが持っているので、呼び出しのたびにエンドユーザー名を付けて記録するのはアプリ側で行います。Langfuse のような、LLM の呼び出しを記録するツールも、このアプリの層に組み込みます。
Select AI では、呼び出しは DB のプロセスから出ます。前回の記事で、LLM 呼び出しは UTL_HTTP 経由で、待機イベント TCP Socket (KGAS) として現れることを確かめています1。資格証明にリソースプリンシパルを使うと、OCI の記録上の呼び出し元は ADB インスタンスになります。今回、リソースプリンシパルが使えなかった間(4.1 章の注意)に ADMIN から管理系の API(モデル一覧の取得)を呼んだところ、OCI Audit に次の値で記録されていました。
| 項目 | 値 |
|---|---|
principalId |
adbtest02 の OCID |
authType |
resource |
| user-agent | DBMS_CLOUD PLSQL Client/1.0.0 |
| 応答 | 404(ListModels failed) |
OCI Audit には、呼び出し元として ADB の OCID が記録されています。ただしこの粒度は「ADB 1つ」で、OCI 側の記録には、どの DB ユーザーが呼んだかを示す値は見つかりませんでした(5.1 章)。
3.2. 記録される可能性がある場所
呼び出しが DB の中から出るので、DB 側の 4か所と OCI 側の 3か所を調べました。
| 場所 | 何の記録か | 全ユーザー分を確認できる人 |
|---|---|---|
会話ビュー USER_CLOUD_AI_CONVERSATION_PROMPTS
|
会話ごとの質問と応答 | いない(本人の分だけ。DBA も不可)2 |
| 統一監査(自作のポリシー) |
DBMS_CLOUD_AI の実行 |
DBA(ADMIN) |
| ADB のデフォルト監査 |
UTL_HTTP の実行(定義上)6
|
DBA(ADMIN)。ただし今回は 1件も見つからなかった(5.3 章) |
| SQL トレース | セッション内の SQL・待機・バインド値 | いない(取得したセッションの中だけ) |
| OCI Audit | OCI API の呼び出し | OCI のテナンシーの管理者(DB の権限とは別) |
| OCI Monitoring | 推論の回数・トークン数 | OCI のテナンシーの管理者(DB の権限とは別) |
| 課金明細(Usage API) | 1 時間ごとの利用量 | OCI のテナンシーの管理者(DB の権限とは別) |
4. 手順
4.1. リソースプリンシパルとプロファイル
OCI 側では、動的グループ(OCI のリソースをまとめて IAM の主体にする仕組み)を作り、ADB インスタンス 1つだけを入れました。
ポリシーは、その動的グループに OCI Generative AI の利用権限を与える 1文だけです。推論(Chat)は use の権限で呼べます。
DB 側では、ADMIN でリソースプリンシパルを有効にしてから、使わせる DB ユーザーごとに利用を許可します7。
-- ADMIN で実行
exec dbms_cloud_admin.enable_resource_principal();
exec dbms_cloud_admin.enable_resource_principal(username => 'SAI_APP');
exec dbms_cloud_admin.enable_resource_principal(username => 'USER_A');
exec dbms_cloud_admin.enable_resource_principal(username => 'USER_B');
過去にリソースプリンシパルを有効にしたことがある ADB では、動的グループとポリシーを後から作っても、呼び出しが ORA-20404: Object not found - https://inference.generativeai.ap-osaka-1.oci.my$cloud_domain/20231130/actions/chat で失敗し続けました。公式ドキュメントには、動的グループとポリシーを作ってから ENABLE_RESOURCE_PRINCIPAL を呼ぶ順序で手順が書かれており7、今回の ADB はこの順序が逆でした。ポリシーの反映待ち(最大 2 時間)を過ぎても、ADB を再起動しても直りませんでした。ADMIN で DISABLE_RESOURCE_PRINCIPAL() → ENABLE_RESOURCE_PRINCIPAL() とやり直すと、直後に成功しました。やり直すと各ユーザーへの許可が消えるので、ユーザー単位の ENABLE_RESOURCE_PRINCIPAL(username => ...) も付け直す必要があります。
プロファイルは各ユーザーのセッションで作ります。プロファイルは所有者のセッションでしか使えないためです(5.5 章)。region を指定しないと、デフォルトの us-chicago-1 が使われます8。
begin
dbms_cloud_ai.create_profile(
profile_name => 'OCI_OSAKA',
attributes => '{"provider":"oci",
"credential_name":"OCI$RESOURCE_PRINCIPAL",
"region":"ap-osaka-1",
"model":"meta.llama-3.3-70b-instruct",
"object_list":[{"owner":"SH","name":"CUSTOMERS"},
{"owner":"SH","name":"COUNTRIES"}]}');
end;
/
4.2. 呼び出し元の分け方
SAI_APP では、アプリがエンドユーザー名を DB セッションに付ける場面を DBMS_SESSION.SET_IDENTIFIER で再現しました。付けた値は、監査記録と会話ビューの CLIENT_IDENTIFIER 列に入ります。
exec dbms_cloud_ai.set_profile('OCI_OSAKA');
-- エンドユーザー alice として 1 回
exec dbms_session.set_identifier('alice');
select ai showsql 顧客は何人いますか [R3p-1];
exec dbms_session.clear_identifier;
-- エンドユーザー bob として 1 回
exec dbms_session.set_identifier('bob');
select ai showsql 顧客は何人いますか [R4p-1];
exec dbms_session.clear_identifier;
この組み合わせを、識別子あり(alice・bob)となし、会話あり(DBMS_CLOUD_AI.CREATE_CONVERSATION を実行してから質問)となしで分け、USER_A・USER_B の分と合わせて 14回呼びました。
質問文の末尾に付けた [R3p-1] は、呼び出しごとに質問文を変えるための目印です。同じ DB ユーザーが同じ文を実行すると、LLM を呼ばないためです(5.4 章)。同じ質問文で 14回実行したときは、実際に LLM を呼んだのは 3回だけでした。なお、目印を角括弧なしで K5 と付けたときは、LLM がそれを WHERE 句の絞り込み条件に使った SQL を返しました。
4.3. 監査ポリシー
DBMS_CLOUD_AI は C##CLOUD$SERVICE スキーマのパッケージです。パッケージの実行は統一監査の対象にできます9。
-- ADMIN で実行
create audit policy sai_exec_pol
actions execute on c##cloud$service.dbms_cloud_ai,
execute on c##cloud$service.dbms_cloud_ai_agent;
audit policy sai_exec_pol by sai_app, user_a, user_b;
記録は UNIFIED_AUDIT_TRAIL から参照します。
select event_timestamp_utc, dbusername, client_identifier,
substr(sql_binds, 1, 200) binds_head
from unified_audit_trail
where unified_audit_policies = 'SAI_EXEC_POL'
and sql_binds like '%select ai%'
and event_timestamp_utc > sys_extract_utc(systimestamp) - interval '1' hour
order by event_timestamp_utc;
この絞り込み(sql_binds like '%select ai%')は必須です。プロファイルを SET_PROFILE で設定したセッションでは、select ai と関係の無い SQL(SQLcl が裏で実行する DBMS_OUTPUT.GET_LINE の呼び出しなど)まで translate_sql を通って 1行ずつ記録されるためです。4.2 章の 14回の呼び出しでは、select ai の行 14行に対して、それ以外の行が 24行記録されていました。
5. 結果
5.1. OCI 側で見えるもの
| 場所 | 推論の記録 | 呼び出し元を示す項目 |
|---|---|---|
| OCI Audit | 見つからなかった(失敗した DB からの 2回と、成功した CLI からの 1回を検索) | 推論の記録が見つからないので確かめられない |
Monitoring(oci_generativeai) |
回数・入出力トークン数が 1 分単位である | ディメンション 16個の中に無い(コンパートメントとモデルまで) |
| 課金明細(Usage API) | 1 時間ごとの利用量がある | 見つからなかった(resourceId はモデルの OCID) |
OCI Audit では、大阪と東京の 2 リージョン、テナンシー内の 6つのコンパートメントを、呼び出した時間帯で検索しました。対象は、リソースプリンシパルが使えなかった間に DB から呼んで 404 で失敗した 2回と、比べるために CLI(管理者ユーザー)から呼んで成功した 1回です。どちらも、どのコンパートメントにも記録されていませんでした。DB から呼んで成功した推論は Audit を直接は検索していませんが、成功した CLI の呼び出しも記録されていなかったので、同じだと考えられます。同じ時間帯の管理系の API(3.1 章のモデル一覧の取得など)は記録されていたので、見つからなかったのは推論の呼び出しだけです。公式ドキュメントには「Currently, all services support logging by Audit.」と書かれていて、CLI からの API 呼び出しも記録の対象に含まれています10。推論の呼び出しを対象外とする記述は見当たらないので、この結果は公式の記述と一致しません。
Monitoring には、オンデマンドの推論でもメトリクスが出ていました。公式のメトリクスのページには、専用 AI クラスタとエンドポイントのメトリクスとしての説明が書かれていて、オンデマンドについての記述はありません11。実際には、オンデマンドの呼び出しも TotalInvocationCount などに数えられています。ディメンション(集計の切り口)は 16個で、その一覧に呼び出し元を示すものはありません。
呼び出し回数のグラフには、DB からの呼び出しと、比べるために CLI から呼んだ 1回が同じ系列で入っています。CLI からの呼び出しとも区別がつかないので、同じコンパートメントに ADB が複数あっても区別できないと考えられます。
課金明細は、Usage API で service・skuName・resourceId・compartmentName を切り口にして 1 時間単位で取りました。Generative AI の推論の行は次の 3行です(金額は載せず、利用量だけを載せます)。
| 時間帯(日本時間) | skuName | resourceId | 単位 | 利用量 |
|---|---|---|---|---|
| 2026-09-11 13時台 | Oracle Cloud Infrastructure Generative AI - Large Meta | モデルの OCID | 10,000 Transactions | 0.0015 |
| 2026-09-11 15時台 | Oracle Cloud Infrastructure Generative AI - Large Meta | モデルの OCID | 10,000 Transactions | 0.7644 |
| 2026-09-11 16時台 | Oracle Cloud Infrastructure Generative AI - Large Meta | モデルの OCID | 10,000 Transactions | 6.0652 |
resourceId はモデルの OCID で、ADB の OCID ではありません。タグで分けられないかも確かめましたが、Usage API のタグ別の集計は service や resourceId の切り口・絞り込みと同時に指定できず(groupBy must be null when groupByTagKey isn't empty)、推論の行だけをタグ別に取り出せませんでした。推論の行の対象はモデルなので、ADB に付けたタグが載ることは無いと考えられます。
5.2. 課金量の照合
課金明細の利用量は、単位が 10,000 Transactions(1文字 = 1 transaction5)なので、10,000 を掛けると文字数になります。
13時台の 0.0015 は 15文字です。この時間帯に成功した推論は、CLI から呼んだ 1回だけでした。
| 項目 | 文字数 |
|---|---|
送信した本文 reply with OK
|
13 |
応答 OK
|
2 |
| 合計 | 15 |
| 課金明細 | 15(0.0015 × 10,000) |
15文字で一致します。同じ時間帯に 404 で失敗した呼び出しが 7回ありましたが、課金されていません。同じ呼び出しの応答には usage としてトークン数(入力 38、出力 2)が返っていましたが、課金に使われたのはトークン数ではなく文字数でした。
15時台は、DB から次の 6回を呼んでいます。
| 呼び出し | 回数 | 1回あたりの文字数 | 小計 |
|---|---|---|---|
chat(reply with OK → OK) |
3 | 15 | 45 |
showsql(顧客は何人いますか) |
3 | 送信本文 2,450 + 生成された SQL 73 | 7,569 |
| 見込みの合計 | 7,614 | ||
| 課金明細 | 7,644(0.7644 × 10,000) |
差は 30文字で、showsql 1回あたり 10文字です。SQLcl の出力や会話ビューに記録された SQL(73文字)は 1行でしたが、LLM が返した実際の SQL には改行やインデントが含まれていたと考えられます。
showsql の送信本文の 2,450文字は、select ai showprompt で取り出したものです。showprompt は、LLM を呼ばずに送信予定の本文を返すキーワードです12。本文は次の 2つの部分からなり、質問文はユーザー側の末尾にある 9文字だけです。
[SYSTEM] ### Oracle SQL tables with their properties:
# CREATE TABLE "SH"."CUSTOMERS" ("CUST_FIRST_NAME" VARCHAR2(20), "CUST_ID" NUMBER, ...(中略)
# CREATE TABLE "SH"."COUNTRIES" ("COUNTRY_ID" NUMBER, ...(中略)
[USER] Given an input Question, create a syntactically correct Oracle SQL query to run. ...(中略)
Question: 顧客は何人いますか
システム側の部分が 1,057文字、ユーザー側の部分が 1,393文字で、ほとんどがプロファイルに指定した表の定義と、Select AI が付ける指示文です。課金は、この送信本文全体と応答にかかっています。
5.3. DB 側に記録されるもの
| 場所 | DB ユーザー | エンドユーザー名 | 質問文 | 送信本文・トークン数 | DBA が全ユーザー分を確認できるか |
|---|---|---|---|---|---|
| 会話ビュー | 本人の行のみ |
CLIENT_IDENTIFIER 列にある |
ある(会話を設定したときだけ) | 無い(列定義にも無い) | 確認できない |
| 統一監査(自作) | ある |
CLIENT_IDENTIFIER 列にある |
ある(SQL_BINDS 列) |
無い(SQL_BINDS は質問文だけ) |
確認できる |
| ADB のデフォルト監査 | 0件 | 0件 | 0件 | 0件 | 確認できる場所に 1件も見つからなかった |
| SQL トレース | ある | ある(*** CLIENT ID:(...) の行とバインド値) |
見つからなかった(narrate の 2回目を除く) |
見つからなかった(全行を検索。見つかったのは接続先の URL だけ) | 取得したセッションの中だけ |
統一監査の記録は次のとおりです(列を一部省略。時刻の列は UTC で、日本時間では 16時01分〜16時02分)。
EVENT_TIMESTAMP_UTC DBUSERNAME CLIENT_IDENTIFIER BINDS_HEAD
26-09-11 07:01:37.154181000 USER_A #1(35):select ai showsql 顧客は何人いますか [R1p-1]
26-09-11 07:02:41.836375000 SAI_APP alice #1(35):select ai showsql 顧客は何人いますか [R3p-1]
26-09-11 07:02:43.377168000 SAI_APP bob #1(35):select ai showsql 顧客は何人いますか [R4p-1]
サービスアカウント 1つを共有していても、CLIENT_IDENTIFIER でエンドユーザーを区別できます。質問文は SQL_TEXT 列ではなく、translate_sql に渡されたバインド値として SQL_BINDS 列に入ります。注意点として、LLM の呼び出しが 404 で失敗したときも、監査記録の RETURN_CODE は 0 でした。パッケージの実行としては成功扱いなので、監査記録からは LLM 呼び出しが成功したかどうかは分かりません。
会話ビューにも CLIENT_IDENTIFIER があります。
CONVERSATION_ID PROFILE_NAME PROMPT_ACTION CLIENT_IDENTIFIER PROMPT_LEN RESP_LEN
5B30ABA9-B6DD-2BD4-E063-F615000A936B OCI_OSAKA SHOWSQL alice 17 73
5B30ABA9-B6E3-2BD4-E063-F615000A936B OCI_OSAKA SHOWSQL bob 17 73
PROMPT 列は質問文(目印込みで 17文字)、PROMPT_RESPONSE 列は生成された SQL(73文字)で、5.2 章の送信本文は入っていません。17文字は、同じ質問文での送信本文(約 2,458文字)の約 0.7% です。トークン数の列もありません2。OCI の応答にはトークン数が入っていますが、今回調べた会話ビュー・監査・トレースには、トークン数の記録は見つかりませんでした。トレースには会話ビューの元になる内部の表 dbms_cloud_ai_conversation_prompt$ への INSERT 文も出ていましたが、その 13列(prompt・rsp・client_identifier など)にトークン数の列はありません。トレース全体でも、トークン数を書き込む INSERT 文や UPDATE 文は見つかりませんでした。ただし、Select AI の内部の表(C##CLOUD$SERVICE スキーマ)そのものは ADMIN からも参照できないため、表の中身までは確かめられていません。
DBA が会話ビューを参照する手段も試しました。DBA_CLOUD_AI_CONVERSATION_PROMPTS というビュー自体は存在しますが、ADMIN で参照すると ORA-41900 になり、ADMIN から READ 権限を付与しようとすると ORA-01031 になります。1 章で引用した公式ドキュメントの記述と一致します。
公式ドキュメントには、ADB のデフォルト監査の対象として「UTL_HTTP or UTL_SMTP that connect to the network」の実行が書かれています6。Select AI の LLM 呼び出しは UTL_HTTP 経由なので(3.1 章)、ここに記録される見込みでしたが、今回の環境では見つかりませんでした。公式の記述と一致しないので、確かめた内容をすべて載せます。
| 確かめたこと | 結果 |
|---|---|
デフォルト監査のポリシー ADB_MANDATORY_AUDIT の定義(AUDIT_UNIFIED_POLICIES) |
UTL_HTTP・UTL_SMTP・UTL_TCP の EXECUTE を含む |
有効なポリシーの一覧(AUDIT_UNIFIED_ENABLED_POLICIES、16行) |
ADB_MANDATORY_AUDIT は含まれていない |
ADMIN から参照できる UNIFIED_AUDIT_TRAIL の 14 日分(Select AI を呼んだ期間を含む) |
UTL_HTTP の行は 0件 |
対照として、SAI_APP から UTL_HTTP.REQUEST で OCI の Object Storage のエンドポイントを直接 1回呼ぶ |
30 秒後と再検索の 2回とも 0件(OBJECT_NAME・SQL_TEXT・DB ユーザー名の 3通りで検索) |
監査記録の書き出しを促す DBMS_AUDIT_MGMT.FLUSH_UNIFIED_AUDIT_TRAIL
|
ADMIN では実行できない(PLS-00302) |
Select AI に限らず、UTL_HTTP の実行そのものが、ADMIN から参照できる監査記録に 1件も記録されていませんでした。ポリシーが有効化されていないのか、ADMIN から参照できない場所(Oracle が管理する側)に記録されているのかは、利用者側から切り分ける手段が見つかりませんでした。今回の結果として言えるのは、DBA が確認できる記録としては使えなかった、というところまでです。
SQL トレースは、5.4 章の 5つのアクションを実行している間をバインド値つきで取りました(34,486行)。推論のエンドポイントの URL はバインド値として記録されていましたが、全行を検索しても、送信本文の指示文や表の定義、応答、トークン数は見つかりませんでした。translate_sql のバインド値は CLOB 型で、トレースには値が空のまま記録されます。そのため質問文も、narrate の 2回目の呼び出し(5.4 章)でリテラルの SQL に埋め込まれた 1行を除いて、トレースには記録されません。
5.4. 回数の突き合わせ
同じ SAI_APP のセッションで、会話を設定してから、新しい質問文で 5つのアクションを 1回ずつ実行し、4つの方法で数えました。「実際の呼び出し」は、SQL トレースに記録された HTTP の待機区間(連続する TCP Socket (KGAS) 待機行のまとまり)の数です。
| アクション | 実際の呼び出し(トレース) | 会話ビュー | 統一監査の select ai 行 |
統一監査の GENERATE 行 |
|---|---|---|---|---|
showsql |
2(うち 1回は会話タイトルの生成) | 1 | 1 | 0 |
runsql |
1 | 1 | 1 | 0 |
narrate |
2 | 1 | 1 | 1 |
explainsql |
2 | 1 | 1 | 0 |
chat |
1 | 1 | 1 | 0 |
| 合計 | 8 | 5 | 5 | 1 |
同じ時間帯の OCI Monitoring の TotalInvocationCount も 8回で、トレースと一致しました。DB 側の記録と実際の呼び出し回数が一致しないのは、次の 3つの場面です。
1つ目は、翻訳の再利用です。同じ DB ユーザーが同じ select ai 文を実行すると、2回目以降は前の翻訳結果が使われて LLM を呼びません。セッションをまたいでも、SET_PROFILE をやり直しても、CLIENT_IDENTIFIER を変えても再利用されました。別の DB ユーザーが同じ文を実行したときは、改めて LLM を呼んでいます。同じ質問文で 14回実行した 1 分間(4.2 章)は、OCI Monitoring の TotalInvocationCount も 3回で、再利用された 11回は OCI にも届いていませんでした。再利用されたときは統一監査にも行が記録されないので、監査の select ai 行は「アプリが実行した回数」ではなく「翻訳が起きた回数」です。質問文を毎回変えた 14回では、監査の select ai 行が 14行で、LLM 呼び出し 14回と一致しました。
2つ目は、1回の実行で LLM を 2回呼ぶアクションです。narrate は SQL の生成と結果の要約、explainsql は SQL の生成と説明文の生成で、それぞれ 2回呼びます。会話ビューと監査の select ai 行は、どちらも 1回の実行につき 1行です。narrate の 2回目だけは、1回目で生成した SQL を埋め込んだ DBMS_CLOUD_AI.GENERATE の呼び出しとして監査に別の行で記録されますが、explainsql の 2回目は会話ビューにも監査にも見つかりませんでした(トレースと Monitoring では数えられます)。
3つ目は、会話タイトルの生成です。会話を設定して最初に質問すると、会話に英語のタイトル(今回は Number of Countries など)を付けるための呼び出しが 1回増えます。この呼び出しは会話ビューにも監査にも見つからず、数えられたのはトレースと OCI Monitoring だけでした。4.2 章で質問文を変えて 10回呼んだ 1 分間(会話 2つを含む)は、Monitoring の TotalInvocationCount が 12回でした。差の 2回は、2つの会話のタイトル生成と考えられます。
LLM を呼んだか、翻訳が再利用されたかは、経過時間では見分けられません。chat の応答が「こんにちは。」の 6文字だったときは 0.244 秒で返りましたが、トレースには HTTP の待機区間が記録されており、LLM を呼んでいました。翻訳が再利用されたときの 0.02 秒前後と同じく、1 秒未満です。
5.5. 利用量を制限する手段
使える量に上限をかける手段を、次の場所で探しました。
| 探した先 | 結果 |
|---|---|
| プロファイルの属性8 | 公式の属性一覧に、呼び出し回数や量の上限にあたる属性は無い。max_tokens(デフォルト 1024)はあるが、呼び出し側が GENERATE の attributes で上書きできた(プロファイルの max_tokens を 16 にして 25文字で打ち切られた応答が、{"max_tokens":2000} を付けると 495文字になった) |
| 他のユーザーのプロファイルを使わせる |
ORA-20000: Profile "ADMIN"."OCI_OSAKA_ADMIN" does not exist.(名前の解決自体ができない) |
| DBA が他のユーザーのプロファイルを変える | ORA-20046: Profile OCI_OSAKA is not owned by current user "ADMIN" |
DB リソースマネージャ(CS_RESOURCE_MANAGER) |
引数の一覧(71項目)は CPU・I/O・並列度・セッション数の制御だけで、LLM の呼び出しにあたる項目は無い |
DISABLE_RESOURCE_PRINCIPAL(username => ...) |
そのユーザーは ORA-20004: Credential "USER_B"."OCI$RESOURCE_PRINCIPAL" does not exist で、LLM を呼ぶ前に失敗する(使える/使えないの切り替え) |
OCI のサービス制限(ai-generative) |
オンデマンドの Chat は毎分 500リクエスト(リージョン単位)。oci limits の表示ではクォータの対象(are-quotas-supported = true)だが、クォータの単位はコンパートメントで、同じ ADB の呼び出しはすべて同じコンパートメントに入る |
| OCI の IAM 条件13 |
target.model.id で呼べるモデルを絞れる。量を絞る条件は見つからなかった |
| OCI Budgets14 | 予算額に対してアラートを出す仕組みで、公式の説明は soft limits(評価は 24 時間ごと) |
公式ドキュメントにも、2026 年 9 月時点でオンデマンド推論の量に上限を設定する項目は見当たりません15。
6. 考察
6.1. 費用の配分は回数までで、課金量はエンドユーザー別に出せない
回数は、アプリが CLIENT_IDENTIFIER を渡していれば、統一監査の select ai 行をエンドユーザー別に数えられます(5.3 章)。ただし、5.4 章の 3つの場面の呼び出しは、この回数に含まれません。
課金量は、DB 側からは出せません。課金されるのは送信本文と応答で、調べた範囲で DB に記録されていたのは質問文だけです(5.2・5.3 章。narrate の 2回目では、1回目に生成した SQL も記録されます)。OCI 側の記録には送信本文の文字数が含まれますが、5.1 章のとおり呼び出し元の項目がありません。
概算で配分するなら、回数に「プロファイルとアクションごとの 1回あたりの文字数」を掛ける方法が考えられます。質問文は送信本文 2,450文字のうち 9文字だけで、ほとんどはプロファイルの表の定義と指示文だからです。15時台の照合(5.2 章)はこの考え方で見込みを出したもので、見込み 7,614文字に対して課金明細は 7,644文字、差は 0.4% でした。ただし確かめたのは showsql と chat だけです。応答が長い narrate や、表の数が多いプロファイルでは、1回あたりの文字数の幅が広がると考えられます。
6.2. 使いすぎの検知と、LLM に送った内容の記録
回数がエンドユーザー別に数えられるので、特定のエンドユーザーが短時間に大量に呼んでいることは統一監査から検知できます。一方、利用を停止する手段は DB ユーザー単位です(5.5 章)。サービスアカウント 1つを共有する構成で停止すると、そのアプリの利用者全員が使えなくなります。エンドユーザー単位の制限は、アプリ側で行う必要があります。
LLM に送った内容のうち、統一監査に記録されるのは質問文とエンドユーザー名です。送信本文に含まれる表の定義や指示文は、調べた 4か所のどこにも記録されていませんでした。表の定義は object_list から、指示文は showprompt で再現できますが、呼び出しの時点で何が送られたかの記録ではありません。ADB の監査記録は、14 日より古いものが毎日自動で削除されます。公式ドキュメントにはこの期間を延ばす設定は書かれておらず、14 日より長く保存するなら Oracle Data Safe を使うよう案内されています6。
6.3. 呼び出しを記録するツールを間に置くと、API キーが必要になる
Langfuse のようなツールで Select AI の LLM 呼び出しを記録するには、DB と OCI Generative AI の間にゲートウェイを置く必要があります。呼び出しは DB のプロセスから出ていて、アプリの層を通らないためです(3.1 章)。
プロファイルの provider_endpoint 属性は、provider が oci のときは OCI Generative AI のエンドポイントを指定するもので、provider を指定しないと OpenAI 互換の API として扱われます8。OpenAI 互換のゲートウェイを作るなら、DB には資格証明として API キーを登録することになり、リソースプリンシパルは使えなくなると考えられます。つまり、API キーを DB に置かない構成と、呼び出しの中身を DB の外のツールで記録する構成は、両立しないと考えられます。ゲートウェイの実装は本記事の範囲外で、試していません。
6.4. コネクションプールでの識別子
今回の検証は、1つのセッションで SET_IDENTIFIER と CLEAR_IDENTIFIER を順に実行する単純な使い方でした。実際のアプリはコネクションプールでセッションを使い回すので、リクエストのたびに識別子を付け、プールに返すときに消す必要があります。消し忘れると、前のリクエストのエンドユーザー名のまま、次のリクエストの呼び出しが記録されます。また、識別子はアプリが自由に設定できる値なので、監査記録の CLIENT_IDENTIFIER が正しいかどうかはアプリの実装次第です。どちらもプール環境では確かめていません。
7. まとめ
リソースプリンシパルで Select AI から OCI Generative AI を呼ぶと、OCI の記録上の呼び出し元は ADB 1つになり、課金明細も Monitoring もモデルとコンパートメントの単位までしか分かりません。DB 側では、アプリがエンドユーザー名を SET_IDENTIFIER で渡し、DBMS_CLOUD_AI の実行を監査するポリシーを自分で作れば、エンドユーザー別の回数と質問文が記録されます。ただし課金されるのは送信本文全体で、DB に記録されるのは質問文だけでした。
使いすぎを止める手段は DB ユーザー単位の使える/使えないだけで、量で止める設定は 2026 年 9 月時点で見つけられませんでした。回数を数えるときは、監査の行数と実際の LLM 呼び出し回数が一致しない場面がある点に注意が必要です(詳しくは 5.4 章)。
参考
-
Select AI Agent が DB の中でどう動いているか V$ ビューと SQL トレースで確かめてみた(前回の記事) ↩ ↩2
-
DBMS_CLOUD_AIビュー(
USER_CLOUD_AI_CONVERSATION_PROMPTSの列定義。他のユーザーには参照権限を付与できないとの記載) ↩ ↩2 ↩3 -
地域別の生成AIモデル(モデルを使えるリージョンの一覧。アジア太平洋は Hyderabad と大阪だけで、東京は載っていない) ↩
-
Select AIについて(AI プロバイダと LLM の表。OCI のデフォルトのモデル) ↩
-
オンデマンド推論に対する支払い(Chat の課金は送信と応答の文字数。1文字 = 1 transaction) ↩ ↩2
-
Autonomous AI Databaseの監査(デフォルトの監査ポリシーと 14 日の保持期間。より長く保存するなら Oracle Data Safe を使うとの案内) ↩ ↩2 ↩3
-
リソース・プリンシパルを使用したOracle Cloud Infrastructureリソースへのアクセス(前提条件としての動的グループとポリシー、ユーザー単位の有効化・無効化、トークンの 2 時間キャッシュ) ↩ ↩2
-
DBMS_CLOUD_AIパッケージ(プロファイルの属性。
regionのデフォルト、max_tokens、provider_endpoint) ↩ ↩2 ↩3 -
Creating Custom Unified Audit Policies(Oracle AI Database 26ai Security Guide。パッケージを監査すると、パッケージ内のすべての関数とプロシージャが対象になる) ↩
-
Overview of Audit(OCI Audit の概要。すべてのサービスが Audit のログ記録に対応し、CLI からの API 呼び出しも記録されるとの記載) ↩
-
生成AIにおけるメトリックの詳細(専用 AI クラスタとエンドポイントのメトリクス一覧) ↩
-
Use AI Keyword to Enter Prompts(
select aiのキーワード一覧。showpromptは送信予定の本文を表示する) ↩ -
IAMポリシーによるモデル推論アクセスの制限(IAM 条件
target.model.idで呼べるモデルを絞る方法) ↩ -
予算(Budgets)(予算額に対するアラートの仕組み。公式の説明は soft limits、評価は 24 時間ごと) ↩
-
生成AIのサービス制限(サービス制限の一覧) ↩




