14 JSONドキュメント・コレクションのシャーディング

Oracle Globally Distributed DatabaseをSODAとともに使用して、JSONドキュメントの表をシャードする方法について説明します。

JSONドキュメントのシャーディングの概要

Oracle Globally Distributed Databaseにより、JSONドキュメントを大量のデータおよびトランザクション・ボリュームに拡張し、障害分離を実現し、データ主権をサポートできます。Oracle Databaseでは、ネイティブJSONオブジェクトをサポートしています。アプリケーションは、SODA (Simple Oracle Document Access) APIを使用してシャード・データベースとやり取りでき、これにより、JSONドキュメント属性を使用してデータにアクセスできるようになります。

Oracle Databaseでは、JSONドキュメントをデータベース表に格納できます。データベース表はJSONコレクションとして機能し、それぞれの行が1つのJSONドキュメントです。JSONドキュメントは、OSONと呼ばれる高度に最適化されたバイナリJSON形式でバックアップされるJSON型としてデータベースに格納されます。

OracleではJSONドキュメントを作成、操作および取得するためのJSON演算子をサポートしていますが、SODAインタフェースもサポートしています。SODAは、JSONドキュメントを使用するためのより直感的なインタフェースを提供します。

SODAは、Oracle DatabaseのNoSQL形式のJSON (JSONのみではない)ドキュメント・コレクションのAPIです。SODA APIを使用すると、アプリケーションはコレクション内のドキュメントに対してCRUD操作を実行できます。コレクションは、通常のOracle表(またはビュー)によってバックアップされます。

通常、コレクションを作成するには、SODA APIを使用します。これにより、コレクションをバックアップする基礎となる表が作成されます。ただし、シャード・コレクションを作成するには、最初に共有表を作成する必要があります。その後、SODAのマップされたコレクション機能を使用して、シャード表の上にコレクションを作成できます。

シャード・データベースでJSONドキュメントを使用すると、シャーディング・キーの概念が導入されます。JSONドキュメントは、シャーディング・キーに従って個々のデータベース表のシャードに分散されます。シャーディング・キーは、JSONドキュメント内からのフィールド、またはSODA APIによって割り当てられたIDなどの外部列のいずれかです。

JSONおよびSODAの詳細は、Oracle DatabaseのJSONおよびSODAの概要を参照してください。

後述のトピックでは、Oracle DatabaseでJSONオブジェクトをシャーディングする方法について詳しく説明します。おおまかなステップは次のとおりです。

  • シャード・データベースをデプロイする
  • アプリケーションでデータのフェッチに使用できるシャーディング・キーを特定する
  • シャード表を作成してOracle DatabaseでJSON用のデータ・ストアを定義する
  • SODAを使用してシャード表をマップする

ライフサイクル管理タスクの詳細は次のとおりです。

  • アプリケーションのシャードJSONコレクションにドキュメントを追加する
  • アプリケーションのシャードJSONコレクションからドキュメント・データをフェッチする

環境の準備

SODAによるOracle Globally Distributed Databaseの構成を開始する前に、シャーディング構成をデプロイし、グローバル・サービスを開始します。

シャード・データベース構成(シャード・ディレクタ、シャード・カタログ、シャード・データベースを含む)およびレプリカをデプロイする必要があります。シャード・データベースをデプロイしたら、アプリケーションからの受信接続リクエストを処理するために、シャードでグローバル・データベース・サービスを作成して起動する必要があります。

シャード・データベース構成の作成およびデプロイの詳細は、シャード・データベースのデプロイを参照してください。

SODA権限を持つ全シャード・ユーザーの作成

シャード・データベースにスキーマ・オブジェクトを作成する権限を持ち、DBMS_SODA PL/SQLパッケージに対して必要なexecute権限も持つユーザーをシャード・カタログに作成します。

このドキュメントにおいては、ユーザーはSharding/SODAユーザーと呼ばれ、例でのユーザー名はapp_schemaです。

Sharding/SODAユーザーを作成するには、次のようにします。

  1. シャード・カタログ・データベースに(たとえば、SYSDBAとして)接続します。
  2. SHARD DDLを有効にします。
  3. CREATE USERコマンドを実行して、次の例に示す権限を付与します。

    Sharding/SODAユーザーは、CDBではなくPDBに作成されます。

Sharding/SODAユーザー作成スクリプトの例を次に示します。

-- Set the container and create the sharded user
alter session set container=SDBPDB;
alter session enable shard ddl;
create user app_schema identified by password;
 
-- Grant basic privileges
grant connect, resource, alter session to app_schema;
grant execute on dbms_crypto to app_schema;

-- All privileges below are required. User can also be granted all privileges
grant create table, create procedure, create tablespace, create
materialized view to app_schema;
grant unlimited tablespace to app_schema;
grant select_catalog_role to app_schema;

-- Grant soda_app for this user
grant soda_app to app_schema;

-- Specific grants on shard plsql
grant execute on exec_shard_plsql to app_schema;
grant gsmadmin_role to app_schema;
grant gsm_pooladmin_role to app_schema;

標準のデータベース・スキーマ権限と、ユーザーに付与された標準のSODA権限を書き留めます。exec_shard_plsql付与は、Sharding/SODAユーザーに必要なシャーディング固有の権限で、これにより、ユーザーはシャード・データベースでPL/SQLプロシージャを実行できるようになります。

シャーディング・ユーザーの作成やPL/SQLの実行など、Oracle Globally Distributed Databaseのスキーマ設計の詳細は、シャード・データベース・スキーマの設計を参照してください。

シャーディング・キーの選択

SODAコレクションは、通常のOracle表によってバックアップされます。これらの表の列の1つがID列で、コレクション内のドキュメントの一意のキーが含まれています。この列はシャーディング・キーとして使用できます。または、ドキュメント・コンテンツのJSONフィールドを選択して、シャーディング・キーにすることもできます。

