1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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 に絞っているのか(接続経路のゲートウェイの仕様なのか、意図的な設定なのか)は公式ドキュメントに記述が見当たらず、ここは推測の域を出ません。分かり次第、追記したいところです。

参考

  1. 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

  2. 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

  3. python-oracledb Documentation, "ConnectParams"(sdu 属性、実効値は小さい方にネゴシエート) https://python-oracledb.readthedocs.io/en/stable/api_manual/connect_params.html

  4. Oracle Database JDBC Developer's Guide, "Statement and Result Set Caching"(implicitStatementCacheSizehttps://docs.oracle.com/en/database/oracle/oracle-database/26/jjdbc/statement-and-result-set-caching.html

  5. 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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?