6 シャード・データベース環境でのリクエストのルーティング

Oracle Shardingでは、シャードへの直接(キー・ベース)ルーティング、プロキシによるルーティングおよび中間層へのルーティングをサポートされています。

次の項では、シャード・データベース環境でのルーティングについて説明します。

シャードへの直接ルーティング

Oracleクライアントおよび接続プールは、高パフォーマンスのデータ依存ルーティングのために接続文字列に指定されたシャーディング・キーを認識できます。接続レイヤーのシャード・ルーティング・キャッシュは、データが常駐するシャードにデータベース・リクエストを直接ルーティングするために使用されます。

次の項では、シャードへの直接(キーベース)ルーティングについて説明します。

シャードへの直接ルーティングについて

シャードへの直接(キーベース)ルーティングでは、必要なトランザクションに関連するデータが含まれている単一の関連するシャードに対して、シャーディング・キーを使用して接続が確立されます。

接続のチェック時にユーザー・セッション・レベルでデータベース接続リクエストをルーティングするために、シャーディング・キーが使用されます。コンポジット・シャーディング方法では、シャーディング・キーおよびスーパー・シャーディング・キーの両方が必要となります。直接(キーベース)ルーティングでは、シャーディング・キー(またはスーパー・シャーディング・キー)を接続の一部として渡す必要があります。この情報に基づいて、指定されたシャーディング・キーまたはスーパー・シャーディング・キーに関係するデータが含まれている関連するシャードに対して、接続が確立されます。

シャードとのセッションが確立されると、すべてのSQL問合せおよびDMLが該当するシャードのスコープ内でサポートされ、実行されます。このルーティングは高速であり、シャード内トランザクションを実行するすべてのワークロードで使用されます。最高のパフォーマンスおよび可用性を必要とするすべてのワークロードに直接ルーティングを使用することをお薦めします。

Oracle Shardingをサポートするために、Oracle接続プールおよびドライバに対して重要な機能強化が行われました。Oracle Database 12cリリース2以降では、JDBC、UCP (Universal Connection Pool)、OCI (OCI Session Pool)およびODP.NET (Oracle Data Provider for .NET)は、接続作成中にシャーディング・キーを渡すための新しいAPIを提供しています。Apache Tomcat、IBM Websphere、Oracle WebLogic ServerおよびJBOSSでは、JDBC/UCPのサポートを活用してシャーディングを使用できます。PHP、Python、PerlおよびNode.jsではOCIのサポートを活用できます。

シャード・トポロジ・キャッシュは、シャードとシャーディング・キーの範囲のマッピングです。Oracle統合接続プールは、このシャード・トポロジ・キャッシュをそのメモリー内に保持します。特定のシャードへの最初の接続時(プールの初期化時またはプールが新しいシャードに接続するとき)に、シャーディング・キーの範囲のマッピングがシャードから収集されて、シャード・トポロジ・キャッシュが動的に作成されます。

シャード・トポロジをキャッシュすると、シャードへの高速なパスが作成され、シャードへの接続を作成するプロセスが迅速になります。シャーディング・キーを使用して接続リクエストが実行されると、接続プールはこの特定のシャーディング・キーが存在する対応するシャードを検索します(トポロジ・キャッシュから)。一致する接続がプールで利用できる場合、プールは内部接続選択アルゴリズムを適用することによって、シャードへの接続を返します。

キャッシュされたトポロジ・マップに存在する特定のシャーディング・キーへのデータベース接続リクエストは、そのシャードに直接送られます(つまり、シャード・ディレクタがバイパスされます)。また、接続プールは、SDBからRLB通知をサブスクライブし、実行時ロード・バランシング・アドバイザに基づいて最適な接続を分配します。接続が確立されると、クライアントはシャードに対するトランザクションを直接実行します。指定したシャーディング・キーのすべてのトランザクションが実行されたら、アプリケーションはその接続をプールに返し、別のキーの接続を取得する必要があります。

一致する接続がプールにない場合は、シャーディング・キーとともに接続リクエストをシャード・ディレクタに転送することによって、新しい接続が作成されます。

プールが初期化され、シャード・トポロジ・キャッシュがすべてのシャードに基づいて作成されると、シャード・ディレクタが停止しても直接ルーティングに影響しなくなります。

シャーディングAPI

Oracle接続プールおよびドライバはOracle Shardingをサポートしています。

JDBC、UCP、OCIおよびOracle Data Provider for .NET (ODP.NET)は、接続チェックの一部としてシャーディング・キーを認識します。Apache Tomcat、WebsphereおよびWebLogicはシャーディングのためのUCPサポートを活用し、PHP、Python、PerlおよびNode.jsはOCIサポートを活用します。

Oracle UCPのシャーディングAPI

シャード対応のアプリケーションは、createShardingKeyBuilderおよびcreateConnectionBuilderという拡張シャーディングAPIを使用してシャーディング・キーを指定することによって、特定のシャードへの接続を取得します。