シャーディング・キーの選択は、アプリケーションによって異なります。

各シャーディング・キーの選択のメリットおよびデメリットを次に示します。

シャーディング・キーとしてのSODA IDの使用

SODA APIは、SODAドキュメントごとに一意のIDを自動的に管理します。このIDは、コレクション内でドキュメントを作成および取得するためにSODA APIによって使用されます。

SODA IDをシャーディング・キーとして使用する場合は、アプリケーションによって手動で指定する必要があります。これは、特定のシャードに新しいドキュメントを作成する場合、適切なシャードに接続するために事前にシャーディング・キーが必要であるためです。SODA APIでは、ドキュメント作成時にSODA IDのこの手動(CLIENTキーとも呼ばれる)割当てが可能です。例については、「シャーディング・キーとしてのSODA IDの使用」のコード・サンプルを参照してください。

このSODA IDが意味のあるもの(顧客IDなど)を表しているか、または単に一意のドキュメントIDであるかを判断するのは、アプリケーションです。いずれの場合も、IDは一意である必要があります。これは、Oracle Globally Distributed Databaseによって課される要件ではなく、SODA APIによって課される要件です。

シャーディング・キーとしてSODA IDを使用するサマリー:

  • シャーディング・キーは一意である必要があります。
  • シャーディング・キーは、JSONフィールドの内容と関係のないドキュメントIDです。
  • 新しいドキュメントが挿入されるたびに、このIDをアプリケーションで指定する必要があります。

シャーディング・キーとしてのJSONフィールドの使用

JSONフィールドをシャーディング・キーとして使用できます。このキーを一意にする必要はありません。

この場合、コレクション内の各ドキュメントには個別のSODA ID (SODAで必要)がありますが、SODA APIによって個別のドキュメントIDとして自動的に管理されます。

シャーディング・キーとしてJSONフィールドを使用するサマリー:

  • シャーディング・キーは一意である必要はありません。
  • シャーディング・キーは、各ドキュメントのJSON内のフィールドです。
  • 新しいドキュメントを挿入するときにSODA IDを指定する必要はありません。

シャーディング・キー・メソッドの選択に関する考慮事項

どちらの場合も、シャーディング・キーはほとんど変更されないフィールドです。これは、一意に割り当てられた顧客またはドキュメントIDである場合があります。顧客の生年月日(日、月、年)または郵便番号など、一意でないIDにすることもできます。

システム管理のシャーディングでは、いずれかのシャーディング・キー方法がシャード間のドキュメントの配布に適しています。

ユーザー定義のシャーディングの場合、シャード・キーとしてのSODA IDは、IDに意味のある値があり、これを範囲でパーティション化することが理にかなっている場合にのみ意味があります。

他の制約がない場合、シャーディング・キーとしてJSONフィールドを使用すると、柔軟性が向上し、シャーディング・キーをJSONの一部として自然に格納できます。

システム管理シャーディングとユーザー定義シャーディング

多くの点で似ていますが、ユーザー定義のシャーディングでは、データの格納場所をより詳細に制御できます。これは、データを地理的に分離する必要がある場合や、データに物理マッピングも必要になるようなその他の理由が発生する場合に便利です。

後続のトピックのプロシージャおよび例の多くは、両方のシャーディング方法に適用されます。これには、2つの例外があります。

  1. SODAコレクションの基礎となるシャード表の作成時に、ユーザー定義シャーディングの物理マッピングを指定する必要があります。「シャーディング・キーとしてのJSONフィールドの使用」で、郵便番号の範囲を特定のシャードに配置する必要がある例を確認できます。
  2. SODA問合せ(QBE)は、このデータ・グループを使用して、シャーディング・キーの範囲を含む1つのシャードに対して問合せを実行できます。

ソリューションの実装方法

使用するシャーディング・キーのタイプを選択したら、次のユースケースを参照して、JSONコレクション用のシャード表を作成する方法およびアプリケーションからシャード表とやり取りする方法の例を確認します。

シャーディング・キーとしてのSODA IDの使用

シャード・データベース・スキーマの作成時に、SODA IDをシャーディング・キーとして指定できます。

次の例は、JSONコレクション用のシャード表を作成し、SODAマッピングを作成し、アプリケーションからシャード表にアクセスする方法をJavaおよびPythonコード・サンプルで示しています。

JSONコレクション用のシャード表の作成

SODA IDをシャーディング・キーとして使用するシャード表を作成するには、次のようにします。

  1. シャード・カタログにSharding/SODAユーザーとして接続します。
  2. SHARD DDLを有効にします。
  3. 表領域セットを作成します。
  4. 次の例に示すように、CREATE SHARDED TABLEを実行します。

次の例では、顧客プロファイル・ドキュメント(CUSTPROFILE)のJSONコレクション用のシャード表(Customers)を作成します。

SODA ID (ID)の列がJSONエントリを識別し、主キーおよびシャーディング・キーとしても使用されます。SODAを使用してJSONエントリを表に作成すると、アプリケーションによってID列に一意の値が移入されます。

その他の列は、SODAによって基礎となるコレクションを保持する表が作成されたときに指定されたデフォルトの列名です。これは、SODAコレクションを作成し、作成した表を調べると自分で確認できます。

シャード表の作成: システム管理

/* Enable shard DDL */
ALTER SESSION ENABLE SHARD DDL;

/* Create a tablespace set */
CREATE TABLESPACE SET TSP_SET_1 USING TEMPLATE
 (datafile size 100m autoextend on next 10M maxsize unlimited
 extent management local segment space management auto); 

