1. はじめに
Oracle Net(SQL*Net)には、1 回の送受信でまとめるデータ量を決める SDU(Session Data Unit) という設定があります。「大量データを転送するなら SDU を大きくすると速くなる」という話は昔からよく聞きます。
一方で相手が Autonomous Database Serverless(以下 ADB-S)だと、サーバ側の sqlnet.ora は触れません。となると SDU をいじれるのはクライアント側だけです。クライアント側で SDU を大きくすれば、ADB-S からの大量フェッチは本当に速くなるのか を、手元の ADB-S(Oracle AI Database 26ai)にインターネット経由でリモート接続して測ってみました。
1.1. 今回の検証ゴール
| # | 検証項目 |
|---|---|
| 1 | クライアントで指定した SDU に対し、ADB-S が実際に使う実効 SDU はいくつか |
| 2 | SDU を変えると、大量フェッチや小さなクエリの所要時間はどう変わるか |
| 3 | SDU で変わらないなら、リモート接続で所要時間を決めるのは何か(RTT を上げた場合・JDBC の場合も確かめる) |
1.2. 結論(先出し)
- ADB-S は、クライアントが SDU をいくら大きく指定しても、実効 SDU が 32 KB で頭打ちになる。64 KB や 2 MB を指定しても 32,768 バイトで止まった。python-oracledb thin と JDBC thin の 2 ドライバで同値なので、この上限はサーバ側にある
- そもそも SDU を大きくしても、大量フェッチの所要時間はほとんど変わらない。8 KB(デフォルト)でも 64 KB でも 2 MB でも同じで、小さくしすぎた 2 KB だけが約 8% 遅かった
- フェッチ時間を決めているのは SDU ではなく 1 回のフェッチで受け取る行数(arraysize / 行プリフェッチ) だった。ここを 100 から 10000 に上げると所要時間は 18 秒から 1.3 秒に短くなった
- ネットワークの往復遅延(RTT)を 10 ms に増やしても結論は同じで、SDU では所要時間は変わらず、行プリフェッチの差だけが広がった。JDBC でも同じ傾向だった
- 実務的には、SDU はデフォルトのままにして、リモートで大量取得するなら フェッチ行数を上げる のが正解
つまり「相手が ADB-S なら、SQL*Net の SDU チューニングに手間をかける意味は薄い」という結果でした。順を追って見ていきます。
2. 検証環境
| 項目 | 内容 |
|---|---|
| 対象 DB | OCI Autonomous Database Serverless(Oracle AI Database 26ai / 23.26.2.2.0) |
| DB の規模 | ECPU 2、自動スケーリング有効、ワークロード OLTP |
| 接続 | mTLS(wallet / TCPS 1522)、サービスは low を使用 |
| クライアント | Windows 11 Pro からインターネット経由(ローカル環境) |
| ドライバ | python-oracledb 4.0.1(thin モード)、JDBC は ojdbc11(thin) |
| 計測ユーザー | ADMIN(v$mystat を読むため) |
ADB-S ではサーバ側の sqlnet.ora やリスナー設定を変更する手段が提供されていません。そのため、この検証で動かせる変数は クライアント側の SDU 指定だけ です。ここが今回の主題です。インターネット経由で RTT が数 ms 程度ある、現実的なリモート接続で測っています。
3. SDU の仕組みと検証の構成
3.1. SDU とは何か、どう指定するか
SDU は、Oracle Net がデータをネットワークに送り出す前に一時的にためるバッファのサイズです。1 回の往復でこのサイズ分までまとめて送れます。大量のデータを流すときに SDU が大きいほど、細切れの送信が減って効率が上がる、というのが一般的な説明です。
公式ドキュメントによれば、SDU は 512 バイトから 2 MB(2,097,152 バイト)まで指定でき、実際に使われる値は 接続時にクライアントとサーバでネゴシエートされ、小さい方に丸められます1。デフォルト値はクライアントが 8,192 バイト、サーバは Oracle Database 23ai 以降 64 KB(65,536 バイト)です2。
クライアント側での指定方法はいくつかあります。
-
tnsnames.oraの接続記述子に(SDU=n)を書く - python-oracledb なら接続時に
sdu=nを渡す(thin / thick 両対応)3
3.2. 検証の共通構成
再現性を保つため、接続方法とテストデータは全パターンで共通にし、SDU(と後半では arraysize)だけを切り替えます。
SDU は python-oracledb の sdu= で接続ごとに指定します。サーバより大きい値を指定してもエラーにはならず小さい方に丸められるだけなので、ADB-S 相手でも安全に試せます。
import oracledb
# SDU を接続ごとに切り替える(wallet は事前に配置)
conn = oracledb.connect(
user="ADMIN", password=PASSWORD, dsn="adbtest02_low",
config_dir=WALLET_DIR, wallet_location=WALLET_DIR,
wallet_password=WALLET_PASSWORD,
sdu=65536, # ここを 2048 / 8192 / 65536 / 2097152 と変える
)
テストデータは、圧縮で転送量が実データとかけ離れないよう、高エントロピーのランダム文字列で作ります。
-- 1 行 200 バイト × 50 万行 ≈ 128 MB
create table sdu_bench as
select rownum as id, dbms_random.string('x', 200) as payload
from dual connect by level <= 500000;
4. 手順・実行
4.1. 実効 SDU を観測する
ネゴシエート後に実際に使われる SDU は、ドライバから直接読めます。python-oracledb thin では接続オブジェクトの conn.sdu プロパティ、Oracle JDBC thin では内部接続の getNegotiatedSDU() が、その接続で使われている SDU を返します。SDU を 2 KB / 8 KB / 64 KB / 2 MB と変えて接続し、この値を読み取りました。
裏付けとして、python-oracledb の PYO_DEBUG_PACKETS(送受信パケットのダンプを出す環境変数)でも、大量フェッチ時の受信パケットの最大サイズを確認しています。こちらはネゴシエート後の SDU からパケットヘッダの数十バイトを引いた値になります。
4.2. 大量フェッチのベンチマーク
3.2 の 128 MB のテーブルを全件フェッチする時間を、SDU 4 パターンで測ります。1 回のフェッチで受け取る行数(arraysize)は 1000 に固定し、回線の状態がどれかのパターンに偏らないよう、4 パターンを 1 巡として 5 巡(インターリーブ実行)しました。結果は中央値で比べます。あわせて v$mystat から往復回数(SQL*Net roundtrips)も取得します。
比較のため、1 行だけ返す小さなクエリのレイテンシも同じ 4 パターンで測りました。
4.3. フェッチ行数(arraysize)を振る
SDU をデフォルトの 8 KB に固定し、arraysize だけを 100 / 1000 / 10000 に変えて、同じ 128 MB を全件フェッチしました。
4.4. RTT を人工的に上げる(tc netem)
ローカル環境からの接続は RTT が 4 ms 前後でした。RTT が大きい遠隔地を模すため、WSL 上の Linux から接続し、tc(netem)で送信側に 10 ms の遅延を足した状態と、素の状態を比べます。
# 送信側に 10 ms の遅延を足す(計測後に del で戻す)
sudo tc qdisc add dev eth0 root netem delay 10ms
この 2 条件で、SDU 比較(8 KB と 64 KB)とフェッチ行数比較(100 / 1000 / 10000)を測りました。
4.5. JDBC で同じことを測る
業務アプリで多い JDBC でも同じ傾向になるかを確かめます。JDBC thin ドライバで、wallet 同梱の JKS(keystore / truststore)を使って ADB-S に mTLS 接続し、SDU(8 KB と 64 KB)とフェッチサイズを、素の状態と netem +10 ms で比べました。フェッチサイズは、素朴なアプリを想定した 10、明示的に大きくした 1000 / 10000、そして 26ai の自動チューニングに任せた場合(未設定)の 4 通りです。
5. 計測結果
5.1. 実効 SDU は約 32 KB で頭打ち
指定した SDU に対する、ネゴシエート後の実効 SDU です。python-oracledb thin(conn.sdu)と Oracle JDBC thin(getNegotiatedSDU())で同じ値になりました。
| 指定した SDU | 実効 SDU(ネゴシエート後) | 受信パケットの最大サイズ |
|---|---|---|
| 2,048 | 2,048 | 1,967 バイト |
| 8,192(クライアントのデフォルト) | 8,192 | 8,111 バイト |
| 65,536 | 32,768 | 32,687 バイト |
| 2,097,152(上限 2 MB) | 32,768 | 32,687 バイト |
2 KB と 8 KB は指定値どおりです。ところが 64 KB を指定しても実効は 32,768 バイト(32 KB ちょうど)で止まり、2 MB を指定しても同じ 32,768 バイトのままでした(右端の受信パケット最大サイズが 32,687 バイトなのは、この 32,768 からヘッダ分を引いた値です)。ADB-S 側が 32 KB で頭打ちにしている と読み取れます。クライアントで 64 KB や 2 MB を指定しても、ADB-S 相手では 32 KB より大きな SDU は使われません。この頭打ちがサーバ側かドライバ側かの切り分けは 6.1 章で扱います。
5.2. 大量フェッチは SDU では変わらない
128 MB を全件フェッチした所要時間です。
| SDU 指定 | 実効 SDU | 所要時間の中央値 | デフォルト(8 KB)比 | 往復回数 |
|---|---|---|---|---|
| 2,048 | 2,048 | 2.90 秒 | 1.08 | 503 |
| 8,192(デフォルト) | 8,192 | 2.68 秒 | 1.00 | 503 |
| 65,536 | 32,768 | 2.70 秒 | 1.01 | 503 |
| 2,097,152 | 32,768 | 2.68 秒 | 1.00 | 503 |
SDU をデフォルトの 8 KB より大きくしても、所要時間はほとんど変わりません。実効が 32 KB で頭打ちの 64 KB / 2 MB が 8 KB と並ぶのは当然として、8 KB と 32 KB の間ですら差がありません。逆に 2 KB まで小さくすると約 8% 遅くなりました。
注目したいのが右端の 往復回数(SQL*Net roundtrips)が 4 パターンとも 503 で一定 なところです。この読み解きは 6.1 章で扱います。
1 行だけ返す小さなクエリでは、4 パターンとも所要時間の中央値が 3.5〜3.9 ms でほぼ同じでした。1 往復あたりのデータ量がごく小さいので、SDU の出番がありません。
計測が CPU 待ちでないことも確認しています。大量フェッチ中、クライアント(Windows)の総 CPU 使用率は平均 28.7%・最大 47%、ADB 側の CPU 使用率は平均 2.04%・最大 19.3% で、どちらも飽和していませんでした。
5.3. 所要時間を決めるのはフェッチ行数
SDU を 8 KB に固定し、arraysize だけを振った結果です。
| arraysize | 往復回数 | 所要時間の中央値 | arraysize 1000 比 |
|---|---|---|---|
| 100 | 5,002 | 18.33 秒 | 6.99 |
| 1,000 | 502 | 2.62 秒 | 1.00 |
| 10,000 | 52 | 1.33 秒 | 0.51 |
往復回数が arraysize に反比例して減り、それに合わせて所要時間も短くなります。100 と 10000 では所要時間が 18.3 秒と 1.3 秒で、約 14 倍の差が付きました。SDU をどう変えても動かなかったのと対照的です。
5.4. RTT を上げても結論は同じ
netem で 10 ms の遅延を足すと、実測 RTT は素の 3.96 ms から 14.5 ms に増えます。10 万行を全件フェッチした結果を、SDU を振る場合とフェッチ行数を振る場合に分けて示します。
まず arraysize を 1000 に固定して SDU を振ったものです。
| 条件(RTT) | SDU 8 KB | SDU 64 KB |
|---|---|---|
| 素(3.96 ms) | 0.54 秒 | 0.56 秒 |
| netem +10 ms(14.5 ms) | 1.70 秒 | 1.65 秒 |
RTT を上げても、SDU 8 KB と 64 KB の差は相変わらずありません(素で 0.54 対 0.56 秒、+10 ms で 1.70 対 1.65 秒)。
次に SDU を 8 KB に固定してフェッチ行数(arraysize)を振ったものです。
| 条件(RTT) | 行数 100 | 行数 1000 | 行数 10000 |
|---|---|---|---|
| 素(3.96 ms) | 4.07 秒 | 0.54 秒 | 0.28 秒 |
| netem +10 ms(14.5 ms) | 14.51 秒 | 1.69 秒 | 0.48 秒 |
こちらは RTT を上げると差が広がります。行数 100 と 10000 の比は、素では約 14.5 倍だったのが、+10 ms では約 30 倍(14.51 秒 対 0.48 秒)になりました。
5.5. JDBC でも同じ傾向
JDBC でも、SDU を振る場合とフェッチサイズを振る場合に分けて示します(2 万行を全件フェッチ)。
まずフェッチサイズを 1000 に固定して SDU を振ったものです。
| 条件(RTT) | SDU 8 KB | SDU 64 KB |
|---|---|---|
| 素(4.42 ms) | 0.12 秒 | 0.13 秒 |
| netem +10 ms(16.5 ms) | 0.39 秒 | 0.41 秒 |
JDBC でも SDU 8 KB と 64 KB に差はありません。
次に SDU を 8 KB に固定してフェッチサイズを振ったものです。「自動」はフェッチサイズを未設定にして 26ai の行プリフェッチ自動チューニングに任せた場合です。
| 条件(RTT) | fetch 10 | fetch 1000 | fetch 10000 | fetch 自動 |
|---|---|---|---|---|
| 素(4.42 ms) | 7.4 秒 | 0.13 秒 | 0.06 秒 | 0.78 秒 |
| netem +10 ms(16.5 ms) | 28.4 秒 | 0.39 秒 | 0.16 秒 | 2.90 秒 |
差が出るのはフェッチサイズです。素朴に 10 行ずつ取ると、RTT +10 ms では 28.4 秒(往復 2,002 回)かかり、10000 行ずつ(往復 4 回)の 0.16 秒に対して約 177 倍も遅くなりました。「自動」は 1 回あたり約 100 行に落ち着き(往復 202 回)、素朴な 10 行固定よりは 9 倍ほど速くなりました。ただし明示的に大きくした 1000 / 10000 行ほどは速くなりません。
6. 考察
6.1. なぜ SDU では速くならないのか
大量フェッチで SDU 4 パターンの往復回数が 503 で一定だったのが答えです。SDU は「1 回の往復でやり取りする中身を、何個の TCP パケットに分けるか」を決める設定で、往復の回数そのものは 1 回のフェッチで何行受け取るか(arraysize)で決まります。今回は 50 万行を arraysize 1000 で取ったので約 500 往復になり、SDU をどう変えても往復数は変わりません。リモート接続で所要時間を左右するのは往復の回数なので、SDU を上げても時間は短くならなかったわけです。5.3 章で arraysize を振ると往復回数が減り、それに比例して所要時間が短くなったのは、この裏返しです。
5.4 章で RTT を上げるとフェッチ行数の差が広がったのも同じ理屈です。RTT が大きいほど 1 往復のコストが上がるので、往復回数を減らせるフェッチ行数の効果が大きくなります。SDU は往復回数を変えないため、RTT をどれだけ上げても差は出ません。
実効 SDU が 32 KB で頭打ちになる原因は、ADB-S のサーバ側にあると切り分けられました。python-oracledb thin と Oracle JDBC thin という別実装の 2 つのドライバで、どちらも 64 KB / 2 MB を要求してネゴシエート後の SDU を読むと、揃って 32,768 バイトになります。両ドライバとも仕様上は 2 MB までの SDU に対応するので、2 つの独立した実装が同じ 32,768 バイトで止まるということは、上限を課しているのはクライアント側ではなくサーバ側(ADB-S)だと言えます。23ai 以降のサーバのデフォルト SDU は 64 KB のはずなので、ADB-S ではその接続経路(ゲートウェイ)で 32 KB に絞っているものと考えられます。
6.2. RTT が 10 ms あるアプリでの設定指針(JDBC・一般アプリ)
「往復回数がフェッチ時間を決める」という原理はドライバに依存しません。RTT が 10 ms ほどあるアプリでは、往復回数を減らす方向の設定がどれも有効です。
-
フェッチサイズ(行プリフェッチ)を大きくする:最優先。JDBC なら
Statement.setFetchSize()か接続プロパティoracle.jdbc.defaultRowPrefetch。26ai なら未設定でも自動チューニングがある程度助けるが、大量取得が分かっているなら明示的に大きくする方が速い。python-oracledb ならarraysize/prefetchrows -
文キャッシュを有効にする:同じ SQL を繰り返すなら、
oracle.jdbc.implicitStatementCacheSizeを正の値にすると parse の往復を減らせる(デフォルトは 0 で無効)4 - 接続はプールする:mTLS の接続確立には往復が要る。1 リクエストごとに接続し直すと、RTT が大きいほど接続コストが積み上がる。UCP などのコネクションプールで接続を使い回す
-
SDU はデフォルトのままでよい:上げても速くならず、下げると遅くなる。ADB-S では 32 KB より大きくは使われないので、
oracle.net.SDUを大きくしても意味がない -
書き込みはバッチにまとめる:1 行ずつの INSERT / UPDATE も往復回数がそのまま所要時間に響く。バッチ DML(
executeBatch等)で往復を減らす
26ai の JDBC thin ドライバは行プリフェッチの自動チューニングを備えており、フェッチサイズを明示しなければ、最初の数回のフェッチでの平均行サイズから 4〜250 行の範囲で自動調整します5。5.5 章の「自動」列はこの挙動で、素朴な 10 行固定よりは速くなりました。ただし大量取得が分かっているなら、明示的に大きくする方が確実です。
7. まとめ
| # | 検証項目 | 結論 |
|---|---|---|
| 1 | ADB-S が使う実効 SDU | クライアントで 64 KB / 2 MB を指定しても 32 KB(32,768 バイト)で頭打ち。python-oracledb thin と JDBC thin で同値のため、上限はサーバ側 |
| 2 | SDU を変えたときの所要時間 | 大量フェッチも小さなクエリもほとんど変わらない。2 KB に下げたときだけ約 8% 遅い |
| 3 | 所要時間を決めるもの | フェッチ行数(arraysize / 行プリフェッチ)。RTT 10 ms でも同じで、JDBC でも同傾向 |
実務的な指針としては、SDU はデフォルトのまま据え置き、リモートで大量取得するならフェッチ行数(arraysize / 行プリフェッチ)を上げる。これが ADB-S 相手では素直な最適化でした。SDU を細かく調整する手間は、少なくとも ADB-S に対しては報われにくいと言えます。
実効 SDU が 32 KB で頭打ちになる原因は、別実装の 2 ドライバ(python-oracledb thin と JDBC thin)が揃って 32,768 バイトに落ちることから、ADB-S のサーバ側にあると切り分けられました。なぜ ADB-S が 32 KB に絞っているのか(接続経路のゲートウェイの仕様なのか、意図的な設定なのか)は公式ドキュメントに記述が見当たらず、ここは推測の域を出ません。分かり次第、追記したいところです。
参考
-
Oracle Database Net Services Administrator's Guide, 14.2 "Configuring Session Data Unit"(SDU の範囲・ネゴシエーション) https://docs.oracle.com/en/database/oracle/oracle-database/26/netag/configuring-session-data-unit.html ↩
-
Oracle Database Net Services Reference, "Parameters for the sqlnet.ora File"(DEFAULT_SDU_SIZE のデフォルト値) https://docs.oracle.com/en/database/oracle/oracle-database/26/netrf/parameters-for-the-sqlnet.ora.html ↩
-
python-oracledb Documentation, "ConnectParams"(
sdu属性、実効値は小さい方にネゴシエート) https://python-oracledb.readthedocs.io/en/stable/api_manual/connect_params.html ↩ -
Oracle Database JDBC Developer's Guide, "Statement and Result Set Caching"(
implicitStatementCacheSize) https://docs.oracle.com/en/database/oracle/oracle-database/26/jjdbc/statement-and-result-set-caching.html ↩ -
Oracle Database JDBC Developer's Guide, "JDBC Coding Tips"(行プリフェッチと 23ai/26ai の自動チューニング) https://docs.oracle.com/en/database/oracle/oracle-database/26/jjdbc/JDBC-coding-tips.html ↩