大まかに言うと、アプリケーションがシャード・データベースと連携するようにするには、次のステップに従う必要があります。

  1. シャード・ディレクタおよびグローバル・サービスが反映されるようにURLを更新します。

  2. プール・パラメータをプール・レベルおよびシャード・レベルで設定します。

    • setInitialPoolSize: UCPが起動されたときに作成される初期の接続数

    • setMinPoolSize: 実行時にプールによって維持される接続の最小数

    • setMaxPoolSize: 接続プールで許可される接続の最大数を設定するためのUCPプロパティ

    • setMaxConnectionsPerShard: シャードごとの最大接続数の設定

  3. createShardingKeyBuilderを使用してシャーディング・キー・オブジェクトを作成します。

  4. createConnectionBuilderを使用して接続を確立します。

  5. 特定のシャードのスコープ内でトランザクションを実行します。

例6-1 UCPシャーディングAPIを使用した接続の確立

シャーディング・キーを作成し、UCPシャーディングAPIコールを使用して接続を確立する方法を示すコード片を次に示します。

...

PoolDataSource pds =                                
     PoolDataSourceFactory.getPoolDataSource();
  
  // Set Connection Pool properties
pds.setURL(DB_URL);
pds.setUser("hr");  
pds.setPassword("****");
pds.setInitialPoolSize(10);
pds.setMinPoolSize(20);
pds.setMaxPoolSize(30);
                
// build the sharding key object

OracleShardingKey shardingKey = 
    pds.createShardingKeyBuilder() 
      .subkey("mary.smith@example.com", OracleType.VARCHAR2)
      .build(); 

  // Get an UCP connection for a shard
Connection conn = 
    pds.createConnectionBuilder()
     .shardingKey(shardingKey)
     .build();
...

例6-2 UCP接続プールを使用したシャード対応アプリケーション・コードの例

この例では、プール設定をプール・レベルおよびシャード・レベルで定義しています。

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import oracle.jdbc.OracleShardingKey;
import oracle.jdbc.OracleType;
import oracle.jdbc.pool.OracleDataSource;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceFactory;