/* Create the sharded table */
CREATE SHARDED TABLE CUSTOMERS
(
"ID" VARCHAR2(255) NOT NULL,
"CREATED_ON" timestamp default sys_extract_utc(SYSTIMESTAMP) NOT NULL,
"LAST_MODIFIED" timestamp default sys_extract_utc(SYSTIMESTAMP) NOT NULL,
"VERSION" varchar2(255) NOT NULL,
"CUSTPROFILE" JSON,
PRIMARY KEY (ID),
)
TABLESPACE SET TSP_SET_1
PARTITION BY CONSISTENT HASH (ID) PARTITIONS AUTO;
シャード表の作成: ユーザー定義

SODA IDに意味のある値がある場合は、ユーザー定義の方法を使用してデータベースをシャーディングでき、次の例を使用してシャード表を作成できます。

ユーザー定義シャード・データベースにシャード表を作成する前に、必要な表領域およびシャード領域が作成されていることを確認してください。シャード・データベース・オブジェクトの作成の詳細は、ユーザー定義シャーディングおよびOracle Globally Distributed Databaseトポロジの構成を参照してください。

/* Enable shard DDL */
ALTER SESSION ENABLE SHARD DDL;

/* Create the sharded table */
CREATE SHARDED TABLE CUSTOMERS
(
"ID" VARCHAR2(255) NOT NULL,
"CREATED_ON" timestamp default sys_extract_utc(SYSTIMESTAMP) NOT NULL,
"LAST_MODIFIED" timestamp default sys_extract_utc(SYSTIMESTAMP) NOT NULL,
"VERSION" varchar2(255) NOT NULL,
"CUSTPROFILE" JSON,
PRIMARY KEY (ID),
)
PARTITION BY RANGE (ID)
(PARTITION p1 VALUES LESS THAN ('5000') TABLESPACE ts1,
PARTITION p2 VALUES LESS THAN ('10000') TABLESPACE ts2)

シャード表でのマップされたSODAコレクションの作成

マップされたSODAコレクションを作成して、シャード表を操作する際に使用する列をSODAに認識させます。

このタスクでは、最初にマップされたコレクションを作成するプロシージャを実行します。これにより、以前に作成した表をSODAコレクションとしてSODAで認識するために必要なメタデータが作成されます。

その後、追加のプロシージャsys.exec_shard_plsql()を実行して、すべてのシャードおよび今後のすべてのシャードでマップ・コレクションが作成されるようにします。

すべてのシャードでSODAマップ済コレクションの作成

Sharding/SODAユーザーとして、SHARD DDLを有効にして、次のコマンドをシャード・カタログで実行します。シャード・カタログは、自動的に処理されるように、すべてのシャードにプロシージャを伝播します。

GRANT SODA_APP TO PROCEDURE APP_SCHEMA.COLLECTION_PROC_CUSTOMERS;

create or replace procedure COLLECTION_PROC_CUSTOMERS AS 
METADATA varchar2(8000);
 COL SODA_COLLECTION_T;
 begin METADATA := '{"tableName":"CUSTOMERS",
 "keyColumn":{"name":"ID","assignmentMethod" : "CLIENT"},
 "contentColumn":{"name":"CUSTPROFILE","sqlType":"JSON"},
 "versionColumn":{"name":"VERSION","method":"UUID"},
 "lastModifiedColumn":{"name":"LAST_MODIFIED"},
 "creationTimeColumn":{"name":"CREATED_ON"},
 "readOnly":false}'; 
-- Create a collection using "map" mode, based on 
-- the table you've created above and specified in 
-- the custom metadata under "tableName" field. 
COL := dbms_soda.create_collection('CUSTOMERS',METADATA,DBMS_SODA.CREATE_MODE_MAP); 
end ; 
/ 

exec sys.exec_shard_plsql('app_schema.collection_proc_customers()',4+1);

keyColumnは、各ドキュメントの一意のIDを保持するIDとしてマップされます。アプリケーションで挿入時にドキュメントごとに一意キーを提供するため、ここではCLIENTとして指定されます。

この時点で、新しいコレクションが作成されています。

PL/SQLを実行してコレクションを表示できます。シャード・カタログで次のコマンドを実行し、次に示すようにCUSTOMERSコレクションが出力に表示されていることを確認します。

SET SERVEROUTPUT ON
DECLARE
l_coll_list SODA_COLLNAME_LIST_T;
BEGIN
l_coll_list := DBMS_SODA.list_collection_names;
 
IF l_coll_list.COUNT > 0 THEN
FOR i IN 1 .. l_coll_list.COUNT LOOP
DBMS_OUTPUT.put_line(i || ' : ' || l_coll_list(i));
END LOOP;
END IF;
END;
/
1 : CUSTOMERS
 
PL/SQL procedure successfully completed.

コード・サンプル

JavaおよびPythonの次のコード・サンプルでは、シャーディング・キーを使用してシャードに接続し、新しいドキュメントを挿入する方法を示しています。

シャード・データベース環境でSODAを使用する場合は、シャード・カタログを使用せずに特定のシャードに接続して新しいドキュメントを作成する必要があることに注意してください。

Javaコード・サンプル

これらのJavaコード・サンプルは、「シャーディング・キーとしてのSODA IDの使用」のユースケース用に作成されています。

次のJavaサンプルは、シャードに接続し、JSONドキュメントをコレクションに挿入する方法を示しています。

import java.sql.Connection;
import java.util.Properties;
import java.util.List;
 
// SODA specific imports
import oracle.soda.rdbms.OracleRDBMSClient;
import oracle.soda.OracleDatabase;
import oracle.soda.OracleCursor;
import oracle.soda.OracleCollection;
import oracle.soda.OracleDocument;
import oracle.soda.OracleException;
 
