1. 概要
TIBCO Data Virtualization (TDV) を活用して仮想ビューを構築する際、最大のボトルネックとなるのがデータソース間を跨ぐ結合(フェデレーテッド・ジョイン)です。
例えば、100万件の「ordersテーブル(PostgreSQL)」と、30件の「customersテーブル(SQLServer)」を結合するビューを考えます。
SELECT
orders.orderid,
orders.customerid,
customers.companyname,
...
FROM
orders INNER JOIN customers
ON orders.customerid = customers.customerid最適化が行われない場合、TDVはPostgreSQLから100万件、SQLServerから30件のデータをそれぞれTDVサーバーへ転送し、TDVのメモリ上で結合処理を行います。
このようなフェデレーテッド・ジョインが高頻度で実行されると、ネットワーク転送量やTDVサーバーのメモリ負荷の増大によって、システム全体のパフォーマンスが低下することがあります。
本記事では、この課題を劇的に改善する「セミジョイン最適化」の仕組みと、TDVにおける具体的な設定方法について解説します。
使用環境
| 製品 | バージョン | 実行環境 |
|---|---|---|
| TDV | 8.8.1 | Windows 11 |
2. セミジョイン最適化とは
TDVにおけるセミジョイン最適化とは、異なるデータソース間を跨ぐ結合(フェデレーテッド・ジョイン)において、一方のテーブルから抽出した結合キーをフィルタ条件として、他方のデータソース側へプッシュダウンする手法です。
これにより、TDVサーバーへ転送されるデータ量を最小限に抑え、クエリ全体のパフォーマンスを劇的に向上させる最適化機能です。
3. TDVにおけるセミジョイン最適化の仕組み
3.1 最適化されないケース
通常、異なるデータソース上のテーブル同士を結合する場合、TDVは両方のソースからデータを全件取得し、自らのメモリ上で結合処理を行います。
3.2 最適化されるケース
一方、セミジョイン最適化が適用されると、TDVは一方のテーブル(駆動表)からユニークな結合キーを抽出し、それを IN 句や一時テーブルの形式で別のデータソース(被駆動表)にプッシュダウンします。これにより、処理の大部分をソースデータベース側で実行させることが可能になります。
具体的には、以下のステップで処理が行われます。
駆動表の処理
まず、データ量の少ない方のテーブル(駆動表)から、結合キーとなる値のリストを抽出します。
(本記事の例ではcustomersテーブル)フィルタのプッシュダウン
そのリストをIN句などの形式で、データ量の多いもう一方のデータソース(被駆動表)に「絞り込み条件」として送信します。データソース側でのフィルタリング
被駆動表を持つデータベースは、送られてきた条件に合致するデータだけをTDVに返します。最終結合
TDVは最小限に絞り込まれたデータ同士を結合します。
4. セミジョイン最適化の具体例
例示したビューを使用し、セミジョイン最適化が適用されないケースと、適用されるケースで、実行計画がどのように変化するか具体例を示します。
4.1 最適化されないケース
以下は、実行計画の全体構造が示されたものです。
最適化されないケースでは、ノード4でPostgreSQLのデータを全件フェッチし、ノード5で SQLServerのデータを全件フェッチし、ノード3でTDVのメモリ上でJOINする計画を表しています。
[ 1] v_sample [ 2] + SELECT [ 3] + JOIN (→ ノード4,5のフェッチ結果をTDVのメモリ上でJOIN) [ 4] + FETCH (→ PostgreSQLのordersをフェッチ) [ 5] + FETCH (→ SQLServerのcustomersをフェッチ)
ノード4: PostgreSQL ordersのフェッチ
ノード4の詳細を見ると、PostgreSQL上のordersテーブルから全件SELECTしています。
[4]
Name: FETCH
Data source type: PostgreSQL
SQL: SELECT
"orders"."orderid",
"orders"."customerid",
......
FROM "public"."orders" "orders"
ORDER BY "orders"."customerid"ノード5: SQLServer customersのフェッチ
ノード5でも、SQLServer上のcustomersテーブルから全件SELECTしています。
[5]
Name: FETCH
Data source type: SqlServer
SQL: SELECT
[customers].[companyname],
[customers].[customerid]
FROM [tdv].[dbo].[customers] [customers]
ORDER BY [customers].[customerid]
4.2 最適化されるケース
最適化されるケースでは、まず先に、ノード4でSQLServerからcustomersをフェッチし、そのIDリストをノード5の「絞り込み条件」にしてPostgreSQL,のordersをフェッチする流れとなります。
PostgreSQLからはIDリストに合致するデータしか転送されないため、ネットワーク負荷が激減します。
[ 1] v_sample [ 2] + SELECT [ 3] + JOIN [ 4] + FETCH (→ SQLServerのcustomersをフェッチ) [ 5] + FETCH (→ ノード4のフェッチ結果を絞り込み条件に、PostgreSQLのordersをフェッチ)
ノード4: SQLServer customersのフェッチ
まず先に、データ量の少ない、SQLServer上のcustomersテーブルを全件SELECTします。
[4]
Name: FETCH
Data source type: SqlServer
SQL: SELECT
[customers].[companyname],
[customers].[customerid]
FROM [tdv].[dbo].[customers] [customers]ノード5: PostgreSQL ordersのフェッチ
次に、PostgreSQL上のordersテーブルに対して、customerテーブルの結果を「絞り込み条件」として、WHERE IN句を追加してSELECTします。
これがセミジョイン最適化の効果です。
[5]
Name: FETCH
Data source type: PostgreSQL
Semi-Join Criteria: (orders.customerid = SemiJoinValueFrom[1](customers.customerid))
SQL: SELECT
"orders"."orderid",
"orders"."customerid",
......
FROM "public"."orders" "orders"
WHERE "orders"."customerid" IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 1)
5. TDVでセミジョイン最適化機能を使う方法
セミジョイン最適化を適用するかどうかは、TDVのクエリオプティマイザが実行計画を立てる際に判断します。
この判断には、オプティマイザが自動的に行う方法(暗黙的:Implicitly)と、開発者からの指示に基づく方法(明示的:Explicitly)の2通りがあります。
5.1 自動的(暗黙的)に行う方法
TDVのオプティマイザは、統計情報に基づいて「セミジョインを使用した方が速い」と判断した場合に自動で適用します。
つまり、デフォルトでセミジョイン最適化は有効となっているため、両方のデータソースの統計情報が取得されていれば、自動的に判断し適用されます。
5.1.1 統計情報の取得方法
データソースの統計情報は、「基数統計」タブにて行います。
① 「統計の作成」ボタンをクリックします。
② 「有効」をチェックし、「モード」を選び、一旦 保存 します。
※ モードは以下のいずれかを選択します
- テーブルの境界統計の収集
- 列の境界統計の収集
- すべての統計の収集
③ 保存後、「今すぐ収集」ボタンが有効になりますので、クリックします。
④ ステータスが「UP」になれば、統計情報の収集が完了です。
両方のデータソースで統計情報を取得したら、セミジョイン最適化が適用されるか確認します。
【参考情報】
ユーザー ガイド > パフォーマンス チューニング > コストベースの最適化のためのカーディナリティ統計の作成 > データ ソースでのカーディナリティ統計の作成
5.1.2 セミジョイン構成パラメータの調整
データソースに過度の負荷をかけないように、セミジョイン最適化の適用を調整するパラメータがあります。
データソースの統計情報を取得しても、セミジョインが適用されないケースや、チューニングを行いたい場合に調整してください。
【TDVサーバー 構成パラメータ】サーバー > SQLエンジン > 最適化 > 半結合
| パラメータ名 | デフォルト値 | 説明 |
|---|---|---|
| 最大ソースサイド基数推定値 | 100 |
ソース側で生成される述語(フィルタ条件)のサイズに上限を設定します。 ソース側基数(行数)の推定値が、この設定よりも大きい場合、TDVクエリーエンジンはセミジョインを自動的に選択しません。 |
| ターゲット基数対ソース基数の最小比率 | 10 |
この設定は、自動セミジョインを選択するための、JOINのターゲット側基数の、ソース側基数に対する推定最小比を制御します。 たとえば、このプロパティが10に設定され、ソース側基数の推定が75の場合、ターゲットの基数が750以上の場合はセミジョインが選択されます。 |
■ 例示のビューにおける判定基準の適用例:
最大ソースサイド基数推定値: データ量の少ない
customersテーブルがソース側(駆動表)となり、その行数は「30件」と判断されます。これは設定された上限値 100以下 をクリアしているため、条件を満たします。ターゲット基数対ソース基数の最小比率: ソース側(
customers)が30件の場合、ターゲット側(orders)には 10倍の 300件以上のデータ量が求められます。実際には100万件存在するため、この条件も十分に満たします。
共に条件を満たすため、セミジョイン最適化が自動的に適用されます。
【参考情報】
ユーザー ガイド > パフォーマンス チューニング > セミジョイン最適化オプション > セミジョイン構成パラメーターの設定
5.2 指示に基づいて(明示的)行う方法
統計情報が取得されていない場合や不十分な場合、セミジョイン最適化は自動的には適用されません。
また、特定のクエリで確実にパフォーマンスを向上させたいこともあります。
そのような場合は、ヒント句(JOIN句のオプション)を使用して適用を促すことができます。
主に、以下3つのオプションを使用します。
ビューの「SQL」タブを使い、SQL文のJOIN句に直接記述することで指示します。
5.2.1 SEMIJOIN (JOIN オプション)
SEMIJOINを使用すると、オプティマイザにセミジョイン最適化の適用を促します。
内部では、まず、左側のクエリ(LHS: Left hand side)を評価して結果をメモリにロードし、結合キーを選択基準(フィルタ条件)として使用するため一意に抽出します。そのデータ量が十分に小さければ、LHSから抽出した選択基準のすべての値を含む IN 句や OR 式を作成します。
このフィルタ条件を右側のクエリ(RHS: Right hand side)の WHERE 句に追加してリモートデータベースにプッシュダウンすることで、RHSからは一致する必要な行だけを効率的に取得します。
■ 構文
{OPTION SEMIJOIN} JOIN
■ 記述例
SELECT
...
FROM
orders INNER {OPTION SEMIJOIN} JOIN customers
ON orders.customerid = customers.customerid■ 例示のビューにおける動作
例示のビューを元にすると、LHSであるordersテーブルをまずメモリにロードし、一意な結合キー(customerid)を抽出した結果、そのデータ量が十分に小さい場合、RHSであるcustomersテーブル側へWHERE IN句としてプッシュダウンします。
ただ、ここで注意したいのは、このオプションが、常にLHSを先に評価することです。
LHSのordersが100万件で、RHSのcustomerが30件の場合でも、件数にかかわらずLHSを先に評価します。
そのため、例示のビューではordersの100万件が常にロードされるため、セミジョイン最適化の恩恵を受けることができません。
このような場合は、LHSのデータ量が少なくなるように、開発者が意識して、customersとordersを置き換える必要があります。
customers INNER {OPTION SEMIJOIN} JOIN orders
5.2.2 RIGHT_CARDINALITY (JOIN オプション)
SEMIJOINヒントで最適化の実行を直接促すのではなく、結合の右側(RHS)のカーディナリティ(基数)を明示的に伝えることで、オプティマイザに適切なクエリ実行計画を選択させる方法もあります。
RIGHT_CARDINALITY は、右側のクエリ(RHS)の推測行数をヒント句としてオプティマイザに与えるオプションです。
■ 構文
{OPTION RIGHT_CARDINALITY=<int>} JOIN
■ 記述例
SELECT
...
FROM
orders INNER {OPTION RIGHT_CARDINALITY=30} JOIN customers
ON orders.customerid = customers.customerid■ 例示のビューにおける動作
上記の例では、本来ならTDVがデータ量を把握できない状態であっても、RHSである customers テーブルが「30件」であることを明示的にオプティマイザへ伝えています。
これにより、オプティマイザは「customers のデータ量が十分に小さい」と判断できるようになります。結果として、テーブルの記述順(左右の位置関係)をSQL上で入れ替えなくても、自動的に customers を駆動表(ソース側)として選択し、orders テーブルに対してセミジョイン最適化を実行するプランを組み立てるようになります。
5.2.3 LEFT_CARDINALITY (JOIN オプション)
LEFT_CARDINALITY は、結合の左側(LHS)のカーディナリティ(基数)を明示的に伝えるオプションです。
右側の指定(RIGHT_CARDINALITY)と同様に、統計情報がない環境などでオプティマイザの実行計画を最適化するために使用します。
■ 構文
{OPTION LEFT_CARDINALITY=<int>} JOIN
■ 記述例
SELECT
...
FROM
orders INNER {OPTION LEFT_CARDINALITY=1000000} JOIN customers
ON orders.customerid = customers.customerid■ 例示のビューにおける動作
上記の例では、LHSである orders テーブルのデータ量が「100万件」という巨大なサイズであることをオプティマイザに伝えています。この情報に基づき、オプティマイザは「LHS側を駆動表(ソース側)としたセミジョイン最適化は、データ量が多すぎるため不適切である(自動適用の基準を満たさない)」と正しく判断し、無駄な最適化処理を回避します。
■ 応用:両方のカーディナリティを指定する
例示のビューのようなケースでは、LEFT_CARDINALITY と RIGHT_CARDINALITY を両方同時に指定することで、オプティマイザはより的確な判断を下せるようになります。
SELECT
...
FROM
orders INNER {OPTION LEFT_CARDINALITY=1000000, RIGHT_CARDINALITY=30} JOIN customers
ON orders.customerid = customers.customerid左右両方の正確なデータ量(LHS:100万件、RHS:30件)を同時に与えることで、オプティマイザは「customers 側を駆動表にすれば、データ量の格差(比率)も十分に大きく、セミジョイン最適化の恩恵が最大化される」ということをインプットできます。結果として、最も効率の良い実行計画が確実に選択されます。
6. まとめ
セミジョイン最適化は、TDVを用いた仮想ビューの実装において、ネットワークトラフィックを削減し、応答速度を向上させるための極めて重要な機能の一つです。
まずは統計情報を適切に整備し、必要に応じて各種ヒント句を使い分けることで、スケーラブルなデータ基盤の構築が可能となります。
留意点
セミジョイン最適化は極めて有効な手法ですが、設計および実装の際には以下の点に留意する必要があります。
統計情報の最新化: 暗黙的な最適化を効果的に機能させるには、TDV Studio上で各リソースの「Statistics(統計情報)」を定期的に更新しておく必要があります。
-
カーディナリティのバランス: 駆動表(絞り込みに使用する側)のデータ量が極端に大きい場合、逆にパフォーマンスが悪化する可能性があります。
効果が高いケース: 小規模なテーブルを用いて、大規模なテーブルを絞り込む場合。
効果が低いケース: 双方のテーブルが数千万件規模であり、かつユニークキーの総数も膨大な場合。
プッシュダウンの可否: 結合対象となるデータソースが、転送されてくる
IN句や複雑なフィルタ条件を処理できる性能(または実行権限)を備えている必要があります。リテラルの制限: 接続先データベースの仕様によっては、SQLの
IN句に含められる値の数に上限が設けられている場合があります。TDVはこれを自動的に分割して処理しますが、一定のオーバーヘッドが発生することに留意してください。
7. 参考情報
ユーザー ガイド > パフォーマンス チューニング > セミジョイン最適化オプション