public class MaxConnPerShard
{    
  public static void main(String[] args) throws SQLException
  {    
    String url = "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(HOST=shard-dir1)(PORT=3216)
 (PROTOCOL=tcp))(CONNECT_DATA=(SERVICE_NAME=shsvc.shpool.oradbcloud)(REGION=east)))";
    String user="testuser1", pwd = "testuser1";  
  
    int maxPerShard = 100, initPoolSize = 20;    

    PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
    pds.setConnectionFactoryClassName(OracleDataSource.class.getName());
    pds.setURL(url);
    pds.setUser(user);
    pds.setPassword(pwd);
    pds.setConnectionPoolName("testpool");
    pds.setInitialPoolSize(initPoolSize);    

    // set max connection per shard
    pds.setMaxConnectionsPerShard(maxPerShard);
    System.out.println("Max-connections per shard is: "+pds.getMaxConnectionsPerShard());
                
    // build the sharding key object
    int shardingKeyVal = 123;    
    OracleShardingKey sdkey = pds.createShardingKeyBuilder()
        .subkey(shardingKeyVal, OracleType.NUMBER)
        .build();

    // try to build maxPerShard connections with the sharding key
    Connection[] conns = new Connection[maxPerShard];
    for (int i=0; i<maxPerShard; i++)
    {      
      conns[i] = pds.createConnectionBuilder()
          .shardingKey(sdkey)
          .build();
    
Statement stmt = conns[i].createStatement();
      ResultSet rs = stmt.executeQuery("select sys_context('userenv', 'instance_name'),
       sys_context('userenv', 'chunk_id') from dual");
      while (rs.next()) {
        System.out.println((i+1)+" - inst:"+rs.getString(1)+", chunk:"+rs.getString(2));
      }
      rs.close();
      stmt.close();
    }      

    System.out.println("Try to build "+(maxPerShard+1)+" connection ...");
    try {
      Connection conn = pds.createConnectionBuilder()
          .shardingKey(sdkey)
          .build();

      Statement stmt = conn.createStatement();
      ResultSet rs = stmt.executeQuery("select sys_context('userenv', 'instance_name'),
       sys_context('userenv', 'chunk_id') from dual");
      while (rs.next()) {
        System.out.println((maxPerShard+1)+" - inst:"+rs.getString(1)+",
         chunk:"+rs.getString(2));
      }
      rs.close();
      stmt.close();

      System.out.println("Problem!!! could not build connection as max-connections per
        shard exceeded");
      conn.close();
    } catch (SQLException e) {
      System.out.println("Max-connections per shard met, could not build connection
        any more, expected exception: "+e.getMessage());
    }    
    for (int i=0; i<conns.length; i++)
    {
      conns[i].close();
    }        
  }
}

ODP.NETシャーディングAPI

シャード対応のアプリケーションは、ODP.NET API (OracleConnectionクラスのSetShardingKey(OracleShardingKey shardingKey, OracleShardingKey superShardingKey)インスタンス・メソッドなど)を使用してシャーディング・キーおよびスーパー・シャーディング・キーを指定することによって、特定のシャードへの接続を取得します。

大まかに言うと、.NETアプリケーションがシャード・データベースと連携するようにするには、次のステップを実行する必要があります。

  1. ODP.NET管理対象外ドライバを使用します。

    シャーディングは、ODP.NET接続プールを使用する場合もODP.NET接続プールを使用しない場合もサポートされます。各プールは、シャード・データベースの異なるシャードへの接続を保持できます。

  2. OracleShardingKeyクラスを使用して、シャーディング・キー、およびスーパー・シャーディング・キーの別のインスタンスを設定します。

  3. ODP.NETが指定したシャーディング・キーおよびスーパー・シャーディング・キーを使用する接続を返すことができるように、OracleConnection.Open()を呼び出す前に、OracleConnection.SetShardingKey()メソッドを呼び出します。

    これらのキーは、OracleConnectionがクローズ状態のときに設定する必要があります。そうしないと、例外がスローされます。

例6-3 ODP.NETを使用したシャード対応アプリケーション・コードの例

using System;
using Oracle.DataAccess.Client;
 
class Sharding
{
  static void Main()
  {
    OracleConnection con = new OracleConnection
      ("user id=hr;password=hr;Data Source=orcl;");
    //Setting a shard key
    OracleShardingKey shardingKey = new OracleShardingKey(OracleDbType.Int32, 123);
    //Setting a second shard key value for a composite key
    shardingKey.SetShardingKey(OracleDbType.Varchar2, "gold");
    //Creating and setting the super shard key
    OracleShardingKey superShardingKey = new OracleShardingKey();
    superShardingKey.SetShardingKey(OracleDbType.Int32, 1000);
    
    //Setting super sharding key and sharding key on the connection
    con.SetShardingKey(shardingKey, superShardingKey);
    con.Open();

    //perform SQL query
  }
}

関連項目:

Oracle Shardingに対するJDBCのサポートの詳細は、Oracle Database JDBC開発者ガイドを参照してください

Oracle Shardingに対するUCPのサポートの詳細は、Oracle Universal Connection Pool開発者ガイドを参照してください

Oracle Shardingに対するODP.NETのサポートの詳細は、Microsoft Windows用Oracle Data Provider for .NET開発者ガイドを参照してください

シャードを使用するためのOCIインタフェースの詳細は、Oracle Call Interfaceプログラマーズ・ガイドを参照してください

シャード・データベースでのプロキシ・ルーティングを使用した問合せおよびDML

シャーディングでは、シャーディング・キーを指定しない問合せのルーティングがサポートされます。これにより、データベース・アプリケーションが柔軟になり、問合せが実行されるシャードを指定することなく、表がシャーディングまたは複製されているシステムでSQL文(SELECTおよびDMLを含む)を実行できるようになります。

次のトピックでは、プロキシ・ルーティングについてさらに詳しく説明します。

シャード・データベースでのプロキシ・ルーティングについて

プロキシ・ルーティングは、開発者の利便性のための補助的な使用パターンです。これを使用するには、コーディネータに対して接続を確立する必要があります。

シャード・カタログ・データベースには、コーディネータ・データベースの役割があります。コーディネータは、シャード・トポロジのメタデータを使用し、シャード・データベースの問合せ処理をサポートします。SQLコンパイラは、関連するシャードを自動的に識別し、関与するすべてのシャード間での問合せの実行を調整します。コーディネータとのセッションが確立されると、変更を必要とせずにSQL問合せおよびDMLが実行されます。

プロキシ・ルーティングは次のシナリオに適しています。

  • 接続中にアプリケーションがシャーディング・キーを渡すことができない場合

  • アプリケーションが複数のシャードに存在するシャード表のデータにアクセスする必要がある場合

  • レポート(販売データの集計など)に一般的に使用されるSQL問合せ

コーディネータを使用したルーティングでは、接続中にシャーディング・キー値を渡すことなく、アプリケーションがSQL文を発行できます。コーディネータのSQLコンパイラは問合せを分析して問合せの断片にリライトし、それらが関与するシャードに送信されて実行されます。問合せは、関与するシャードでほとんどの問合せ処理が行われて、コーディネータによって集計されるようにリライトされます。要するに、シャードはコーディネータによって実行された問合せの計算ノードとして動作します。計算がデータのあるシャードにプッシュされるため、シャードとコーディネータの間のデータの移動が減少します。この仕組みによって、コーディネータの処理負荷ができるだけ多くのシャードに移動するため、リソースを効率的に使用できるようになります。

アプリケーションでワークロードを直接ルーティングとプロキシ・ルーティングに分離することをお薦めします。これらのワークロードのために、個別の接続プールを作成する必要があります。

Oracle Shardingコーディネータ

Oracle Shardingコーディネータ・データベースには、シャード・トポロジのメタデータが含まれており、シャード・データベースの問合せ処理をサポートします。

コーディネータへの接続

マルチシャード問合せを実行するには、シャード・カタログ・データベースでGDS$CATALOGサービスを使用してコーディネータに接続します。

sqlplus app_schema/app_schema@shardcatvm:1521/GDS\$CATALOG.oradbcloud

コーディネータの高可用性

コーディネータを使用できないと、プロキシ・ルーティング・ベースのワークロードに影響するため、ファスト・スタート・フェイルオーバーを有効にして最大可用性保護モード(データ損失のないフェイルオーバー)でData Guardを使用してコーディネータを保護することをお薦めします。コーディネータはさらなる可用性およびスケーラビリティのためにオプションでOracle RAC対応にすることができます。

コーディネータが使用できなくても、直接ルーティングを使用したワークロードには影響しません。

コーディネータ・データベースのサイズ設定

シャード・カタログおよびコーディネータは次の重要な情報をホストします。

  • シャード・データベース・トポロジのメタデータ

  • シャード・アプリケーションのスキーマ

  • 重複表のマスター・コピー

メタデータおよびスキーマのサイズは通常どおりです。ただし、コーディネータのサイズを決定するときに、重複表の数およびそれらが占有する領域を計画する必要があります。

それに加えて、CPU、I/Oおよびメモリーが集中的に使用されることがあるプロキシ・ルーティングを処理するために、SQL問合せおよび処理されるデータ量に基づいて、コーディネータのサイズを設定する必要もあります。

プロキシ・ルーティングの回復力

可用性およびスケーラビリティのために、ファスト・スタート・フェイルオーバーを持つData Guardを使用したり、オプションでOracle RAC対応にすることによって、コーディネータを保護することをお薦めします。

コーディネータの障害は、コーディネータを介してルーティングされるマルチシャード問合せおよび単一シャード問合せに影響します。問合せの実行中の障害のシナリオ、およびプロキシ・ルーティングの予期される動作を次に示します。

  • 関与するシャードが停止している場合、コーディネータは同じデータを使用してその問合せを別のシャードに送信します。

  • 関与するシャードに対して問合せを実行しているときに障害が発生した場合、ユーザーはエラーを受け取ります。

プロキシ・ルーティングを使用した問合せおよびDML

プロキシ・ルーティングを使用すると、データを集計したり、複数のシャードに関するレポートを作成したりできます。また、データベース・アプリケーションが柔軟になり、問合せが実行されるシャーディング・キーを(接続中に)指定することなく、表がシャーディングまたは複製されているシステムでSQL文(SELECT、DMLを含む)を実行できるようになります。

集計とシャーディング・キーを使用しないSQL実行のどちらのユースケースでも、ユーザーは直接(キーベース)ルーティングと比べて低いレベルのパフォーマンスを受け入れます。

シャード・データベース(SDB)には、2つのタイプの表(シャード表および重複表)があります。

シャード表はシャーディング・キーによって等価パーティション化されます。

S=S1 U S2 UU Sn

重複表はすべてのシャードで同一です。

R = R1 = … = Rn

SDBのプロキシ・ルーティングは、アプリケーションが関連するシャードを指定することなく、シャード表および重複表のデータにアクセスする通常のSQL問合せを実行するための透過的なメカニズムを提供します。SQLコンパイラは、関連するシャードを自動的に識別し、関与するすべてのシャード間での問合せの実行を調整します。コーディネータとシャードの間の通信のために、データベース・リンクが使用されます。

大まかに言うと、コーディネータは各受信問合せQを2つの問合せ(CQおよびSQ)で構成される分配形式にリライトします。ここで、SQ (シャード問合せ)は関与する各シャードで実行されるQの部分であり、CQ (コーディネータ問合せ)はコーディネータ・シャードで実行される部分です。

Q  =>  CQ ( Shard_Iterator( SQ ) )

次の例は、集計問合せQ1をシャード間実行のためにQ1'にリライトしています。

Q1 : SELECT COUNT(*) FROM customers

Q1’: SELECT SUM(sc) FROM (Shard_Iterator(SELECT COUNT(*) sc FROM s1 (i) ))

このプロセスには2つの重要な要素があります。(1)関連するシャードを識別し、(2)分配形式でシャードごとに繰り返すように問合せをリライトすることです。

コーディネータ・データベースでの問合せのコンパイル中に、問合せコンパイラはシャーディング・キーに対する述語を分析し、関与するシャード(つまり、問合せで参照されるシャード表の行があるシャード)を識別するために使用できる述語を抽出します。残りのシャードは除外されたシャードと呼ばれます。

関与するシャードが1つのみ識別された場合は、問合せ全体がそのシャードにルーティングされて、すべてが実行されます。これは単一シャード問合せと言います。関与する複数のシャードがある場合、その問合せはマルチシャード問合せと呼ばれ、リライトされます。リライト処理では、問合せによって計算される式および問合せの形態が考慮されます。

単一シャード問合せのためのプロキシ・ルーティング

単一シャード問合せは、1つのシャードでのみデータをスキャンする必要があり、他のシャードでデータを検索する必要がない問合せです。

単一シャード問合せは、特定のシャードに接続してそのシャードに問合せを発行するクライアントに似ています。このシナリオでは、問合せ全体が関与する単一のシャードで実行され、コーディネータは処理された行をクライアントに返すだけです。コーディネータの計画は、リモート・マップ・カーソルに似ています。

たとえば、次の問合せは、顧客123のデータが1つのシャードのみにあるため、そのシャードにすべてマップされます。

SELECT count(*) FROM customers c, orders o WHERE c.custno = o.custno and c.custno = 123;

この問合せには、問合せのコンパイル時(リテラル)または問合せ開始時(バインド)に判明する1つのシャードにのみマップするシャード・キーの条件が含まれています。問合せは該当するシャードですべて実行されます。単一シャード問合せでは、SELECT、UPDATE、DELETEおよびINSERTを使用できます。MERGE/UPSERTはサポートされません。

単一シャード問合せでは次のものがサポートされます。

  • 等価およびINリスト(Area = 'West'など)

  • リテラル、バインド、またはリテラルとバインドの式を含む次のような条件

    Area = :bind
    
    Area = CASE :bind <10 THEN ‘West’ ELSE ‘East’ END
  • SELECTUPDATEDELETEおよびINSERT

マルチシャード問合せのためのプロキシ・ルーティング

マルチシャード問合せでは、複数のシャードでデータをスキャンする必要があり、各シャードでの処理は他のシャードから独立しています。

マルチシャード問合せは、複数のシャードにマップされ、結果をクライアントに送信する前に、コーディネータが処理する必要がある場合があります。たとえば、次の問合せは各顧客によって発注されたオーダー数をフェッチします。

SELECT count(*), c.custno FROM customers c, orders o WHERE c.custno = o.custno
 GROUP BY c.custno;

この問合せは、コーディネータによって次のように変換されます。

SELECT sum(count_col), custno FROM (SELECT count(*) count_col, c.custno
 FROM customers c, orders o 
 WHERE c.custno = o.custno GROUP BY c.custno) GROUP BY custno;

インライン問合せブロックは、リモート・マップ問合せブロックのように各シャードにマップされます。コーディネータは、すべてのシャードからの結果セットに対して、さらなる集計およびGROUP BYを実行します。各シャードでの実行の単位はインライン問合せブロックです。

マルチシャード問合せではSELECT文のみがサポートされます。問合せは、単一のシャード(等価フィルタの場合)またはすべてのシャード(シャーディング・キーにフィルタ述語がない場合)にアクセスできます。

マルチシャード問合せでの一貫性レベルの指定

シャード間でマルチシャード問合せを実行するときに、初期化パラメータMULTISHARD_QUERY_DATA_CONSISTENCYを使用して様々な一貫性レベルを設定できます。

マルチシャード問合せでは、様々な一貫性レベルを指定できます。たとえば、一部の問合せでシャード間のSCN同期のコストを回避する必要がある場合は、それらのシャードをグローバルに分散できます。別のユース・ケースとして、レプリケーション用のスタンバイを使用している場合は、プライマリとそのスタンバイから結果がフェッチされる可能性があるため、マルチシャード問合せで少し古いデータが許容されます。

デフォルトのモードは厳密な一貫性であり、すべてのシャード間でSCN同期が実行されます。他のモードでは、SCN同期はスキップされます。delayed_standby_allowedレベルでは、ロード・バランシングなどの要因に応じてスタンバイからもデータをフェッチでき、古いデータを含めることができます。

このパラメータは、システム・レベルまたはセッション・レベルで設定できます。

関連項目:

MULTISHARD_QUERY_DATA_CONSISTENCYの使用方法の詳細は、Oracle Databaseリファレンスを参照してください。

プロキシ・ルーティングでサポートされる問合せの形態

Oracle Shardingでは、単一シャード問合せとマルチシャード問合せの形態がサポートされていますが、いくつかの制限事項があります。

制限事項

Oracle Shardingでの問合せコンストラクトの使用に関する制限事項は、次のとおりです。

  • DMLの制限事項は、下の「トランザクション」の項の例を参照してください。

  • CONNECT BY問合せはサポートされていません。

  • コーディネータのシャード表のIASはサポートされていません。

  • MODEL句はサポートされていません。

重複表のみが関係する問合せ

重複表のみが関係する問合せの場合、問合せの形態に制限はありません。問合せはコーディネータで実行されます。

シャード表のみが関係する問合せ

単一表の問合せの場合、問合せにはシャードを限定するシャーディング・キーに対する等価フィルタを指定できます。結合問合せの場合は、すべての表がシャーディング・キーに対する等価フィルタを使用して結合される必要があります。次の例では、シャード表が関係する問合せの例をいくつか示します。

シャーディング・キーのみを使用する等価結合の内部結合

SELECT … FROM s1 INNER JOIN s2 ON s1.sk=s2.sk 
WHERE any_filter(s1) AND any_filter(s2)

シャーディング・キーのみを使用する左外部結合

SELECT … FROM s1 LEFT OUTER JOIN s2 ON s1.sk=s2.sk

右外部結合(左外部結合と同じ)

SELECT … FROM s1 RIGHT OUTER JOIN s2 ON s1.sk=s2.sk

シャーディング・キーのみを使用する完全外部結合(等価結合を必要とするネイティブが有効な場合のみ)

SELECT … FROM s1 FULL OUTER JOIN s2 ON s1.sk=s2.sk
WHERE any_filter(s1) AND any_filter(s2)

シャード表および重複表が関係する問合せ

シャード表と重複表が関係する問合せは、シャーディング・キーに対する述語に応じて、単一シャード問合せまたはマルチシャード問合せになります。唯一の違いは、各シャードで重複している非シャード表が問合せに含まれていることです。

シャード表と重複表の結合では、任意の列で、任意の比較演算子(= < > <= >=)または任意の結合式を使用できます。結合パターンの例を次に示します。

内部結合

SELECT … FROM s1 INNER JOIN r1 ON any_join_condition(s1,r1) 
WHERE any_filter(s1) AND any_filter(r1)

左/右外部結合

シャード表がLEFT OUTER JOINの最初の表である

SELECT … FROM s1 LEFT OUTER JOIN r1 ON any_join_condition(s1,r1) 
WHERE any_filter(s1) AND any_filter(r1)

SELECT … FROM r1 LEFT OUTER JOIN s1 ON any_join_condition(s1,s2) 
AND any_filter(r1) AND filter_one_shard(s1)

シャード表がRIGHT OUTER JOINの2番目の表である

SELECT … FROM r1 RIGHT OUTER JOIN s1 ON any_join_condition(s1,r1) 
WHERE any_filter(s1) AND any_filter(r1)

SELECT … FROM s1 RIGHT OUTER JOIN r1 ON any_join_condition(s1,s2) 
AND filter_one_shard(s1) AND any_filter(r1)

完全外部結合

SELECT … FROM s1 FULL OUTER JOIN r1 ON s1.sk=s2.sk
WHERE any_filter(s1) AND any_filter(s2)

セミ結合(EXISTS)

SELECT … FROM s1 EXISTS 
(SELECT 1 FROM r1 WHERE r1.anykey=s1.anykey)

SELECT … FROM r1 EXISTS 
(SELECT 1 FROM s1 WHERE r1.anykey=s1.anykey and filter_one_shard(s1))

アンチ結合(NOT EXISTS)

SELECT … FROM s1 NOT EXISTS 
(SELECT 1 FROM r1 WHERE r1.anykey=s1.anykey)	

左/右外部結合

重複表がLEFT OUTER JOINの最初の表であるか、シャード表が最初の表でシャーディング・キーに対するフィルタ述語に基づいて単一のシャードにマップされます。

SELECT … FROM r1 LEFT OUTER JOIN s1 ON any_join_condition(s1,s2) 
AND any_filter(r1) AND any_filter(s1)

重複表がRIGHT OUTER JOINの2番目の表であるか、シャード表が2番目の表でシャーディング・キーに対するフィルタ述語に基づいて単一のシャードにマップされます。

SELECT … FROM s1 RIGHT OUTER JOIN r1 ON any_join_condition(s1,s2) 
AND any_filter (s1) AND any_filter(r1)

完全外部結合

複数のシャードへのアクセスが必要となるシャード表:

SELECT … FROM s1 FULL OUTER JOIN r1 ON s1.non_sk=s2.non_sk
WHERE any_filter(s1) AND any_filter(s2)

セミ結合(EXISTS)

複数のシャードへのアクセスが必要となる副問合せのシャード表。

SELECT … FROM r1 EXISTS 
(SELECT 1 FROM s1 WHERE r1.anykey=s1.anykey)

アンチ結合(NOT EXISTS)

シャード表が副問合せにあります:

SELECT … FROM r1 NOT EXISTS 
(SELECT 1 FROM s1 WHERE r1.anykey=s1.anykey

集計関数

次の集計はプロキシ・ルーティングによってサポートされます。

  • COUNT

  • SUM

  • MIN

  • MAX

  • AVG

マルチシャード問合せとグローバルCR

マルチシャード問合せでは、すべてのシャードで最も大きい共通SCNで問合せを発行することによって、グローバルな読込み一貫性(CR)を維持する必要があります。一貫性レベルを設定する方法の詳細は、マルチシャード問合せでの一貫性レベルの指定を参照してください。

トランザクション

1つのシャードのみに影響するDML文はサポートされます。例:

update S1 set col = … where sk = <constant>;

複数のシャードに影響するDML文はサポートされません。例:

update S1 set col = …;

1つのトランザクション内では、複数の単一シャードDMLを異なるシャードに対して実行できます。例:

insert into S1 values (…);
Update S1 set col = … where sk = constant;
Delete S1 where sk = constant;
Commit;

マルチシャードDMLの場合、コーディネータはデータベース・リンクを使用して分散トランザクションを開始し、2フェーズ・コミットを実行して分散トランザクションの一貫性を保証します。インダウト・トランザクションの場合、データベース管理者はそれを手動でリカバリする必要があります。

マルチシャード問合せでのヒントの受け渡し

コーディネータに対する元の問合せで指定したヒントは、シャードに伝播されます。

マルチシャード問合せでの実行速度低下のトレースとトラブルシューティング

クエリー・リライトおよびシャード・プルーニングをトレースするには、コーディネータに対してトレース・イベントshard_sqlを設定します。観測される一般的なパフォーマンス問題の1つは、シャーディングの一定の制限により、GROUP BYがシャードにプッシュされない場合です。考えられるすべての操作がシャードにプッシュされ、シャードからの結果を統合するためのコーディネータでの処理が最低限であるかどうかを確認します。

プロキシ・ルーティング用の実行計画

マルチシャード問合せでは、各シャードが問合せ内の他のシャードの計画とは異なる可能性がある別個の実行計画を生成します。

Oracle Database 18c以降では、SQLフラグメントの実行計画を確認するために個別のシャードに接続する必要はなくなりました。dbms_xplan.display_cursor()で提供されるインタフェースは、シャードで実行されたSQLセグメントの計画をコーディネータに表示します。また、[V/X]$SHARD_SQLは、マルチシャード問合せのシャードSQLフラグメントをターゲットのシャード・データベースに一意にマップします。

dbms_xplan.display_cursor()のSQLセグメント・インタフェース

2つのインタフェースを使用して、シャードで実行されたSQLセグメントの計画を表示します。これらのインタフェースは、引数としてシャードIDを取り、指定されたシャードの計画を表示します。ALL_SHARDS形式では、すべてのシャードの計画が表示されます。

シャードのすべての計画を出力するには、次に示すようにALL_SHARDS形式を使用します。

select * from table(dbms_xplan.display_cursor(sql_id=>:sqlid,
                                              cursor_child_no=>:childno,
                                              format=>'BASIC +ALL_SHARDS‘,
                                              shard_ids=>shard_ids))

シャードの計画を選択して出力するには、display_cursor()関数内でシャードIDを渡します。複数のシャードの計画を出力する場合は、次に示すようにshard_idsパラメータで目的のシャードIDを含む数値の配列を渡します。

select * from table(dbms_xplan.display_cursor(sql_id=>:sqlid, 
                                               cursor_child_no=>:childno,
                                               format=>'BASIC',
                                               shard_ids=>ids))

1つのシャードの計画を返すには、次に示すようにshard_idパラメータにシャードIDを直接渡します。

select * from table(dbms_xplan.display_cursor(sql_id=>:sqlid,
                                              cursor_child_no=>:childno,
                                              format=>'BASIC',
                                              shard_id=>1))

[V/X]$SHARD_SQL

[V/X]$SHARD_SQLは、マルチシャード問合せのシャードSQLフラグメントをターゲットのシャード・データベースに一意にマップします。このビューは、特定のマルチシャード問合せのシャードSQLフラグメントごとにアクセスされたシャードのリストがシャード・コーディネータ・データベースに格納されている場合にのみ関連します。マルチシャード問合せを実行するたびに、異なるシャードのセットに対してシャードSQLフラグメントを実行できるため、実行のたびにシャードIDが更新されます。このビューには、各リモート・ノードのシャードSQLフラグメントのSQL IDと、シャードSQLフラグメントが実行されたシャードIDが保持されています。

Name                                      Null?    Type
----------------------------------------- -------- ----------------------------
 SQL_ID 				                              	   VARCHAR2(13)
 CHILD_NUMBER		                          			   NUMBER
 NODE_ID					                                 NUMBER
 SHARD_SQL_ID			                           		   VARCHAR2(13)
 SHARD_ID					                                 NUMBER
 SHARD_CHILD_NUMBER			                      	   NUMBER
  • SQL ID - コーディネータ上のマルチシャード問合せのSQL ID

  • CHILD_NUMBER - コーディネータ上のマルチシャード問合せのカーソル子番号

  • NODE - マルチシャード問合せのシャードSQLフラグメントのリモート・ノードのID

  • SHARD_SQL_ID - 指定されたリモート・ノードIDに対するシャードSQLフラグメントのSQL ID

  • SHARD_ID - シャードSQLフラグメントが実行されたシャードのID

  • SHARD_CHILD_NUMBER - シャードに対するシャードSQLフラグメントのカーソル子番号(デフォルトは0)

シャード・データベースに対するマルチシャード問合せと実行計画の例を次に示します。

SQL> select count(*) from departments a where exists (select distinct department_id
 from departments b where b.department_id=60);
------------------------------------------------
| Id  | Operation          | Name              |
------------------------------------------------
|   0 | SELECT STATEMENT   |                   |
|   1 |  SORT AGGREGATE    |                   |
|   2 |   FILTER           |                   |
|   3 |    VIEW            | VW_SHARD_377C5901 |
|   4 |     SHARD ITERATOR |                   |
|   5 |      REMOTE        |                   |
|   6 |    VIEW            | VW_SHARD_EEC581E4 |
|   7 |     SHARD ITERATOR |                   |
|   8 |      REMOTE        |                   |
------------------------------------------------

V$SHARD_SQLビューに対するSQL_IDの問合せ。

SQL> Select * from v$shard_sql where SQL_ID = ‘1m024z033271u’;
SQL_ID        NODE_ID   SHARD_SQL_ID  SHARD_ID
------------- -------  -------------- --------
1m024z033271u       5   5z386yz9suujt        1
1m024z033271u       5   5z386yz9suujt       11 
1m024z033271u       5   5z386yz9suujt       21 
1m024z033271u       8   8f50ctj1a2tbs 	    11

中間層接続プールとシャード間のアフィニティの作成

中間層ルーティングと呼ばれるOracle Universal Connection Pool (UCP)機能により、スマート・ルーター(F5 BigIPなど)がシャーディング・キーに関連付けられた中間層にルーティングできます。シャード・データベース・トポロジをルーター層に公開して、特定のシャーディング・キーに基づくリクエストが適切なアプリケーション中間層にルーティングされ、そこから特定のシャード・サブセットに対して接続が確立されるようにします。

一般的なOracle Sharding環境では、中間層接続プールはデータベース・リクエストを特定のシャードにルーティングします。このため、各中間層接続プールが各シャードへの接続を確立する状況をまねくことがあります。この場合、データベースへの接続が多くなりすぎる可能性があります。この問題は、中間層とシャード間にアフィニティを作成することで解決できます。このシナリオでは、各データ・センターまたはクラウドに中間層(Webサーバー、アプリケーション・サーバー)を確保し、クライアント・データ(クライアント・シャーディング・キーに対応する)を含むシャードが存在する中間層にクライアント・リクエストを直接ルーティングすることが理想的です。この種の設定によく使用される用語はスイム・レーンです。各スイムレーンは、Webサーバーからアプリケーション・サーバー、データベースへと至る専用のスタックです。

Oracle Universal Connection Pool (UCP)は、関連する中間層にクライアント・リクエストをルーティングするために使用できる中間層ルーティングAPIを提供することで、この問題を解決します。UCP中間層APIは、OracleShardRoutingCacheクラスによって公開されます。このクラスのインスタンスはUCP内部シャード・ルーティング・キャッシュを表し、ユーザー、パスワード、URLなどの接続プロパティを指定して作成できます。ルーティング・キャッシュは、シャーディング・カタログに接続して、シャード・マッピング・トポロジへのキーを取得し、キャッシュに格納します。

ルーティング・キャッシュはUCP中間層API getShardInfoForKey(shardKey,superShardKey)によって使用されます。これは、シャーディング・キーを入力として受け入れ、入力シャーディング・キーにマップされたShardInfoインスタンスのセットを返します。ShardInfoインスタンスは、シャードの一意のシャード名と優先度をカプセル化します。中間層APIを使用するアプリケーションは、戻された一意のシャード名の値を、特定のシャードへの接続がある中間層にマップできます。ルーティング・キャッシュは、各ONSイベントをサブスクライブすることによってチャンクの分割または他のシャードへの移動時に自動的に更新されます。

次のコード例は、Oracle UCP中間層ルーティングAPIの使用方法を示しています。

例6-4 UCP APIを使用した中間層ルーティング

import java.sql.SQLException;
import java.util.Properties;
import java.util.Random;
import java.util.Set;

import oracle.jdbc.OracleShardingKey;
import oracle.jdbc.OracleType;
import oracle.ucp.UniversalConnectionPoolException;
import oracle.ucp.routing.ShardInfo;
import oracle.ucp.routing.oracle.OracleShardRoutingCache;

/**
 * The code example illustrates the usage of UCP's mid-tier routing feature.  
 * The API accepts sharding key as input and returns the set of ShardInfo 
 * instances mapped to the sharding key. The ShardInfo instance encapsulates 
 * unique shard name and priority. The unique shard name then can be mapped 
 * to a mid-tier server which connects to a specific shard.
 *
 */
public class MidtierShardingExample {

  private static String user = "testuser1";
  private static String password = "testuser1";

  // catalog DB URL
  private static String url = "jdbc:oracle:thin:@//hostName:1521/catalogServiceName";
  private static String region = "regionName";

  public static void main(String args[]) throws Exception {
    testMidTierRouting();
  }

  static void testMidTierRouting() throws UniversalConnectionPoolException,
      SQLException {

    Properties dbConnectProperties = new Properties();
    dbConnectProperties.setProperty(OracleShardRoutingCache.USER, user);
    dbConnectProperties.setProperty(OracleShardRoutingCache.PASSWORD, password);
    // Mid-tier routing API accepts catalog DB URL
    dbConnectProperties.setProperty(OracleShardRoutingCache.URL, url);

    // Region name is required to get the ONS config string
    dbConnectProperties.setProperty(OracleShardRoutingCache.REGION, region);

    OracleShardRoutingCache routingCache = new OracleShardRoutingCache(
        dbConnectProperties);

    final int COUNT = 10;
    Random random = new Random();

    for (int i = 0; i < COUNT; i++) {
      int key = random.nextInt();
      OracleShardingKey shardKey = routingCache.getShardingKeyBuilder()
          .subkey(key, OracleType.NUMBER).build();
      OracleShardingKey superShardKey = null;

      Set<ShardInfo> shardInfoSet = routingCache.getShardInfoForKey(shardKey,
          superShardKey);

      for (ShardInfo shardInfo : shardInfoSet) {
        System.out.println("Sharding Key=" + key + " Shard Name="
            + shardInfo.getName() + " Priority=" + shardInfo.getPriority());
      }
    }

  }
}