// Sharding and UCP imports
import oracle.jdbc.OracleShardingKey;
import oracle.jdbc.OracleType;
import oracle.jdbc.pool.OracleDataSource;
import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;
 
 
/*
* The sample demonstrates connecting to a Sharded Database using
* Oracle JDBC driver and UCP as a client side connection pool.
*/
public class QuickInsertShard {
 
 public static void main(String args[]) throws Exception {
 
// TNS_ADMIN - Should be the path where the tnsnames.ora file resides
// dbshard_rw - It is the TNS alias present in tnsnames.ora.
// Note that the connection is to the Shard Director (GSM) and the service name is the shard RW service
final String DB_URL="jdbc:oracle:thin:@dbshard_rw?TNS_ADMIN=/home/opc/dbhome/";
 
// Update the Database Username and Password to the Shard User
final String DB_USER = "app_schema";
String DB_PASSWORD = "<user_password>" ;
 
 
// Get the PoolDataSource for UCP
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
 
// Set the connection factory first before all other properties
pds.setConnectionFactoryClassName(OracleDataSource.class.getName());
pds.setURL(DB_URL);
pds.setUser(DB_USER);
pds.setPassword(DB_PASSWORD);
pds.setConnectionPoolName("JDBC_UCP_POOL");
 
// Default is 0. Set the initial number of connections to be created
// when UCP is started.
pds.setInitialPoolSize(10);
// Default is 0. Set the minimum number of connections
// that is maintained by UCP at runtime.
pds.setMinPoolSize(10);
// Instead of Max Pool Size, we can set the number of max connections per shard
pds.setMaxConnectionsPerShard(20);
 
// We cannot get the connection until we have the Shard key which is part of the SQL
//We first set the sharding key or document id explicitly
String shardingKeyVal="10";
 
// Now we build the connection using this shard key
OracleShardingKey sdkey = pds.createShardingKeyBuilder().subkey(shardingKeyVal, OracleType.VARCHAR2).build();
System.out.println("Initiating UCP and Creating Connection...");
Connection conn = pds.createConnectionBuilder().shardingKey(sdkey).build();
 
// Enable the SODA Shared Metadata cache
Properties props = new Properties();
props.put("oracle.soda.sharedMetadataCache", "true");
OracleRDBMSClient cl = new OracleRDBMSClient(props);
 
// Get a DB Connection for use in SODA
OracleDatabase db = cl.getDatabase(conn);
 
// Print all the Collections in this DB
List<String> names =  db.admin().getCollectionNames();
for (String name : names)
 System.out.println ("Collection name: " + name);
 
// Open up the CUSTOMERS Collection
OracleCollection col = db.openCollection("CUSTOMERS");
 
//For a collection configured with client-assigned document keys,
//you must provide the key for the input document. Build a document with JSON.
OracleDocument cKeyDoc = db.createDocumentFromString(shardingKeyVal, "{\"name\": \"Matilda\", \"State\": \"CA\", \"ZIP\":\"94065\"}");
 
// Insert the document above
//If the key  already identifies a document in the collection
//then this will replace the existing doc.
OracleDocument savedDoc = col.saveAndGet(cKeyDoc);
 
// Get the document back assuming we only know the key
// We are still connected to the same shard
OracleDocument doc = col.find().key(shardingKeyVal).getOne();
String content = doc.getContentAsString();
System.out.println("Retrieved content is: " + content);
 
// We are done, so close the connection to the shard
conn.close();
 
// At this point we could open up a new shard connection using a different sharding key
 
 
 }} // End of QuickInsertShard

このJavaサンプルは、マルチシャード問合せの実行方法を示しています。

import java.sql.Connection;
import java.util.Properties;
import java.util.List;
 
// SODA specific imports
import oracle.soda.rdbms.OracleRDBMSClient;
import oracle.soda.OracleDatabase;
import oracle.soda.OracleCursor;
import oracle.soda.OracleCollection;
import oracle.soda.OracleDocument;
import oracle.soda.OracleException;
 
// Sharding and UCP imports
import oracle.jdbc.OracleShardingKey;
import oracle.jdbc.OracleType;
import oracle.jdbc.pool.OracleDataSource;
import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;
 
 
/*
* The sample demonstrates connecting to a Sharded Database using
* Oracle JDBC driver and UCP as a client side connection pool.
*/
public class QuickQueryCat {
 
 public static void main(String args[]) throws Exception {
 
// TNS_ADMIN - Should be the path where the tnsnames.ora file resides
// dbshard_rw - It is the TNS alias present in tnsnames.ora.
// This connection is to the shard director using the catalog service name.
final String DB_URL="jdbc:oracle:thin:@dbcat?TNS_ADMIN=/home/opc/dbhome/";
 
// Update the Database Username and Password to the Shard User
final String DB_USER = "app_schema";
String DB_PASSWORD = "<user_password>" ;
 
 
// Get the PoolDataSource for UCP
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
 
// Set the connection factory first before all other properties
pds.setConnectionFactoryClassName(OracleDataSource.class.getName());
pds.setURL(DB_URL);
pds.setUser(DB_USER);
pds.setPassword(DB_PASSWORD);
pds.setConnectionPoolName("JDBC_UCP_POOL");
 
// Now we get a direct connection to the shard catalog
System.out.println("Initiating UCP and Creating Connection...");
Connection conn = pds.getConnection();
 
// Enable the SODA Shared Metadata cache
Properties props = new Properties();
props.put("oracle.soda.sharedMetadataCache", "true");
OracleRDBMSClient cl = new OracleRDBMSClient(props);
 
// Get a DB Connection
OracleDatabase db = cl.getDatabase(conn);
 
// Print all the Collections in this DB
List<String> names =  db.admin().getCollectionNames();
for (String name : names)
 System.out.println ("Collection name: " + name);
 
// Open up the CUSTOMERS Collection
OracleCollection col = db.openCollection("CUSTOMERS");
 
// Do a search across ALL Shards. In this case all users named Matilda
// Setup the specification and open a cursor
OracleDocument filterSpec = db.createDocumentFromString("{ \"name\" : \"Matilda\"}");
 
OracleCursor c = col.find().filter(filterSpec).getCursor();
 
// Print the results of the query
while (c.hasNext()) {
  OracleDocument resultDoc = c.next();
 
  // Print the document key and document content
  System.out.println ("Document key: " + resultDoc.getKey() + "\n" +
                        " document content: " + resultDoc.getContentAsString());
}
 
// Close the cursor
c.close();
 
// Here, we could initiate another multi-shard query if desired
 
// We are done, so close the connection
conn.close();
 
 
 }} // End of QuickQueryCat
