9 シャード・データベースへのデータの移行

非シャード・データベースからOracle Shardingシャード・データベースにデータを移行する際には、ここで説明する方法を使用してください。

ここに示すデータのロード方法は、シャード・データベースに移行する時点で、非シャードOracleデータベースを使用していることを想定しています。また、ここで提案する方法は、その他のデータベース・システムからのデータの移行や、新規のデータベース・ユーザーにも適用されます。

シャード・データベースへのデータの移行について

Oracle Shardingソフトウェアのインストール後に、シャード・データベースを構成して作成すると、シャード・データベースにデータを移行できるようになります。

次に、シャード・データベース環境に移行する際のステップの概要を示します。

  1. シャード・データベース・スキーマを設計して作成します。
  2. データを移行します。
  3. アプリケーションを移行します。

関連項目:

シャーディングに対するアプリケーションの適合性: シャード・データベース・アプリケーションへの移行に関する制約について理解してください。

シャード・データベースへのデータのロードに関する一般的なガイドライン

非シャード・データベースからシャード・データベースに移行するときには、非シャード表のデータをシャード表と重複表に移動する必要があります。非シャード表から重複表にデータを移動するときには複雑なことはありませんが、非シャード表からシャード表にデータを移動するときには特別な注意が必要です。

重複表へのデータのロード

重複表へのデータのロードは、既存のデータベース・ツール(データ・ポンプ、SQL LoaderまたはプレーンSQL)を使用して実行できます。データのロードには、シャード・カタログ(コーディネータ)データベース・ノードを使用する必要があります。つまり、シャード・カタログ・データベースには重複表のすべての内容が格納されることになります。重複表の内容はマテリアライズド・ビューを使用してデータベース・シャードに完全にレプリケートされるため、重複表のロードにかかる時間は同じデータを非シャード表にロードするよりも長くなる場合があります。

図9-1 重複表のロード



シャード表へのデータのロード

シャード表をロードする場合は、各データベース・シャードがデータ・セット全体のサブセット(シャード)を個別に収容するため、データを分割(シャッフル)してから各サブセットを特定のシャードにロードする必要があります。

図9-2 シャード・カタログを使用したシャード表のロード



データベース・シャードのすべてにサブセットのデータをロードするには、Oracle Data Pumpユーティリティを使用する必要があります。次の2つのオプションについて考えてみます。

  • 上の図に示すように、シャーディング・コーディネータ(カタログ)ノード経由でデータをロードします。
  • 次の図に示すように、データベース・シャードにデータを直接ロードします。

図9-3 データベース・シャードへのシャード表の直接ロード



シャーディング・コーディネータを使用してシャード・データベースにデータをロードすると、シャーディング・コーディネータ(カタログ)ノードで実行される分割ロジックと、データをシャードにプッシュする追加のオーバーヘッドにより、非シャード表にデータ・セット全体をロードするよりも時間がかかります。

データベース・シャードに直接データをロードすると、各シャードが個別にロードされるためロード時間は大幅に短くなります。つまり、各シャードでデータ・ポンプを実行することで、データ・セット全体のうち最大のサブセットのシャードをロードするために必要な期間内でデータ・ロード操作を完了できます。一般に、このロード時間は、データ・セット全体を非シャード・データベースにロードする場合に必要な時間をシャード・データベース内のシャード数で除算することで概算できます。