Pythonコード・サンプル

このPythonサンプルは、シャード・データベース環境でSODAを使用してJSONオブジェクトを実際に操作する方法を示しています。

このサンプル・コードを環境で使用するには、Python用のcx_Oracleモジュールをインストールする手順に従います: https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html

この例は、シャーディング・キーを使用してシャードに接続し、新しいドキュメントを挿入する方法を示しています。

シャード・データベース環境でSODAを使用する場合は、シャード・カタログを使用せずに特定のシャードに接続して新しいドキュメントを作成する必要があることに注意してください。

# import the cx_Oracle module for Python
import cx_Oracle

# Create a connection pool that will be used for connecting to all shards
# The components of the dsn are hostname (shard director),
# port (usually 1522), global service (created with GDSCTL)
# The pool is then created and SODA metadata caching is enabled.
dsn=cx_Oracle.makedsn("shard_director_host",1522,service_name="service_name")
pool=cx_Oracle.SessionPool("app_schema","password",dsn, soda_metadata_cache=True)

# Connect to a specific shard by using the sharding key, which in this example is
# set explicitly with "sodaid", but this might be passed in or part of a loop 
# You must know beforehand if you are creating or working with a document for a specific Customer
# 
sodaid="2468"
connection=pool.acquire(shardingkey=[sodaid])

# Set autocommit and open the CUSTOMERS collection
connection.autocommit = True
soda = connection.getSodaDatabase()
collection = soda.openCollection("CUSTOMERS")

# Insert a document
# Because you are specifying the shard key, you must pass that in with the document (key=custid)
# The value can be a UUID for example but it need not have any relation to the JSON Content.

content = {'name': 'Matilda', 'State': 'CA', 'ZIP':'94065'}
idcontent=soda.createDocument(content, key=sodaid)
doc = collection.insertOneAndGet(idcontent)

# Fetch the document back by key
doc = collection.find().key(sodaid).getOne()
content = doc.getContent()
print('Retrieved SODA document dictionary is:')
print(content)

# After you have finished, release this connection back into the pool
pool.release(connection)
  
# If you want to add or work with more customers, start with another connection
# For example: connection=pool.acquire(shardingkey=["123"]) and so on.
  
#When you are completely finished working with customers you can shut down the pool
pool.close()

このコード・サンプルは、マルチシャード問合せを実行して、名前がMで始まるすべての顧客名を返す方法を示しています。

import cx_Oracle
  
# Create an unpooled connection to the shard catalog
# In general, pooled connections should be used for all connections. This is shown here only as an example.
# The connect string connects to the shard director, but uses the catalog service, e.g. GD$catalog.oradbcloud
connection = cx_Oracle.connect("app_schema","password","db_connect_string")
  
# Open the CUSTOMERS collection
connection.autocommit = True
soda = connection.getSodaDatabase()
collection = soda.openCollection("CUSTOMERS")
  
# Now query the collection
# It is important to note that this is a query across ALL shards
# In other words, you will get ALL users whose names start with M
documents = collection.find().filter({'name': {'$like': 'M%'}}).getDocuments()
for d in documents:
    content = d.getContent()
    print(content["name"])
  
# Close the connection
connection.close()

シャーディング・キーとしてのJSONフィールドの使用

シャード・データベース・スキーマの作成時に、JSONフィールドをシャーディング・キーとして指定できます。

次のトピックの例は、JSONコレクション用のシャード表を作成し、SODAマッピングを作成し、シャーディング・キー列の移入をトリガーし、アプリケーションからシャード表にアクセスする方法をJavaおよびPythonコード・サンプルで示しています。

JSONコレクション用のシャード表の作成

SODA ID以外のシャーディング・キーを使用するシャード表を作成するには、次のようにします。

  1. シャード・カタログにSharding/SODAユーザーとして接続します。
  2. SHARD DDLを有効にします。
  3. 表領域セットを作成します。
  4. 次の例に示すように、CREATE SHARDED TABLEを実行します。

次の例では、顧客プロファイル・ドキュメント(CUSTPROFILE)のJSONコレクション用のシャード表(Customers)を作成します。

SODA ID (ID)の列によってJSONエントリが識別されます。SODAを使用してJSONエントリを表に作成すると、アプリケーションによってID列に一意の値が移入されます。

シャーディング・キー列(ZIP)は、JSONドキュメントから抽出された郵便番号値です。

その他の列は、SODAによって基礎となるコレクションを保持する表が作成されたときに指定されたデフォルトの列名です。これは、SODAコレクションを作成し、作成した表を調べると自分で確認できます。

ID列自体を主キーにはできません。主キーはシャーディング・キー(この場合はZIP)であるか、シャーディング・キーを含んでいる必要があります。アプリケーションの例では、IDとZIPの両方を使用して、シャード・データを操作します。前述の例では、ZIPは単独で一意の値にならないため、主キーはシャーディング・キーおよびSODA ID (ZIP、ID)で構成されます。

Oracle 21cでは、(ZIP, ID)または(ID, ZIP)を組み合せた主キーとして使用できます。通常、このテーブルへのアクセスは、組合せではなく、これらの値に対して個別にアクセスする必要があります。IDおよび顧客の問合せを検索するこれらの例のSODAアクセスは、JSONフィールド(この場合はZIP)を使用している可能性があるため、いずれの場合も個々の索引を作成します。

適切なシャーディング・キーの選択は、使用状況とアプリケーションの要件によって異なります。一意のシャーディング・キー(顧客IDなど)を使用できますが、その場合は、SODA IDを使用してシャーディング・キーを格納することもできます。

シャード表の作成: システム管理

/* Enable shard DDL */
ALTER SESSION ENABLE SHARD DDL;

/* Create a tablespace set */
CREATE TABLESPACE SET TSP_SET_1 USING TEMPLATE
 (datafile size 100m autoextend on next 10M maxsize unlimited
 extent management local segment space management auto); 

/* Create the sharded table */
CREATE SHARDED TABLE CUSTOMERS
(
"ID" VARCHAR2(255) NOT NULL,
"CREATED_ON" timestamp default sys_extract_utc(SYSTIMESTAMP) NOT NULL,
"LAST_MODIFIED" timestamp default sys_extract_utc(SYSTIMESTAMP) NOT NULL,
"VERSION" varchar2(255) NOT NULL,
"ZIP" VARCHAR2(60) NOT NULL,
"CUSTPROFILE" JSON,
PRIMARY KEY (ID,ZIP))
TABLESPACE SET TSP_SET_1
PARTITION BY CONSISTENT HASH (ZIP) PARTITIONS AUTO;
シャード表の作成: ユーザー定義

必要なすべての表領域およびシャード領域が作成されていることを確認します。

/* Enable shard DDL */
ALTER SESSION ENABLE SHARD DDL;

/* Create the sharded table */
CREATE SHARDED TABLE CUSTOMERS
(
"ID" VARCHAR2(255) NOT NULL,
"CREATED_ON" timestamp default sys_extract_utc(SYSTIMESTAMP) NOT NULL,
"LAST_MODIFIED" timestamp default sys_extract_utc(SYSTIMESTAMP) NOT NULL,
"VERSION" varchar2(255) NOT NULL,
"ZIP" VARCHAR2(60) NOT NULL,
"CUSTPROFILE" JSON,
PRIMARY KEY (ID,ZIP))
PARTITION BY RANGE (ZIP)
(PARTITION p1 VALUES LESS THAN ('50000') TABLESPACE ts1,
PARTITION p2 VALUES LESS THAN ('99999') TABLESPACE ts2)

シャード表でのマップされたSODAコレクションの作成

マップを作成して、シャード表を操作する際に使用する列をSODAに認識させ、シャード表をコレクションのリストに追加します。

プロシージャを実行してマップを作成できますが、このプロシージャは、シャード・データベース内のすべてのシャードで実行する必要もあります。また、このプロシージャは、将来追加されるどのシャードでも実行する必要があります。これらの要件はいずれも、シャーディング固有のPL/SQLプロシージャsys.exec_shard_plsql()を使用して達成できます。

すべてのシャードにわたってSODAマップを作成するには、次のようにします。

Sharding/SODAユーザーとして、SHARD DDLを有効にして、次のコマンドをシャード・カタログで実行します。シャード・カタログは、自動的に処理されるように、すべてのシャードにプロシージャを伝播します。

create or replace procedure COLLECTION_PROC_CUSTOMERS AS
METADATA varchar2(8000);
COL SODA_COLLECTION_T;
begin
METADATA := '{"tableName":"CUSTOMERS",
"keyColumn":{"name":"ID"},
"contentColumn":{"name":"CUSTPROFILE","sqlType":"JSON"},
"versionColumn":{"name":"VERSION","method":"UUID"},
"lastModifiedColumn":{"name":"LAST_MODIFIED"},
"creationTimeColumn":{"name":"CREATED_ON"},
"readOnly":false}';
 -- Create a collection using "map" mode, based on
 -- the table you've created above and specified in 
 -- the custom metadata under "tableName" field.
COL := dbms_soda.create_collection('CUSTOMERS',METADATA,DBMS_SODA.CREATE_MODE_MAP);
end ;
/
 
exec sys.exec_shard_plsql('app_schema.collection_proc_customers()',4+1);

keyColumnはIDであり、コレクションを挿入および取得するためにSODAで使用されるキーです。マッピングでSODAが使用していないため、ZIP列への参照はありません。

この時点で、CREATE COLLECTIONコマンドを実行したかのように新しいコレクションが作成されました。

いくつかPL/SQLを実行してコレクションを表示できます。シャード・カタログで次のコマンドを実行し、Customers表が出力に表示されていることを確認します。

SET SERVEROUTPUT ON
DECLARE
l_coll_list SODA_COLLNAME_LIST_T;
BEGIN
l_coll_list := DBMS_SODA.list_collection_names;
 
IF l_coll_list.COUNT > 0 THEN
FOR i IN 1 .. l_coll_list.COUNT LOOP
DBMS_OUTPUT.put_line(i || ' : ' || l_coll_list(i));
END LOOP;
END IF;
END;
/
1 : CUSTOMERS
 
PL/SQL procedure successfully completed.
 
SQL>

シャーディング・キーを移入するためのトリガーの作成

SODAは、ドキュメントを挿入または更新すると、コレクション・メタデータ(ID、CUSTPROFILE、LAST_MODIFIED、CREATED_ONおよびVERSION)で説明されている基礎となる表の列を自動的に移入します。ただし、ZIP列にも移入する必要があり、値はJSONドキュメント内から取得する必要があります。これは、トリガーを使用して実行します。