Oracle Data Pumpユーティリティに依存してロード・データ・セットを個別のサブセットに分割するかわりに、アプリケーションに分割(シャッフル)ロジックを統合するオープンのソース・シャード分割ライブラリが使用できます。シャード分割ライブラリのソース・コードと使用例は、Oracle Sharding Tools Library (https://github.com/oracle/db-sharding/)で入手できます。このシャード分割ライブラリを基にして、OracleではOracle Cloudで使用するための汎用ストリーミング・ロード・ライブラリを開発しています。現時点では、このストリーミング・ロード・ライブラリは請求した場合にのみ利用できます。

スキーマの移行

既存のデータベースをシャード・データベースに移行する前に、シャードの数とレプリケーションの計画を含めてシャード・データベースの編成方法を決定し、アプリケーションのどの表をシャード表にして、どの表を重複表にするかを決定する必要があります。シャード表については、シャーディング方法と、表ファミリでのシャード表間の親子関係を決定する必要があります。

シャード・データベース環境へのスキーマの移行については、サンプル・アプリケーションを使用して説明します。このアプリケーションはデータ・モデルによって定義され、いくつかの制約が適用されます。シャード・データベースへの移行がアプリケーションに与える影響については、サンプル・プログラム・コードを使用して分析します。次の図は、サンプル・アプリケーションのデータ・モデルを示しています

このデータ・モデルは、4つの表Customers、Orders、StockItemsおよびLineItemsで構成され、次の主キー制約が適用されます。

  • Customer.(CustNo)

  • Orders.(PONo)

  • StockItems.(StockNo)

  • LineItems.(LineNo, PONo)

このデータ・モデルでは、次の参照整合性制約が定義されます。

  • Customers.CustNo -> Orders.CustNo

  • Orders.PONo -> LineItems.PONo

  • StockItems.StockNo -> LineItems.StockNo

次のDDL文では、サンプル・アプリケーションのデータベース・スキーマ定義を作成します。

CREATE TABLE Customers (
 CustNo     NUMBER(3) NOT NULL,
 CusName    VARCHAR2(30) NOT NULL,
 Street     VARCHAR2(20) NOT NULL,
 City       VARCHAR2(20) NOT NULL,
 State      CHAR(2) NOT NULL,
 Zip        VARCHAR2(10) NOT NULL,
 Phone      VARCHAR2(12),
 PRIMARY KEY (CustNo)
);

CREATE TABLE Orders (
 PoNo       NUMBER(5),
 CustNo     NUMBER(3) REFERENCES Customers,
 OrderDate  DATE,
 ShipDate   DATE,
 ToStreet   VARCHAR2(20),
 ToCity     VARCHAR2(20),
 ToState    CHAR(2),
 ToZip      VARCHAR2(10),
 PRIMARY KEY (PoNo)
);

CREATE TABLE StockItems (
 StockNo     NUMBER(4) PRIMARY KEY
 Description VARCHAR2(20),
 Price       NUMBER(6,2)
);

CREATE TABLE LineItems (
 LineNo      NUMBER(2),
 PoNo        NUMBER(5) REFERENCES Orders,
 StockNo     NUMBER(4) REFERENCES StockItems,
 Quantity    NUMBER(2),
 Discount    NUMBER(4,2),
 PRIMARY KEY (LineNo, PoNo)
);

シャーディング・キー

シャーディングは、複数の独立したOracleデータベース間にデータを水平にパーティション化するデータベースのスケーリング手法です。データベース・リクエストは、シャーディング・キー列の値に基づいて適切なシャード・データベースにルーティングされます。シャーディング設計の目標は、単一のシャード操作が最大化されるようになり、シャード間操作が最小化または排除されるようになるシャーディング・キーを選択することです。

サンプル・アプリケーションのデータ・モデルで特定される主キーから外部キーへの機能的な依存関係に基づいて、次の表ファミリが形成されます。

  • Customers – 親表

  • Orders – 子表

  • Lineitems – 孫表

残りのStockItems表は単なる参照表で、在庫品目番号を在庫品目の説明と価格にマッピングします(StockNo -> (Description, Price))。

シャード・データベース定義には、参照パーティション化を使用する表ファミリのメンバーごとの次のDDL文と、StockItems参照表を定義する追加のDDL文が必要です。

CREATE SHARDED TABLE Customers (
 CustNo     NUMBER(3) NOT NULL,
 CusName    VARCHAR2(30) NOT NULL,
 Street     VARCHAR2(20) NOT NULL,
 City       VARCHAR2(20) NOT NULL,
 State      CHAR(2) NOT NULL,
 Zip        VARCHAR2(10) NOT NULL,
 Phone      VARCHAR2(12),
 CONSTRAINT RootPK PRIMARY KEY (CustNo)
)
PARTITION BY CONSISTENT HASH (CustNo)
PARTITIONS AUTO
TABLESPACE SET ts1
;

CREATE SHARDED TABLE Orders (
 PoNo       NUMBER(5) NOT NULL,
 CustNo     NUMBER(3) NOT NULL,
 OrderDate  DATE,
 ShipDate   DATE,
 ToStreet   VARCHAR2(20),
 ToCity     VARCHAR2(20),
 ToState    CHAR(2),
 ToZip      VARCHAR2(10),
 CONSTRAINT OrderPK PRIMARY KEY (CustNo, PoNo)
 CONSTRAINT CustFK Foreign Key (CustNo) REFERENCES Cusomters (CustNo)
)
PARTITION BY REFERENCE (CustFK)
;

CREATE DUPLICATED TABLE StockItems (
 StockNo     NUMBER(4) PRIMARY KEY
 Description VARCHAR2(20),
 Price       NUMBER(6,2)
);

CREATE SHARDED TABLE LineItems (
 LineNo      NUMBER(2) NOT NULL,
 PoNo        NUMBER(5) NOT NULL,
 StockNo     NUMBER(4) REFERENCES StockItems,
 Quantity    NUMBER(2),
 Discount    NUMBER(4,2),
 CONSTRAINT LinePK PRIMARY KEY (CustNo, LineNo, PoNo)
 CONSTRAINT LineFK FOREIGN KEY (CustNo, PoNo) REFERENCES Orders (CustNo, PoNo)
)
PARTITION BY REFERENCE (LineFK)
;

前述のシャード・データベースDDLと元の(非シャード)データベースの表を比較すると、次の構造的な表の変更があることがわかります(太字のイタリック体で示しています)。

  • 表ファミリの表に対するCREATE TABLE文には、追加のSHARDEDキーワードが含まれています。

  • 参照表のCREATE TABLE文には、追加のDUPLICATEDキーワードが含まれています。

  • 表ファミリのすべての表には、シャーディング・キー列CustNoが主キーの先頭の列として格納されています。これはシャーディング固有の非正規化であり、先頭のコンポーネントとして直接の親キー(レベル・キーとも呼ばれます)が含まれるように、表ファミリ階層の各レベルで複合主キーを拡張しています。

  • シャード表は、シャーディング・キー列でパーティション化(PARTITIONED BY)されます。この例の場合は、ファミリのルート表がCONSISTENT HASHでパーティション化されています。このパーティション化スキーマは、BY REFERENCE (参照パーティション化)によって階層の下位レベルに伝播されます。CONSISTENT HASHによるデータのパーティション化は、システム管理のシャーディング方法と呼ばれます(これは、ユーザー定義のシャーディングと対になる用語です)。

  • システム管理のシャーディングでは、シャード表に表領域セットが定義されます。表領域の最初のセットは、SHARDED表に使用されます。表領域セットは、1つ以上のシャード表と索引の論理的な記憶域単位として、シャード・データベースで使用されるものです。表領域セットは、シャード領域内のシャード間で分散される複数の表領域で構成されます。表領域は、データベースによって表領域セットとして自動的に作成されます。表領域の数は自動的に決定され、対応するシャード領域のチャンクの数と等しくなります。

    シャード表のCREATE TABLESPACE SET tbs1;

    この例では、Customers、OrdersおよびLineItemsは表領域セットのtbs1に配置されます。つまり、表ファミリの3つの表に対応するパーティションは、同じ表領域セットtbs1に格納されるということです(参照によるパーティション)。ただし、各表に個別の表領域セットを指定できます。

ソース・データベースの準備

シャード・データベース・スキーマへの移行を円滑に進めるために、ターゲットのシャード・データベースの表定義と一致するように、ソースの非シャード・データベースを変更できます。

理想としては、ターゲット・シャード・データベースの表定義(表名、列名およびそのデータ型)と、ソース・データベースの表定義を完全に同じにします。ただし、シャード・データベースへの移行の一環として、シャード・データベースで使用する表定義の変更が必要になることがあります。その場合は、新しいシャード・データベースの表定義と一致するように、ソースの非シャード・データベースを変更できます。変更の範囲によっては、アプリケーション・コードの変更が必要になることもあります。移行プロセスの前に、ターゲットのシャード・データベース・スキーマと一致するようにソースのデータベース・スキーマを変更しておくことで、元の非シャード・データベースから新しいシャード・データベースへの中断のない移行のための条件を整えます。このような準備は、停止時間が発生したとしても、停止時間を最小にするための前提条件になります。また、サンプル・アプリケーションで示すように、移行の準備はシームレスな簡単に元に戻せるプロセスです。ソース・データベースを準備することは非常に望ましい行動ですが、必須ではありません。なんらかの理由でソース・データベースのオペレーティング環境を変更できない場合は、このトピックをスキップしてもかまいません。

ここに示すステップは、前のトピックで定義したものと同じサンプル・スキーマに従います。

サンプル・データベースを移行するために、次の例に示すようにALTER TABLEを使用して、ソース・データベースのLineItems表にシャーディング・キー列CustNoを追加する必要があります。

ALTER TABLE LineItems ADD (CustNo NUMBER(3));

この追加の列により、ソース表の行データ・レイアウトとターゲットのシャード表の目的とするレイアウトが同じになります。これで、この新しい列に一致するデータを設定する準備ができました。LineItems表の追加のシャーディング・キー列CustNoの値は、親子関係のOrdersとLineItemsの結合から導出する必要があります。

SELECT Orders.CustNo FROM Orders JOIN LineItems ON Orders.PONo = LineItems.PONo;

この例では、MERGE文を使用してCustNo列に移入しています。標準SQLを使用して、同じ目標を達成することもできます。ここに示した例では、MERGE文は次のようになります。

SQL> BEGIN
  2  MERGE INTO LineItems l
  3  USING Orders o
  4  ON (l.PONo = o.PONo)
  5  WHEN MATCHED THEN
  6  UPDATE SET l.CustNo = o.CustNo;
  7  END;
  8  /

この時点で、CustNo列に対する参照整合性が維持されることがわかります。新しい列が正しく移入されるようにするには、MERGE文の実行後に、次に示すようにNOT NULL制約を追加する必要があります。

ALTER TABLE LineItems MODIFY CustNo NOT NULL;

前述のMERGE文を実行すると、LineItems表の行レイアウトと行データが目的の状態になります。追加のCustNo列により、LineItems表が表ファミリのルート(Customers)と同じ方法でシャーディングされます。この変更は、実際の移行前の最後のアクションの1つとして使用することを検討してください。そうしない場合は、アプリケーション内で、この新しい列を維持管理する必要があります。その結果として、追加されたシャーディング・キー列の参照整合性を既存のデータベースで維持管理することも必要になります。一致するCustNo列の参照整合性制約は、次に示すようにLineItems表で定義します。

ALTER TABLE LineItems ADD CONSTRAINT LineFk FOREIGN KEY (CustNo, PONo) REFERENCES Orders (CustNo, PONo);

LineItems表の参照整合性を変更する前に、Orders表を参照する既存のFOREIGN KEY制約を削除する必要があります。この操作を安全に完了するには、DROP CONSTRAINT文と後続のADD CONSTRAINT文をALTER TABLE LineItems READ ONLY;ALTER TABLE LineItems READ WRITE;で囲むか、制約の変更中は単に表をLOCK TABLE LineItems IN SHARE MODEでロックします。

CustNo列はLineItems表の外部キー定義の一部として追加するため、Orders表の主キーの変更が必要になります。主キーを変更すると、索引の再構築が必要になるため、この作業の完了までには時間がかかる場合があります。この作業は、シャード・データベースに移行する前の一定期間、この新しいスキーマに対してアプリケーションを実行する予定がある場合にのみ意味があります。

次の例は、LineItems表にシャーディング・キーを追加した結果としてのLineItemsおよびOrdersスキーマの変更操作を示しています。LineItems表の既存の外部キー制約を削除して、Orders表の主キー制約を削除する前に、次に示すように、それぞれの制約名を取得しておく必要があります。

SQL> SELECT a.table_name, a.column_name, a.constraint_name
  2  FROM ALL_CONS_COLUMNS A, ALL_CONSTRAINTS C
  3  WHERE A.CONSTRAINT_NAME = C.CONSTRAINT_NAME
  4  and a.table_name='LINEITEMS' and C.CONSTRAINT_TYPE = 'R';

LINEITEMS
PONO
SYS_C009087

LINEITEMS
STOCKNO
SYS_C009088

SQL> SELECT cols.table_name, cols.column_name, cols.constraint_name, cols.position
  2  FROM all_constraints cons, all_cons_columns cols
  3  WHERE cons.constraint_type = 'P'
  4  AND cons.constraint_name = cols.constraint_name
  5  AND cols.table_name = 'ORDERS'
  6  ORDER BY cols.table_name, cols.position;

ORDERS
ORDER_ID
ORDER_PK
         1
ORDERS
PONO
SYS_C009148
         1

SQL> ALTER TABLE LineItems READ ONLY;

Table altered.

SQL> ALTER TABLE Orders READ ONLY;

Table altered.

SQL> ALTER TABLE LineItems DROP CONSTRAINT SYS_C009087;

Table altered.

SQL> ALTER TABLE ORDERS DROP CONSTRAINT SYS_C009148;

Table altered.

SQL> ALTER TABLE ORDERS ADD CONSTRAINT order_pk PRIMARY KEY (CustNo, PONo);

Table altered.

SQL> ALTER TABLE LineItems ADD CONSTRAINT LineFk FOREIGN KEY (CustNo, PONo) REFERENCES Orders (CustNo, PONo);

Table altered.

SQL> ALTER TABLE Orders READ WRITE;

Table altered.

SQL> ALTER TABLE LineItems READ WRITE;

Table altered.

同様に、次に示すように、先頭の列としてCustNoを含めることで完全なレベル・キーになるようにLineItems表のPRIMARY KEY定義を拡張する必要があります。

ALTER TABLE LineItems ADD CONSTRAINT LinePK PRIMARY KEY (CustNo, PONo, LineNo);

繰り返しになりますが、新しい制約を導入する前に、既存のPRIMARY KEY制約を削除する必要があります。データ整合性を維持するために、前述の2つのトランザクション分離戦略のいずれかを使用して、PRIMARY KEY制約とFOREIGN KEY制約を変更します。次の例では、LineItems表が制約の変更の実行中にロックされます。その後、COMMITによってロックが解放されます。

SQL> SELECT cols.table_name, cols.column_name, cols.constraint_name, cols.position
  2  FROM all_constraints cons, all_cons_columns cols
  3  WHERE cons.constraint_type = 'P'
  4  AND cons.constraint_name = cols.constraint_name
  5  AND cols.table_name = 'LINEITEMS'
  6  ORDER BY cols.table_name, cols.position;

LINEITEMS
LINENO
SYS_C009086
         1
LINEITEMS
PONO
SYS_C009086
         2

SQL> LOCK TABLE LineItems IN SHARE MODE;

Table(s) Locked.

SQL> ALTER TABLE LINEITEMS DROP CONSTRAINT SYS_C009086;

Table altered.

SQL> ALTER TABLE LineItems ADD CONSTRAINT LinePK PRIMARY KEY (CustNo, PONo, LineNo);

Table altered.

SQL> COMMIT;

Commit complete.

参照整合性に関連する変更はオプションです。この変更案によって、ソース・データベースはターゲットのシャード・データベースとほとんど同じ状態になります。これにより、移行プロセスが容易になります。

参照整合性は、適用できないことや、作成することが望ましくないこともあります。その場合は、参照パーティション化を定義できません。そうした状況では、かわりにPARENT句を使用できます。

最後に、LineItems表の追加のCustNo列は、SELECT * FROM LineItemsなどの既存の問合せに影響する場合があります。この問題を回避するために、次に示すように、CustNo列を不可視に変更できます。

SQL> ALTER TABLE LineItems MODIFY CustNo INVISIBLE;

ソース・データベース表に対してこれらの変更を実施することで、既存のサンプル・データベースの移行のための準備が整います。

ターゲット・シャード・データベースの準備

シャード・データベースへのデータの移行を開始する前に、設計に応じてシャード・データベース・スキーマを作成する必要があります。

非シャード・データベース環境からシャード・データベース環境へのデータの移行は、次の2つの方法で実行できます。

  • 2段階アプローチ: データ移行のためのより慎重な増分アプローチです。1つのシャードのみでシャード・データベースを作成します。シャード・データベースが1つのシャードに格納されているかぎり、アプリケーション・コードの変更なしに関連するごくわずかな変更で、アプリケーションやデータベース・メンテナンスの手順を使用できます。つまり、シャード・データベースは、シャード環境に移行する時点の非シャード・データベースと同じように動作するということです。その後で、シャーディングに対応するようにアプリケーションと操作手順を変更して、データベースを適切な数のシャードにスケール・アウトする作業を進めます。
  • 1段階アプローチ: 最初に適切な数のシャードでシャード・データベースを作成します。この場合、アプリケーションと操作手順は、移行する時点でシャーディング操作に対応するように完全に準備する必要があります。

より慎重な2段階アプローチを使用すると、シャード・データベース環境への移行がスムーズになりますが、かなり長い時間がかかるようにもなります。単一のシャードに対してアプリケーションを実行すると、ダイレクト・ルーティングに向けてアプリケーションを徐々に変更するための時間が得られます。シャード・ディレクタを使用して適切なシャードに接続するように既存のアプリケーションを変更した後にのみ、残りのシャードをインスタンス化できます。

プロセスの最初のステップは、シャードが1つのみのシャード・データベースを作成することです。その後で、次の各項のいずれかで提案されているように既存のアプリケーションを変更します。最後のステップで、シャード・データベースを必要なシャード数までスケールアウトします。この方法では、スケール・アウトの前に、すべてのシャードでデータ・チャンクを分割およびリバランスする機会も得られます。

この2段階の移行方法には、長い時間がかかるだけでなく、多くの領域も必要になります。すべてのデータを1つのシャードに移行する場合は、制限なしでシャード表のデータを単一のシャードに直接ロードできます。複数のシャードにスケール・アウトした後で、シャード表を適切に複数のデータベース・シャードに直接ロードする場合は、データ・ポンプ・ユーティリティを必ず使用する必要があります。重複表はカタログ・データベースに存在しているため、重複表のロードには常にカタログ・データベースを使用することになります。

1段階のアプローチと2段階のアプローチのどちらを使用するにしても、シャード・データベースにデータをロードする前に、データのエクスポートとシャード・データベース・スキーマの作成が必要になります。次の例は、ソフトウェアのインストールが完了していて、シャード用に少なくとも1つのシャーディング・カタログと1つ以上のデータベースを含むシャード・データベース環境を作成していることを想定しています。新しいシャード・カタログを作成することも、既存のシャード・カタログを使用することもできます。移行プロセスを説明するために、この手順の例では次のデータベース・インスタンスを使用します。

  • orignode: 元の非シャード・データベース・インスタンス(SID=orig)をホストするサイト

  • catnode: シャード・カタログ・データベース・インスタンス(SID=ctlg)をホストするカタログ・ノード

  • shrdnodeN: データベース・シャード・インスタンス(SID=shrdN、Nは1、2など)をホストするシャード・ノード

  • gsmnode: シャード・ディレクタ(GSM)インスタンス(SID=gsm1)をホストするカタログ・ノード

移行の準備でソース・データベースを変更したかどうかに関係なく、移行プロセスでは、少なくともCREATE SHARDED TABLE文とCREATE DUPLICATED TABLE文が含まれているDDL定義に変更を加える必要があります。データベース・スキーマをターゲットのシャード・データベースに移行するには、ソース・データベースからDDL定義を抽出して、SHARDED表およびDUPLICATED表のメタデータを変更する必要があります。ソース・データベースからDDL文を抽出する際の便利な方法は、データ・ポンプ抽出ファイルを作成することです。その後で、次に示すように、データベース・エクスポート・ファイルに対してデータ・ポンプ・インポート・ユーティリティを使用します。

impdp uname/pwd@orignode directory=expdir dumpfile=sample.dmp sqlfile=sample_ddl.sql

この例では、impdpコマンドは、実際にはダンプ・ファイルの内容をインポートしません。そのかわりに、sqlfileパラメータによって、sample_ddl.sqlという名前のスクリプトの作成がトリガーされます。このスクリプトには、エクスポート・ダンプ・ファイル内のすべてのDDLが格納されています。このデータベース・エクスポート・ファイルは、次に示すようにして作成します。

expdp uname/pwd@orignode full=Y directory=expdir dumpfile=sample.dmp logfile=sample.log

全データベース・エクスポート・ファイルには、データベースのすべての内容(データとメタデータ)が含まれています。データベース全体のエクスポートには時間がかかることがあります。このステップを短時間で実行するために、この例に示すように、メタデータのみをエクスポートすることも、必要な表のセットが含まれる部分のみをエクスポートすることもできます。

expdp uname/pwd directory=DATA_PUMP_DIR dumpfile=sample_mdt.dmp logfile=sample_mdt.log INCLUDE=TABLE:\"IN \( \'CUSTOMERS\', \'ORDERS\', \'STOCKITEMS\', \'LINEITEMS\' \) \" CONSISTENT=Y CONTENT=METADATA_ONLY

このようにエクスポートをトリミングして、時間がかかることがあるデータ・ポンプ・プロセスなしに、データベース・メタデータの一貫したイメージを効率的に取得します。それでも、テキスト形式でDDL文を取得して、シャード・データベース・スキーマ設計で要求されるようにDDLを変更する必要があります。データベース全体をエクスポートする場合は、データをロードするための入力としても使用することになります。

データ・ポンプは、データを転送するための安全な方法を提供します。データベース管理者は、次に示すように、データベース・ユーザーにデータベース・エクスポート・ディレクトリへのアクセスに必要な権限を付与する必要があります。

CREATE OR REPLACE DIRECTORY expdir AS ‘/some/directory’; 
GRANT READ, WRITE ON DIRECTORY expdir TO uname;
GRANT EXP_FULL_DATABASE TO uname;

全データベース・エクスポートでは、データベース管理者がユーザーunameEXP_FULL_DATABASEロールを付与する必要があります。表レベルのエクスポートには、追加のロールは必要ありません。データ・ポンプ・ユーティリティの詳細は、後述の関連項目に示したデータベース・ユーティリティのドキュメントを参照してください。

ターゲット(シャード)データベースの行レイアウトと一致するようにソース(非シャード)データベースを変更した場合は、全データベースまたは表レベルのエクスポートは必要ありません。データは、中間ダンプ・ファイルなしでそのまま効率的に転送できます。

シャード・データベース・スキーマを完成させたら、データベース管理者の資格証明を使用して、シャーディング・カタログ・データベース(ctlg)に対して準備したDDLを実行します。すべてのDDL文をSHARD DDL設定が有効になっているセッションで実行して、すべてのDDL文がカタログ・データベース(ctlg)からシャード・データベース(shrd1、2、...、N)に伝播されるようにする必要があります。

ALTER SESSTION ENABLE SHARD DDL;

シャード表と重複表が定義されていると、シャード・データベースはデータ・ロードの準備が整っていることになります。データのロード前に、GDSCTL VALIDATEコマンドを使用してシャーディング構成を検証することをお薦めします。

gdsctl validate

検証に成功すると、シャード・データベースはデータ・ロードの準備が整っていることになります。不整合やエラーが見つかった場合は、GDSCTLコマンドのSHOW DDLおよびRECOVERを使用して問題を修正する必要があります。

データ・ポンプ・エクスポート・ユーティリティのファイルは、デフォルトで表ごとの一貫性が確保されます。エクスポート内のすべての表に同じ時点での一貫性を持たせる場合は、FLASHBACK_SCNパラメータまたはFLASHBACK_TIMEパラメータを使用する必要があります。「特定」の時点での一貫性があるデータベース・エクスポート・ファイルを用意することをお薦めします。これは、非シャード・データベースからシャード・データベースへの中断のない移行を選択した場合、つまり、移行中にも継続的なデータベース操作を提供する場合は特に重要です。データ・ポンプを使用した継続的な操作時のシャード・データベースへの移行は、Oracle GoldenGateによって補完されます。一貫性のあるデータベース・スナップショットを生成するエクスポート・コマンドは、次のようになります。

expdp uname/pwd@orignode full=Y directory=expdir dumpfile=sample.dmp logfile=sample.log CONSISTENT=Y FLASHBACK_TIME=SYSTIMESTAMP

一貫性のあるスナップショット・データベース・イメージには、追加のパラメータCONSISTENTまたはFLASHBACK_TIMEが必要です。このコマンドを実行すると、CONSISTENTFLASHBACK_TIMEのどちらのパラメータも意味が同じになることがわかります。次に示すように、タイムスタンプがシステム変更番号(SCN)に変換される点に注意してください。

SQL> SELECT TIMESTAMP_TO_SCN(SYSTIMESTAMP) FROM dual; 
TIMESTAMP_TO_SCN(SYSTIMESTAMP)
------------------------------
                       1559981

FLASHBACK_TIMEではなくFLASHBACK_SCNを使用すると、現在のSCNは、次に示すようにV$DATABASEから選択することで取得できます。

SQL> SELECT current_scn FROM v$database;
CURRENT_SCN
-----------
    1560005

または、次に示すように宣言することもできます。

SQL> SET SERVEROUTPUT ON
SQL> DECLARE SCN NUMBER;
  2  BEGIN
  3  SCN := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER;
  4  DBMS_OUTPUT.PUT_LINE(SCN);
  5  END;
  6  /
1560598

データベース管理者に、DBMS_FLASHBACKへのアクセス許可を求めることが必要になる場合があります。

データ・ポンプの実行は、PARALLELパラメータを使用すると高速化できます。次の例に示すように、このパラメータはDUMPFILEパラメータの%Uワイルドカードとともに使用して、複数のダンプ・ファイルを作成できるようにします。

expdp uname/pwd@orignode full=Y directory=expdir dumpfile=samp_%U.dmp logfile=samp.log CONSISTENT=Y PARALLEL=3 

前述のコマンドでは、4つのパラレル・ワーカーを使用して、接尾辞として_01、_02、_03が付いた4つのダンプ・ファイルを作成します。インポート時に同じワイルドカードを使用すると、複数の入力ファイルを参照できるようになります。前述の例では1つの出力ファイルsample.dmpが作成されましたが、並列エクスポートによって3つのダンプ・ファイルsamp_01.dmp、samp_02.dmpおよびsamp_03.dmpが作成される点に注目してください。また、並列エクスポートの経過時間は、シリアル実行(1つのダンプ・ファイル出力)の経過時間の1/3未満になります。

データの移行

1つまたは複数のシャードを含むターゲット・シャード・データベースを作成して、シャード表と重複表を定義したら、シャード・データベースへのデータの移行を開始できます。

ソースからシャード・データベースにデータを移行する前に、次に示すデータ・ロードの考慮事項を理解しておいてください。

  • 複製表とシャード表の移行の相違点

    重複表はシャード・カタログに存在していて、その表は使用可能なデータ・ロード・ユーティリティまたはプレーンSQLを使用して常にシャード・カタログ・データベースにロードされます。ただし、シャード表をロードするためのオプションは2つあります。シャード表は、シャーディング・コーディネータ(カタログ)を使用してロードすることも、データ・ポンプ・ユーティリティを使用してシャードに直接ロードすることもできます。

  • コーディネータを使用したシャード表の移行またはシャードへのダイレクト・ロード

    シャード・データベースにシャード表を直接ロードすると、同時に複数のシャードをロードできるため常に高速になります。

  • 複数のシャードへの移行または1つのシャードへの移行

    非シャード・データベースをシャードが1つのシャード・データベースに移行する場合は、アプリケーションとデータベースのメンテナンス手順に大幅な変更は必要ありません。データをシャードに分割する準備が整うまで、大幅な変更なしに現在の操作を継続できます。ただし、非シャード・データベースをシャードが複数あるシャード・データベースに移行する場合は、かなりの準備プロセスが必要になります。最も時間がかかる前提条件は、アプリケーション・ソース・コードの変更です。シャードが1つのシャード・データベースに移行する場合は、データベースを複数のシャードにスケーリングするための計画を立てる必要もあります。つまり、ある程度の時間をおいてから、複数のシャード全体にデータベース・チャンクを分散するということです。その間に、複数のシャードがあるシャード・データベースで実行するために必要なアプリケーションの変更などの作業を進めることができます。

  • 停止時間のある移行または中断のない移行

    非シャード・データベースからシャードが1つのみのシャード・データベースへの中断のない移行は、シャードが複数あるシャード・データベースへの移行よりも簡単です。その後の1つのシャードから複数のシャードへのスケール・アウトは、データベースの実行中に実施されます。

    非シャード・データベースからターゲットのマルチシャード・データベースに1段階で移行する場合は、その移行を最初にテスト環境で試してみて、テスト環境の移行に問題がないことを確認してからのみ、本番環境への移行を開始するようにしてください。

ターゲットの行データ・レイアウトがソース・データベースで準備されているかどうかに関係なく、効率的なデータ移行のための様々な方法があります。様々な条件(使用可能なディスク領域、リモート・ファイル・アクセス、ネットワーク・スループットなど)に最適な方法を選択してください。

移行時の停止時間の検討

データベースの停止時間をなくすには、移行計画にOracle GoldenGateを含める必要があります。ターゲット(シャード)データベースとソース(非シャード)データベースの同期を維持するには、Oracle GoldenGateを使用して、移行プロセス中にソース・データベースに加えられた変更を処理する必要があります。

停止時間ゼロの移行という利点に加えて、Oracle GoldenGateはシャード・データベースのアクティブ-アクティブ・レプリケーションの際にも使用できます。シャード・データベースに対してOracle GoldenGateアクティブ-アクティブ・レプリケーションを定義していると、すべてのデータ移行のアクティビティがシャード・カタログ・データベースに制限されるようになります。

シャード表への停止時間のあるデータ移行

Oracle GoldenGateを使用しない場合は、移行時に可能なかぎりデータベースが使用できる状態を保つための対策を取る必要があり、停止時間についての計画を立てる必要があります。

移行前にソースの非シャード・データベースの表を変更して、表スキーマがターゲットのシャード・データベース・スキーマと一致するようにしておくと、この移行前のステップを実行しなかった場合よりもデータ移行のプロセスが円滑に進みます。このアプローチにより、ソース・データベースの行データ・レイアウトが、ターゲット・シャード・データベースの対応するレイアウトと同じになるため、データベースの内容を直接コピーできます。

  1. データベース表からデータをエクスポートします。
    expdp uname/pwd@non_sharded_db directory=file_dir
          dumpfile=original_tables.dmp logfile=original_table.log
          INCLUDE=TABLE:\"IN \( \'CUSTOMERS\', \'ORDERS\', \'STOCKITEMS\',
          \'LINEITEMS\' \) \" CONSISTENT=Y CONTENT=DATA_ONLY

    このデータ・ポンプ・エクスポートの例は、Oracle Shardingサンプル・アプリケーションで使用される表に制限されています。SHARDED表とDUPLICATED表は、すでにサンプルで作成されているため、表のコンテンツのみをエクスポートします(DATA_ONLY)。

  2. ターゲットのデータベース・ノードでエクスポート・ファイル(original_tables.dmp)にアクセスできるようにしてから、シャード・データベースへのデータのインポートを開始します。

    このファイル(並列エクスポートの場合は複数のファイル)は、ターゲット・データベース・システムに移動することも、ネットワーク経由で共有することもできます。

  3. エクスポートが完了すると、シャード・データベースへの内容のインポートが開始できます。

    DUPLICATED表(StockItems)は、シャード・カタログを使用してロードする必要があります。次に、インポート・コマンドの例を示します。

    impdp uname/pwd@catnode:1521/ctlg directory=data_pump_dir
          dumpfile=original_tables.dmp logfile=imp.log tables=StockItems
          content=DATA_ONLY
  4. シャードを直接ロードするか、シャード・カタログを使用してシャードをロードします。

    SHARDED表(Customers、OrdersおよびLineItems)をロードするための最適な方法は、各シャード(shrd1、2、...、N)でデータ・ポンプを直接実行することです。次に、最初のシャードでのインポート・コマンドの例を示します。

    impdp uname/pwd@shrdnode:1521/shrd1 directory=data_pump_dir
          dumpfile=original_tables.dmp logfile=imp.log tables=Customers,
          Orders, LineItems content=DATA_ONLY

    または、シャード・カタログでデータ・ポンプを実行して、すべての表をロードすることもできます。次の例は、そのインポート・コマンドを示しています。

    impdp uname/pwd@catnode:1521/ctlg directory=data_pump_dir
          dumpfile=original_tables.dmp logfile=imp.log
          tables=Customers, Orders, LineItems, StockItems
          content=DATA_ONLY

シャード表への停止時間のないデータ移行

Oracle Shardingを使用すると、Oracle GoldenGateレプリケーションとデータ・ポンプ・エクスポートおよびインポート・ユーティリティが密接に統合されます。Oracle GoldenGateがソース・データベースとターゲット・シャード・データベースのどちらにも存在しない場合は、それをインストールする前に、ソース・データベースとターゲット・データベースの両方で最新リリースのOracle Databaseにデータベースをアップグレードしておく必要があります。データベースをアップグレードすると、最大限の機能が使用できるようになり、設定が簡単になります。Oracle Database 12cリリース2以降の場合は、非シャード・ソースで統合キャプチャが使用可能になり、ターゲット・シャード・データベースで統合レプリケートが使用可能になります。

データベース全体の再作成にデータ・ポンプ・エクスポートとインポートを使用すれば、エクスポートおよびインポートの実行中と実行後のデータベースの変更が、Oracle GoldenGateによって確実に移行されます。移行プロセス中にOracle GoldenGateでレプリケートする必要のあるものは、ユーザーが決定します。アプリケーションで必要としないデータベースの変更は、レプリケートしないようにしてください。

ソース・データベースとターゲット・データベースに対してOracle GoldenGateが構成されている場合は、本番移行をスケジュールする前に、ライブ・データを使用してテストすることをお薦めします。ソース・データベースがクローニングされている場合は、そのクローンを使用することで本番環境に影響を与えずに移行のテストを実行できます。

図9-5 停止時間のない移行



上の図は、データ・ポンプとOracle GoldenGateを組み合せて使用する移行プロセスです。データ移行の大部分はデータ・ポンプを使用して実行され、シャード・カタログとシャードにデータを直接移動しています。データ・ポンプ実行中のデータベースの変更は、Oracle GoldeGateのローカル証跡ファイルに収集され、シャード・カタログ・データベースに移動されます。シャード表に対する変更は、シャード・カタログ・データベースからシャードに伝播されます。

データ・ポンプの観点からは、impdpの非シャード・プロセスとimpdpのシャード・プロセスを使用して、ソース・データベース(ORIG)がシャード・カタログ(CTLG)とシャード・データベース(SHRD1、SHRD2およびSHRD3)に分割されます。impdpの非シャード・プロセスでは、重複表をシャード・カタログ・データベースに移行します。3つのimpdpシャード・パラレル・プロセスによって、シャード表がシャードSHRD1、SHRD2およびSHRD3に直接移行されます。

Oracle GoldenGateの観点からは、すべてのデータベースがExtractルート・プロセスから分岐した抽出(exsh)、ポンプ(pmsh)およびレプリケート(rpsh)プロセスのパイプラインを共有しています。

上の図に対応するobeyファイルを準備してあるとすると、ソース・データベース・ノード(orignode)からシャード・カタログ・ノード(catnode)へのOracle GoldenGateパイプラインのGGSCIターミナル・セッションは、次の例のようになります。

view params ./dirprm/add_exsh_2pumps.oby
-- add a change data extract process group named exsh 
-- exsh reads DUPLICATED and SHARDED tables from orig database redo logs
add extract exsh, tranlog, begin now
-- associate the trail file as output from exsh process group
add exttrail ./dirdat/et, extract exsh
-- add SHARDED and DUPLICATE change data extract pump process pmsh
-- pmsh copies local trail data to ctlgnode remote trail location
add extract pmsh, exttrailsource ./dirdat/et
-- associate the remote trail with pmsh
add rmttrail ./dirdat/et, extract pmsh
-- connect to the database and add table level supplemental logging for:
-- Customers, Orders, LineItems, and StockItems tables
add trandata uname.Customers
add trandata uname.Orders
add trandata uname.LineItems
add trandata uname.StockItems

パイプラインに対してGGSCI obeyコマンドを実行してから、info allを実行します。

obey ./dirprm/add_exsh_2pumps.oby
info all

非シャード・パイプラインおよびシャード・パイプラインの抽出プロセスが初期化されて、停止状態で待機していることがわかります。

…
EXTRACT STOPPED exsh …
EXTRACT STOPPED pmsh …
…

シャード表およびカタログ表のプロセス・グループ・パラメータEXTRACTを確認します。

view params exsh
-- first line must be extract followed the name
extract exsh
-- login info to get metadata
userid uname@orignode, password pwd
-- export is writing to trail info
exttrail ./dirdat/exsh
-- checkpoint time interval with source
checkpointsecs 1
-- source table
table uname.Customers
table uname.Orders
table uname.LineItems
table uname.StockItems

すべての表のPumpプロセス・グループ(pmsh)のパラメータを表示します。

view parms pmsh
-- first line must be extract followed the name
extract pmsh
-- no need to log into the database 
passthru
-- connect to remote host, write and talk to the manager there
rmthost shrdnode, mgrport 7810
-- where is the trail on remote host
rmttrail ./dirdat/rt
-- checkpoint time interval with target
checkpointsecs 1
-- tables
table uname.Customers
table uname.Orders
table uname.LineItems
table uname.StockItems

Oracle GoldenGateのPumpプロセスは、シャード・カタログ・ノード(catnode)のマネージャ・プロセスがポート番号7810を使用しているという想定で作成されています。シャード・カタログおよびシャード・データベースは別のマシンで実行されるため、これは適切な想定です。

シャード・カタログ・ノード(catnode)の定義を確認します。

view params mgr
PORT 7810
-- used by the PUMP process on the source side for the collector on the target
DYNAMICPORTLIST 8000-8010
SYSLOG NONE

シャード・カタログ・ノード(catnode)で、準備したReplicatプロセスのobeyコマンド・ファイルを確認します。

view params ./dirprm/add_rpsh.oby
-- connect to the database
dblogin userid uname@catnode, password pwd
-- add checkpoint table
add checkpointtable uname.gg_checkpoint
-- add replicat process rpsh that will convert remote trail into SQL continuously
add replicat rpsh, exttrail ./dirdat/rt, checkpointtable uname.gg_checkpoint

移行の準備で、シャード行データ・レイアウトと一致するようにソース・データベースを変更した場合、不可視の列を導入している可能性があります。不可視の列は、ReplicatプロセスのパラメータとしてMAPINVISIBLECOLUMNSを追加することで、レプリケーション・プロセス中に保持できます。

シャード・カタログ・ノード(catnode)で、obeyファイル./dirprm/add_rpsh.obyを実行します。

この時点で、システムは停止時間なしのデータ移行用に構成されます。ロードの大部分は、データ・ポンプ・エクスポート(expdp)およびインポート(impdp)データベース・ユーティリティによって実行されます。エクスポートおよびインポート・プロセス中の最終的なデータベース変更は、Oracle GoldenGateプロセスによって同期されます。Oracle GoldenGateのPumpプロセスは、データ・ポンプ・プロセスのいずれとも関係ない点に注意してください。

データ・ポンプ・エクスポートを開始する前に、Oracle GoldenGateのレプリケーション・プロセスがデータ・ポンプ・エクスポートに含まれる各表のインスタンス化コミット順序番号(CSN)で指定されている必要があります。そうするには、前述したように、expdpの実行時にCONSISTENT=YFLASBACK_SCN=scn_numを指定します。FLASHBACK_SCNは、次の文で取得できます。

SELECT current_scn from v$database;

expdpの実行にCONSISTENT=Yを指定したため、すべての表イメージが「scn_numの時点」を表すようになり、レプリケーション・プロセスは同じCSN番号(この例の場合)から開始できるようになります。

シャード・カタログ・ノードでのレプリケートに適切なGGSCIコマンドは、次のようになります。

START REPLICAT rpsh, AFTERCSN scn_num

これは、ソース・ノードでADD SCHEMATRANDATA GGSCIコマンドを使用すると、さらに簡単になります。このコマンドは、インポート時にインスタンス化CSNを使用できるようにして、システム表およびビューを移入します。これにより、データベースのコミット済バージョンを表す2つのスタンプを照合することで、Oracle GoldenGate CSNがデータ・ポンプSCNと同期されるようになります。つまり、エクスポート・プロセス用のFLASHBACK_SCNとレプリケート・プロセス用のAFTERCSNは、次に示すように自動的に定義されるということです。

ADD SCHEMATRANDATA uname PREPARESCN ALLCOLS

ADD SCHEMATRANDATAコマンドにより、ORIGデータベースの特定の'uname'スキーマに含まれるすべての表に対する現在および将来のスキーマ・レベルのサプリメンタル・ロギングを有効にして、Oracle GoldenGateが行の識別に使用するキーのスーパーセットを自動的にログ記録します。PREPARESCNパラメータは、データ・ポンプ・エクスポート(expdp)に対して、インポート(impdp)時にターゲットの各表にインスタンス化CSNを設定するアクション(GGSCI コマンドのSET_INSTANTIATION_CSN)を自動的に生成するように指示します。ALLCOLSパラメータでは、指定のスキーマに含まれる現在および将来のすべての表について、すべてのサポートされているキー列と非キー列の無条件サプリメンタル・ロギングを有効にします。このオプションでは、依存性の計算に必要なキーのロギングと、フィルタリングや競合解決などの目的に必要な列のロギングを有効にします。シャーディング関連のデータ移行は特定の表セットに制限され、それ以外のデータベースの(増分)変更はシャード・カタログに伝播されない点に十分注意してください。

ターゲット・データベースのレプリケーションは、データ・ポンプ・エクスポートの開始前に停止しておく必要があります。ターゲット・データベースは新規作成されるため、これは常に当てはまります。シャード・カタログ・ノードのレプリケート・プロセスを停止するGGSCIコマンドは、次のとおりです。

STOP REPLICAT rpsh

このコマンドでは、次回のレプリケート・プロセスの開始のために同期の状態を保持して、Oracle GoldenGateマネージャがレプリケート・プロセスを自動的に開始しないようにします。

ソース・データベースの抽出プロセスは、データ・ポンプ・エクスポートの開始前にアクティブにしておく必要があります。抽出がアクティブになっているかどうかを確認するには、INFO EXTRACTコマンドまたはSTATUS EXTRACTコマンドを使用します。次のコマンドでは、抽出プロセスを開始します(まだ開始されていない場合)。

START EXTRACT exsh, BEGIN NOW

この時点から、抽出プロセスは、シャーディング・プロセスに関係するすべての表について、ソース・データベースに対する変更を収集するため、データ・ポンプ・エクスポートを安全に開始できます。

expdp uname/pwd@orignode full=Y directory=expdir dumpfile=sample.dmp
      logfile=sample.log

エクスポート・ユーティリティexpdpのFLASHBACK_SCNパラメータが自動的に追加されていることを確認するようにしてください。expdpコマンドの出力に、「FLASHBACKでは、データベース整合性が自動的に維持されます。」というメッセージがあります。エクスポートの完了後は、シャード・カタログについて前述したように、すぐにターゲット・データベースでデータ・ポンプ・インポートを続行します。

impdp uname/pwd@catnode:1521/ctlg directory=data_pump_dir
      dumpfile=sample.dmp logfile=imp.log
      tables=Customers,Orders,LineItems,StockItems content=DATA_ONLY

この時点では、シャード・カタログ・ノードで安全にレプリケート・プロセスを開始できます。

START REPLICAT rpsh

この時点から、ソース・データベースの選択した表セット(Customers、Orders、LineItemsおよびStockItems)は、シャードのCustomers表、Orders表およびLineItems表、シャード・カタログ・データベースの重複表StockItemsと同期するようになります。レプリケート・プロセスの開始後に、そのレプリケート・プロセスに関するレポート・ファイルを確認するようにしてください。エクスポートの進行中に、レプリケート・プロセスがデータベースに存在するSCNまたはCSN番号を認識していて、そのSCN以降の変更をターゲット表に適用する必要があることがわかっていることを確認します(その確認のために、「Instantiation CSN filtering is enabled on table uname.Customers, uname,Orders, …」を見つけてください)。

アプリケーションの移行

シャード・データベースのオペレーティング環境では、アプリケーションはシャードに直接アクセスできるようになります。この機能により、本当の線形スケーラビリティが得られますが、アプリケーション・コードへのわずかな変更という少しの手間がかかります。

次の各例では、アプリケーションの移行方法を示します。この例は、移行したデータベース・スキーマとデータに対応するサンプル・アプリケーションのスケルトンです。サンプル・アプリケーションの主要部分には、オーダー管理およびレポート機能を組み込みます(JavaクラスPOManagerに含めます)。このクラスの最初の静的メソッドでは、addCustomer()メソッドを使用してCustomers表に新しい行を導入します。次に示すように、この関数には、パラメータ・リストを使用して列値が渡されます。

import java.sql.*;
import java.io.*;
import oracle.jdbc.driver.*;

public class POManager {
  public static void addCustomer (int custNo, String custName,
    String street, String city, String state, String zipCode,
    String phoneNo) throws SQLException {
      String sql = "INSERT INTO Customers VALUES (?,?,?,?,?,?,?)";
      try {
        Connection conn =
          DriverManager.getConnection("jdbc:default:connection:");
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, custNo);
        pstmt.setString(2, custName);
        pstmt.setString(3, street);
        pstmt.setString(4, city);
        pstmt.setString(5, state);
        pstmt.setString(6, zipCode);
        pstmt.setString(7, phoneNo);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  }

POManagerクラスの2番目の静的メソッドaddStockItem()では、StockItem表に行を追加します。次の例に示すように、列の値はパラメータ値として渡されます。

  public static void addStockItem (int stockNo, String description,
    float price) throws SQLException {
      String sql = "INSERT INTO StockItems VALUES (?,?,?)";
      try {
        Connection conn =
          DriverManager.getConnection("jdbc:default:connection:");
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, stockNo);
        pstmt.setString(2, description);
        pstmt.setFloat(3, price);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  }

POManagerクラスの3番目の静的メソッドenterOrder()では、Orders表に行を追加します。列値は、次に示すようにパラメータ・リストで指定します。

  public static void enterOrder (int orderNo, int custNo,
    String orderDate, String shipDate, String toStreet,
    String toCity, String toState, String toZipCode)
    throws SQLException {
      String sql = "INSERT INTO Orders VALUES (?,?,?,?,?,?,?,?)";
      try {
        Connection conn =
          DriverManager.getConnection("jdbc:default:connection:");
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, orderNo);
        pstmt.setInt(2, custNo);
        pstmt.setString(3, orderDate);
        pstmt.setString(4, shipDate);
        pstmt.setString(5, toStreet);
        pstmt.setString(6, toCity);
        pstmt.setString(7, toState);
        pstmt.setString(8, toZipCode);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  }

その次のPOManagerクラスの静的メソッドaddLineItem()では、LineItems表に行を追加します。次の例に示すように、列値はパラメータ値として渡されます。

public static void addLineItem (int lineNo, int orderNo,
    int stockNo, int quantity, float discount) throws SQLException {
      String sql = "INSERT INTO LineItems VALUES (?,?,?,?,?)";
      try {
        Connection conn =
          DriverManager.getConnection("jdbc:default:connection:");
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, lineNo);
        pstmt.setInt(2, orderNo);
        pstmt.setInt(3, stockNo);
        pstmt.setInt(4, quantity);
        pstmt.setFloat(5, discount);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  }

その次のPOManagerクラスの静的メソッドtotalOrders()では、Orders表のすべての注文の合計注文値を生成します。次に示すように、結果セットの関係はprintResult()メソッドを使用して出力されます。

public static void totalOrders () throws SQLException {
    String sql = 
      "SELECT O.PONo, ROUND(SUM(S.Price * L.Quantity)) AS TOTAL " +
      "FROM Orders O, LineItems L, StockItems S " +
      "WHERE O.PONo = L.PONo AND L.StockNo = S.StockNo " +
      "GROUP BY O.PONo";
    try {
      Connection conn =
        DriverManager.getConnection("jdbc:default:connection:");
      PreparedStatement pstmt = conn.prepareStatement(sql);
      ResultSet rset = pstmt.executeQuery();
      printResults(rset);
      rset.close();
      pstmt.close();
    } catch (SQLException e) {System.err.println(e.getMessage());}
  }

次に示すヘルパー・メソッドprintResults()は、totalOrders()メソッドによって生成された結果セットの関係を出力するために使用されます。結果セットの関係への参照がパラメータとして渡されます。

static void printResults (ResultSet rset) throws SQLException {
    String buffer = "";
    try {
      ResultSetMetaData meta = rset.getMetaData();
      int cols = meta.getColumnCount(), rows = 0;
      for (int i = 1; i <= cols; i++) {
        int size = meta.getPrecision(i);
        String label = meta.getColumnLabel(i);
        if (label.length() > size) size = label.length();
        while (label.length() < size) label += " ";
        buffer = buffer + label + "  ";
      }
      buffer = buffer + "\n";
      while (rset.next()) {
        rows++;
        for (int i = 1; i <= cols; i++) {
          int size = meta.getPrecision(i);
          String label = meta.getColumnLabel(i);
          String value = rset.getString(i);
          if (label.length() > size) size = label.length();
          while (value.length() < size) value += " ";
          buffer = buffer + value + "  ";
        }
        buffer = buffer + "\n";
      }
      if (rows == 0) buffer = "No data found!\n";
      System.out.println(buffer);
    } catch (SQLException e) {System.err.println(e.getMessage());}
  }

次に示す静的メソッドcheckStockItem()は、指定された在庫品目のすべてのオーダー、顧客および明細品目詳細を取得します。在庫品目はパラメータとして渡されます。前述のヘルパー・メソッドprintResults()は、checkStockItem()メソッドによって生成された結果セットの関係を出力するために使用されます。

public static void checkStockItem (int stockNo)
    throws SQLException {
      String sql = "SELECT O.PONo, O.CustNo, L.StockNo, " + 
        "L.LineNo, L.Quantity, L.Discount " +
        "FROM Orders O, LineItems L " +
        "WHERE O.PONo = L.PONo AND L.StockNo = ?";
      try {
        Connection conn =
          DriverManager.getConnection("jdbc:default:connection:");
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, stockNo);
        ResultSet rset = pstmt.executeQuery();
        printResults(rset);
        rset.close();
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  }

静的メソッドchangeQuantity()では、指定のオーダーおよび在庫品目の明細品目数量を更新します。次に示すように、特定のオーダー番号と在庫品目を入力パラメータとして指定します。

  public static void changeQuantity (int newQty, int orderNo,
    int stockNo) throws SQLException {
      String sql = "UPDATE LineItems SET Quantity = ? " +
        "WHERE PONo = ? AND StockNo = ?";
      try {
        Connection conn =
          DriverManager.getConnection("jdbc:default:connection:");
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, newQty);
        pstmt.setInt(2, orderNo);
        pstmt.setInt(3, stockNo);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  }

最後の静的メソッドdeleteOrder()では、Orders表から特定のオーダーと関連するすべての明細品目を削除します。次の例に示すように、削除するオーダーは入力パラメータとして指定します。

public static void deleteOrder (int orderNo) throws SQLException {
    String sql = "DELETE FROM LineItems WHERE PONo = ?";
    try {
      Connection conn =
        DriverManager.getConnection("jdbc:default:connection:");
      PreparedStatement pstmt = conn.prepareStatement(sql);
      pstmt.setInt(1, orderNo);
      pstmt.executeUpdate(); 
      sql = "DELETE FROM Orders WHERE PONo = ?";
      pstmt = conn.prepareStatement(sql);
      pstmt.setInt(1, orderNo);
      pstmt.executeUpdate(); 
      pstmt.close();
    } catch (SQLException e) {System.err.println(e.getMessage());}
  }
}

ここで、シャーディング用に変更したサンプル・アプリケーションのプログラム・コードを確認します。最初の注目点は、OracleShardingKey以降のシャーディングに関連するJavaパッケージの追加インポートです。

import java.sql.*;
import java.io.*;
import oracle.jdbc.driver.*;

//
// import sharding and related packages
//
import oracle.jdbc.OracleShardingKey;
import oracle.jdbc.OracleType;
import oracle.jdbc.pool.OracleDataSource;

// 
// Sample App: order management and reporting
// modified for sharding
//
public class POManager 
{
  // Connection factory for the sharded database used by Sample App
  private OracleDataSource ods;

  // 
  // Construct POManager class using Sharded database connection properties.
  // Use service name when connecting to sharded database, something like:
  // "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(HOST=myhost)(PORT=3216)”
  //   “(PROTOCOL=tcp))(CONNECT_DATA=(SERVICE_NAME=myservice)(REGION=east)))"
  //
  public POManager(String yourURL, String yourUser, String yourPwd) 
    throws SQLException
  {
      ods = new OracleDataSource();
      ods.setURL(yourURL);
      ods.setUser(yourUser);
      ods.setPassword(yourPwd);
 } // POManager

前述したように、POManagerクラスには、ConnectionオブジェクトのためのOracleDataSourceファクトリが含まれるようになりました。次の2つのメソッドは、接続ファクトリを使用して直接ルーティングとプロキシ・ルーティングの接続を作成する方法を示しています。getCatConn()メソッドは、シャーディング・カタログへの接続を返します。getShardConn()メソッドは、パラメータとして指定されたシャーディング・キーと一致するシャードへの接続を返します。

 //
 // Connect to the Sharding Catalog database.
 //
 public static Connection getCatConn() throws SQLException
 {
    Connection catConn = ods.getConnection();
 } // getCatConn

 //
 // Connect to Shard database using sharding key.
 //
 public static Connection getShardConn(int custNo) throws SQLException
 {
     OracleShardingKey shardKey = 
    ods.createShardingKeyBuilder().subkey(custNo, JDBCType.NUMERIC).build();
  OracleConnection shardConn = 
    ods.createConnectionBuilder().shardingKey(shardingKey);
  return shardConn;
 } // getShardConn

これらすべてのことに注意して、次に示すように、シャーディング環境用にgetShardConn()を使用してaddCustomer()メソッドをリライトします。

  //
  // Connect to Shard database to add a customer.
  //
  public static void addCustomer (int custNo, String custName,
    String street, String city, String state, String zipCode,
    String phoneNo) throws SQLException {
      String sql = "INSERT INTO Customers VALUES (?,?,?,?,?,?,?)";
      try {
        Connection conn = getShardConn(custNo);
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, custNo);
        pstmt.setString(2, custName);
        pstmt.setString(3, street);
        pstmt.setString(4, city);
        pstmt.setString(5, state);
        pstmt.setString(6, zipCode);
        pstmt.setString(7, phoneNo);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  } // addCustomer

addStockItem()では、getCatConn()を使用してシャード・カタログ・データベースに接続し、重複表StockItemsに挿入します。

//
  // Connect to Sharding Catalog to add a stock item.
  //
  public static void addStockItem (int stockNo, String description,
    float price) throws SQLException {
      String sql = "INSERT INTO StockItems VALUES (?,?,?)";
      try {
        Connection conn = getCatConn();
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, stockNo);
        pstmt.setString(2, description);
        pstmt.setFloat(3, price);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  } // addStockItem

Orders表は、Customers表の子です。Orders表に挿入するには、次の例に示すように、指定されたシャーディング・キーに基づいてシャード・データベースに接続します。

//
  // Connect to Shard database to add an order for a customer.
  //
  public static void enterOrder (int orderNo, int custNo,
    String orderDate, String shipDate, String toStreet,
    String toCity, String toState, String toZipCode)
    throws SQLException {
      String sql = "INSERT INTO Orders VALUES (?,?,?,?,?,?,?,?)";
      try {
        Connection conn = getShardConn(custNo);
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, orderNo);
        pstmt.setInt(2, custNo);
        pstmt.setString(3, orderDate);
        pstmt.setString(4, shipDate);
        pstmt.setString(5, toStreet);
        pstmt.setString(6, toCity);
        pstmt.setString(7, toState);
        pstmt.setString(8, toZipCode);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  } // enterOrder

表ファミリのルートのCustomersと、直接の子レベルのOrdersのルートについては、パラメータとしてシャーディング・キーを指定します。直接の子より下のレベルについては、完全なレベル・キーがないことがあるため、直接の親から取得する必要があります。次の例には、Order表のキー値orderNoに基づいて、Order表からシャーディング・キーcustNoを取得するヘルパー・メソッドのgetCustomerFromOrder()があります。それぞれの子表の行には、単一の親行が必要です。そのため、親を持たない子、または複数の親を持つ子に対して整合性違反の例外が発生します。

//
  // Determine which shard order is in
  //
  static int getCustomerFromOrder(int orderNo) throws SQLException
  {
     String sql = "SELECT O.CustNo FROM Orders O " +
                  "WHERE O.PONo = ?";
     int custNo;
     int rsSize = 0;
     Exception exception;
     try {
        Connection conn = getCatConn();
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, orderNo);
        PreparedStatement pstmt = conn.prepareStatement(sql);
        ResultSet rset = pstmt.executeQuery();
        while(rset.next() && rsSize < 3) {
          custNo = rs.getInt(“CustNo);
          rsSize++;
        }
        rset.close();
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
      if (rsSize == 0) {
        throw new 
          SQLIntegrityConstraintViolationException(
                                    “No matching parent level key”);
      If (rsSize == 2)
      {
        throw new
          SQLIntegrityConstraintViolationException(
                                   “More than one parent level key”);
      }
      return custNo;
  } // getCustomerFromOrder

ノート:

コードでは、シャード・データベースに含まれるシャードの数を仮定しないでください。

次の例は、リライトしたaddLineItem()メソッドを示しています。このメソッドは、元のPOManagerクラスにあったものですが、getCustomerFromOrder()ヘルパー・メソッドを使用するようになっています。特定のorderNoについて、一致するcustNo値のシャード・カタログを問い合せます。問合せのこの部分は、プロキシ・ルーティングを使用するすべてのシャードに伝播されます。このヘルパー関数は、1つのcustNo値を返します。この値に基づいて、シャードへの直接ルートを使用してLineItems表の行を挿入します。

  //
  // Get customer (parent) from the catalog, then insert into a shard!
  // 
  public static void addLineItem (int lineNo, int orderNo,
    int stockNo, int quantity, float discount) throws SQLException {
      String sql = "INSERT INTO LineItems VALUES (?,?,?,?,?,?)";
      try {
        int custNo = getCustomerFromOrder(orderNo);
        Connection conn = getShardConn(custNo);
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, custNo);
        pstmt.setInt(2, lineNo);
        pstmt.setInt(3, orderNo);
        pstmt.setInt(4, stockNo);
        pstmt.setInt(5, quantity);
        pstmt.setFloat(6, discount);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  } // addLineItem

  //
  // You’ve got whole level key handy, insert into a shard directly.
  // 
  public static void addLineItemWithinParent (int lineNo, int orderNo,
    int custNo, int stockNo, int quantity, float discount) 
    throws SQLException 
  {
      String sql = "INSERT INTO LineItems VALUES (?,?,?,?,?,?)";
      try {
        Connection conn = getShardConn(custNo);
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, custNo);
        pstmt.setInt(2, lineNo);
        pstmt.setInt(3, orderNo);
        pstmt.setInt(4, stockNo);
        pstmt.setInt(5, quantity);
        pstmt.setFloat(6, discount);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  } // addLineItemWithinParent

ノート:

ほとんどの場合、完全なレベル・キー・コンテキストがアプリケーション・コンテキストで使用可能になります。そのため、LineItems表の先頭列custNoコンポジット・レベル・キーに基づいて、シャードに直接接続する追加のメソッドaddLineItemWithinParent()を導入しています。これにより、シャード・カタログへのラウンドトリップがなくなります。ここに示したヘルパー関数getCustomerFromOrder()と同様のコストの高いシャーディング・プログラミング手法は、可能なかぎり使用しないでください。

集計問合せの大部分は、シャード・カタログ接続を使用して実行する必要があります。シャード・カタログ・データベースは、プロキシ・ルーティングを使用してシャードからの部分的な結果を収集します。最終集計は、シャードによって生成された部分的な結果に基づいて生成されます。そのため、次に示すように、元のPOManagerクラスに導入されたtotalOrders()メソッドは、シャード・カタログ・データベースに接続するようにリライトされています。

//
  // xshard aggregate connects to the shard catalog
  //
  public static void totalOrders () throws SQLException {
    String sql = 
      "SELECT O.PONo, ROUND(SUM(S.Price * L.Quantity)) AS TOTAL " +
      "FROM Orders O, LineItems L, StockItems S " +
      "WHERE O.PONo = L.PONo AND L.StockNo = S.StockNo " +
      "GROUP BY O.PONo";
    try {
      Connection conn = getCatConn();
      PreparedStatement pstmt = conn.prepareStatement(sql);
      ResultSet rset = pstmt.executeQuery();
      printResults(rset);
      rset.close();
      pstmt.close();
    } catch (SQLException e) {System.err.println(e.getMessage());}
  } // totalOrders

前の手順で導入したprintResults()ヘルパー関数は、データベース構造に依存しないため変更は不要です。

  //
  // helper function – no change required
  //
  static void printResults (ResultSet rset) throws SQLException {
    String buffer = "";
    try {
      ResultSetMetaData meta = rset.getMetaData();
      int cols = meta.getColumnCount(), rows = 0;
      for (int i = 1; i <= cols; i++) {
        int size = meta.getPrecision(i);
        String label = meta.getColumnLabel(i);
        if (label.length() > size) size = label.length();
        while (label.length() < size) label += " ";
        buffer = buffer + label + "  ";
      }
      buffer = buffer + "\n";
      while (rset.next()) {
        rows++;
        for (int i = 1; i <= cols; i++) {
          int size = meta.getPrecision(i);
          String label = meta.getColumnLabel(i);
          String value = rset.getString(i);
          if (label.length() > size) size = label.length();
          while (value.length() < size) value += " ";
          buffer = buffer + value + "  ";
        }
        buffer = buffer + "\n";
      }
      if (rows == 0) buffer = "No data found!\n";
      System.out.println(buffer);
    } catch (SQLException e) {System.err.println(e.getMessage());}
  } // printResults

StockItem表のキー列stockNoの値は、すべてのシャードのOrder表の行と一致する可能性があります。そのため、シャード・カタログに接続するように、元のPOManagerクラスに導入したcheckStockItem()メソッドを変更する必要があります。シャード・カタログ・データベースは、各シャードで実行されたローカル結合の結果として返されるすべての行の結合を返します。

 //
  // xshard query matching duplicated table 
  //
  public static void checkStockItem (int stockNo)
    throws SQLException {
      String sql = "SELECT O.PONo, O.CustNo, L.StockNo, " + 
        "L.LineNo, L.Quantity, L.Discount " +
        "FROM Orders O, LineItems L " +
        "WHERE O.PONo = L.PONo AND L.StockNo = ?";
      try {
        Connection conn = getCatConn();
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, stockNo);
        ResultSet rset = pstmt.executeQuery();
        printResults(rset);
        rset.close();
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  } // checkStockItem

元のPOManagerクラスに導入したchangeQuantity()メソッドでは、孫表LineItemsを更新します。繰り返しになりますが、getCustomerFromOrder()ヘルパー・メソッドを使用してシャーディング・キーを取得し、アプリケーションが更新を実行する適切なシャードに接続できるようにします。addLineItem()メソッドの変更と同様に、custNo列の値はアプリケーション・コンテキストでも使用可能であると考えられます。そのため、changeQuantity()でchangeQuantifyWithinParent()を使用して、シャード・カタログへのラウンド・トリップを省くようにします。

  //
  // Get customer (parent) from the catalog then update a shard!
  //
  public static void changeQuantity (int newQty, 
                                     int orderNo, int stockNo) 
     throws SQLException 
  {
      String sql = "UPDATE LineItems SET Quantity = ? " +
        "WHERE CustNo = ? AND PONo = ? AND StockNo = ?";
      try {
        int custNo = getCustomerFromOrder(orderNo);
        Connection conn = getShardConn(custNo);
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, newQty);
        pstmt.setInt(2, custNo);
        pstmt.setInt(3, orderNo);
        pstmt.setInt(4, stockNo);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  } // changeQuantity

  //
  // You’ve got the full level key handy, update shard directly. 
  //
  public static void changeQuantityWithinParent (int newQty, 
                       int custNo, int orderNo, int stockNo) 
      throws SQLException 
  {
      String sql = "UPDATE LineItems SET Quantity = ? " +
        "WHERE CustNo = ? AND PONo = ? AND StockNo = ?";
      try {
        Connection conn = getShardConn(custNo);
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, newQty);
        pstmt.setInt(2, custNo);
        pstmt.setInt(3, orderNo);
        pstmt.setInt(4, stockNo);
        pstmt.executeUpdate(); 
        pstmt.close();
      } catch (SQLException e) {System.err.println(e.getMessage());}
  } // changeQuantityWithinParent

最後のメソッドdeleteOrder()に対する変更は、addLineItem()およびchangeQuantity()に適用したものと同じガイドラインに従います。LineItems表から行を削除する前に、アプリケーションでは、まず、リクエストされたオーダーに対応するcustNoシャーディング・キー値についてシャード・カタログを参照する必要があります。シャーディング・キーを取得したら、シャードに接続して削除を実行します。繰り返しになりますが、アプリケーション・コンテキストでもシャーディング・キー値が使用可能であると考えて、deleteOrderWithinParent()メソッドを使用します。

  //
  // Get customer (parent) first from the catalog, delete in a shard!
  //
  public static void deleteOrder (int orderNo) throws SQLException 
  {
    String sql = "DELETE FROM LineItems WHERE CustNo = ? AND PONo = ?";
    try {
      int custNo = getCustomerFromOrder(orderNo);
      Connection conn = getShardConn(custNo);
      PreparedStatement pstmt = conn.prepareStatement(sql);
      pstmt.setInt(1, orderNo);
      pstmt.executeUpdate(); 
      sql = "DELETE FROM Orders WHERE PONo = ?";
      pstmt = conn.prepareStatement(sql);
      pstmt.setInt(1, custNo);
      pstmt.setInt(2, orderNo);
      pstmt.executeUpdate(); 
      pstmt.close();
    } catch (SQLException e) {System.err.println(e.getMessage());}
  } // deleteOrder

  //
  // You’ve got whole level key handy, delete in shard directly
  //
  public static void deleteOrderWithinParent (int custNo, 
                                              int orderNo) 
      throws SQLException 
  {
    String sql = "DELETE FROM LineItems WHERE CustNo = ? AND PONo = ?";
    try {
      Connection conn = getShardConn(custNo);
      PreparedStatement pstmt = conn.prepareStatement(sql);
      pstmt.setInt(1, orderNo);
      pstmt.executeUpdate(); 
      sql = "DELETE FROM Orders WHERE PONo = ?";
      pstmt = conn.prepareStatement(sql);
      pstmt.setInt(1, custNo);
      pstmt.setInt(2, orderNo);
      pstmt.executeUpdate(); 
      pstmt.close();
    } catch (SQLException e) {System.err.println(e.getMessage());}
  } // deleteOrderWithinParent


} // POManager