これはBEFOREトリガーであるため、その列が主キーであっても列に移入できます。

シャード・カタログで、アプリケーション・スキーマ・ユーザーとして次の文を実行します。プロシージャsys.exec_shard_plsqlは、すべてのシャードおよび今後のすべてのシャードでも実行されるようにします。

alter session enable shard ddl
 
create or replace procedure COLLECTION_BF_ZIP_CUSTOMERS AS
begin
EXECUTE IMMEDIATE 'alter session enable shard operations';
EXECUTE IMMEDIATE q'%
Create or Replace TRIGGER CUST_BF_TRIG
BEFORE INSERT or UPDATE on CUSTOMERS
FOR EACH ROW
begin
:new.ZIP := JSON_VALUE(:NEW.CUSTPROFILE, '$.ZIP' error on error error on empty);
end;
%';
end;
/

exec sys.exec_shard_plsql('app_schema.collection_bf_zip_customers()',4+1+2);

前述の例では、ZIPはJSONドキュメントのトップレベル・フィールドであると想定されています。ネストされたフィールド(たとえば、ADDRESSフィールドの下)に値がある場合は、フィールド階層を含める('$.ADDRESS.ZIP'など)必要があります。

コード・サンプル

「シャーディング・キーとしてのJSONフィールドの使用」のJavaおよびPythonコード・サンプルは、シャード・データベース環境でSODAを使用してJSONオブジェクトを実際に操作する方法を示しています。

これらの例では、シャード・キーを使用してシャードに接続し、新しいドキュメントを挿入しています。

シャード・データベース環境でSODAを使用する場合は、シャード・カタログを使用せずに特定のシャードに接続して新しいドキュメントを作成する必要があることに注意してください。

Javaコード・サンプル

次のJavaコード・サンプルは、データがJSONフィールド(この例では郵便番号)によってシャーディングされるコレクションにJSONドキュメントを挿入する方法を示しています。

import java.sql.Connection;
import java.util.Properties;
import java.util.List;
 
// SODA specific imports
import oracle.soda.rdbms.OracleRDBMSClient;
import oracle.soda.OracleDatabase;
import oracle.soda.OracleCursor;
import oracle.soda.OracleCollection;
import oracle.soda.OracleDocument;
import oracle.soda.OracleException;
 
// Sharding and UCP imports
import oracle.jdbc.OracleShardingKey;
import oracle.jdbc.OracleType;
import oracle.jdbc.pool.OracleDataSource;
import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;
 
 
/*
* The sample demonstrates connecting to a Sharded Database using
* Oracle JDBC driver and UCP as a client side connection pool.
*/
public class QuickInsertShardJSONField {
 
 public static void main(String args[]) throws Exception {
 
// TNS_ADMIN - Should be the path where the tnsnames.ora file resides
// dbshard_rw - It is the TNS alias present in tnsnames.ora.
// Note that the connection is to the Shard Director (GSM) and the service name is the shard RW service
final String DB_URL="jdbc:oracle:thin:@dbshard_rw?TNS_ADMIN=/home/opc/dbhome/";
 
// Update the Database Username and Password to the Shard User
final String DB_USER = "app_schema";
String DB_PASSWORD = "<user_password>" ;
 
 
// Get the PoolDataSource for UCP
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
 
// Set the connection factory first before all other properties
pds.setConnectionFactoryClassName(OracleDataSource.class.getName());
pds.setURL(DB_URL);
pds.setUser(DB_USER);
pds.setPassword(DB_PASSWORD);
pds.setConnectionPoolName("JDBC_UCP_POOL");
 
// Default is 0. Set the initial number of connections to be created
// when UCP is started.
pds.setInitialPoolSize(10);
// Default is 0. Set the minimum number of connections
// that is maintained by UCP at runtime.
pds.setMinPoolSize(10);
// Instead of Max Pool Size, we can set the number of max connections per shard
pds.setMaxConnectionsPerShard(20);
 
// We cannot get the connection until we have the Shard key which is part of the SQL
//We first set the sharding key which in our case is the value of the ZIP code field
String shardingKeyVal="94065";
 
// Now we build the connection using this shard key
OracleShardingKey sdkey = pds.createShardingKeyBuilder().subkey(shardingKeyVal, OracleType.VARCHAR2).build();
System.out.println("Initiating UCP and Creating Connection...");
Connection conn = pds.createConnectionBuilder().shardingKey(sdkey).build();
 
// Enable the SODA Shared Metadata cache
Properties props = new Properties();
props.put("oracle.soda.sharedMetadataCache", "true");
OracleRDBMSClient cl = new OracleRDBMSClient(props);
 
// Get a DB Connection
OracleDatabase db = cl.getDatabase(conn);
 
// Print all the Collections in this DB
List<String> names =  db.admin().getCollectionNames();
for (String name : names)
 System.out.println ("Collection name: " + name);
 
// Open up the CUSTOMERS Collection
OracleCollection col = db.openCollection("CUSTOMERS");
 
//We do not provide an SODA ID column.
//This is provided by SODA when the document is created
// Note that the ZIP field MUST match what we have specified as the key
OracleDocument cDoc = db.createDocumentFromString("{\"name\": \"Matilda\", \"State\": \"CA\", \"ZIP\":\"94065\"}");
 
// Insert the document above
OracleDocument insertedDoc = col.insertAndGet(cDoc);
 
// Get the document key
String dockey = insertedDoc.getKey();
 
// Get the document back by key
// We are still connected to the same shard
OracleDocument doc = col.find().key(dockey).getOne();
String content = doc.getContentAsString();
System.out.println("Retrieved content is: " + content);
 
// We are done, so close the connection to the shard
conn.close();
 
// At this point we could open up a new shard connection using a different sharding key
 
 
 }} // End of QuickInsertShardJSONField
Pythonコード・サンプル

Pythonのこのコード・サンプルは、シャード・データベース環境でSODAを使用してJSONオブジェクトを実際に操作する方法を示しています。

このサンプル・コードを環境で使用するには、Python用のcx_Oracleモジュールをインストールする手順に従います: https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html

この例では、シャーディング・キーを使用してシャードに接続し、新しいドキュメントを挿入します。

シャード・データベース環境でSODAを使用する場合は、シャード・カタログを使用せずに特定のシャードに接続して新しいドキュメントを作成する必要があることに注意してください。

# import the cx_Oracle module for Python
import cx_Oracle
 
# Create a connection pool that will be used for connecting to all shards
# The components of the dsn are hostname (shard director),
# port (usually 1522), global service (created using GDSCTL)
# We also enable SODA metadata caching
dsn=cx_Oracle.makedsn("shard_director_host",1522,service_name="service_name")
pool=cx_Oracle.SessionPool("app_schema","password",dsn,soda_metadata_cache=True)

# Connect to a specific shard by using the shard key, a ZIP code. which in this
# example is set explicitly as '94065', but this might be passed in or part of a loop 
# You must know beforehand whether you are creating or working with a document
# with a specific ZIP code value.
connection=pool.acquire(shardingkey=["94065"])
 
# set autocommit and open the CUSTOMERS collection
connection.autocommit = True
soda = connection.getSodaDatabase()
collection = soda.openCollection("CUSTOMERS")
 
# Insert a document
# A system generated SODA key is created by default.
content = {'name': 'Matilda', 'STATE': 'CA', 'ZIP': '94065'}
doc = collection.insertOneAndGet(content)
 
# The SODA key can now be used to work with this document directly
# We can retrieve it immediately
key = doc.key
print('The key of the new SODA document is: ', key)
 
# Fetch the document back by this same SODA key. 
# This only works because we are still connected to the same shard
doc = collection.find().key(key).getOne()
content = doc.getContent()
print('Retrieved SODA document dictionary is:')
print(content)
 

# Next, add another customer. We are in the shard containing 94065,
# so we can add a customer with the same ZIP code '94065'
content = {'name': 'Mildred', 'STATE': 'CA', 'ZIP: '94065'}
doc = collection.insertOneAndGet(content)
 
# Now do a query.
# It is important to note that this query is ONLY executed within this one shard,
# the shard which contains the part of the sharded table with 94065 ZIP codes.
# In other words, the actual query has the additional bound of customers whose
# names start with 'M' in 94065
# and any other ZIPs stored on this shard. This is unlikely to be a useful query
# for system-managed sharding.
documents = collection.find().filter({'name': {'$like': 'M%'}}).getDocuments()
for d in documents:
    content = d.getContent()
    print(content["name"])
 
# After you have finished, release this connection back into the pool
pool.release(connection)
 
# If you want to add or work with more customers with a different
# shard key start with another connection
# For example: connection=pool.acquire(shardingkey=["10012"]) and so on.
 
# When you are completely finished working with customers, shut down the pool.
pool.close()

このコード・サンプルは、マルチシャード問合せを実行して、すべてのシャードで名前がMで始まるすべての顧客名を返す方法を示しています。

import cx_Oracle
 
# Create an unpooled connection to the shard catalog
# The connect string connects to the shard director, but uses the catalog service,
# e.g. GD$catalog.oradbcloud
connection = cx_Oracle.connect("app_schema","password","db_connect_string")

# Open the CUSTOMERS collection
connection.autocommit = True
soda = connection.getSodaDatabase()
collection = soda.openCollection("CUSTOMERS")
  
# Now query the collection
# It is important to note that this is a query across ALL shards
# In other words, you will get ALL users whose name starts with M across ALL Zip codes
documents = collection.find().filter({'name': {'$like': 'M%'}}).getDocuments()
for d in documents:
    content = d.getContent()
    print(content["name"])
  
#Close the connection
connection.close()

SODAによるシャーディングに関する追加情報

パフォーマンス・チューニング

メタデータおよび文キャッシング

すべての実装について、文キャッシュを接続プールでオンにする必要があります。これにより、データベースへの不要なラウンド・トリップを回避できます。

SODAメタデータ・キャッシュを有効にするには:

  • Javaの場合:

    Properties props = new Properties();
    props.put("oracle.soda.sharedMetadataCache", "true");
    OracleRDBMSClient cl = new OracleRDBMSClient(props);

    詳細は、SODAコレクションのメタデータ・キャッシュに関する項を参照してください。

  • Pythonの場合:

    # Create the session pool
    pool = cx_Oracle.SessionPool(user="hr", password=userpwd,
                   dsn="dbhost.example.com/orclpdb1",soda_metadata_cache=True)

    詳細は、SODAメタデータ・キャッシュの使用に関する項を参照してください

スレッド

リソースの最適な使用のために、OracleClientのインスタンス化はスレッド間で共有されるため、1回のみ必要です。

これから取得したオブジェクト(OracleDatabaseなど)および結果としてOracleCollectionはスレッドセーフではなく、新規リクエストの作成時にインスタンス化する必要があります。

索引の作成と管理

シャード・キーは主キーの一部である必要があります。追加の索引の作成に制限はありません。

索引の作成および管理に関するSODAドキュメントで提供されるすべてのガイドラインは引き続き適用されます。

シャードのスケール・アウト

データベース構成に新しいシャードを追加すると、SODAメタデータおよびトリガーを含むすべてのDDLが新しいシャードで自動的に使用可能になります。

SODA/JSONシャーディングに追加構成は必要ありません。