6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Oracle Database 23ai で追加された True Cache に JDBC Thin driver でアクセスしてみた

Posted at

Oracle Database 23ai から True Cache という機能が追加されました。
トランザクション量が多く、応答時間にシビアなシステムにおいてはアプリとDBの間にキャッシュ製品を挟む構成がよくあります。
通常はRedisやMemcachedといった製品を使うケースが多いですが、同様の機能をOracle Databaseの機能として提供するのが True Cache です。
True Cache は内部で Active Data Guard (ADG) の仕組みを使ってキャッシュにデータ読み込みを行います。
ただし通常の ADG と異なるのは、データをディスクに保持せず、バッファ・キャッシュのみに格納する点です。
truecache.jpg
以下はキャッシュに True Cache を使う主なメリットです。

  • アプリからの接続に使うサービスを1つに集約できる (JDBC Thin driver 23以上が必要)
    • アプリ実装時にDB/キャッシュどちらに接続するか考える必要なし
    • Write伴うならPrimary DB (マスターデータを持つ大元のDB)、Read Onlyなら True Cache へ自動振り分け
  • キャッシュへのデータ読込はOracle Databaseが実施 (アプリ実装不要)
    • キャッシュされていないデータがあれば、Primary DBから自動でキャッシュに読込
    • キャッシュ済みデータは Data Guard の仕組みにより常に最新化

今回はTrue Cacheについて以下作業を試してみました。

  • True Cacheの構成
  • Primary DBとTrue Cache両方にアクセスできるサービスの構成
  • JDBC Thin driverからサービス接続した際に、Read処理がTrue Cacheへ自動振り分けされる動作の確認

なお本検証では Oracle Database 23ai Free を使いました。
Free版でも1つの True Cache を構成できますので、お手軽に試して頂けます。
またサーバはOCI Compute (Oracle Linux 8) を活用し、Primary DB と True Cache は別々のサーバにインストールしました。
以降は Oracle Database 23ai Free のインストールから True Cache の構成、サンプルのJavaコードを使った JDBC Thin driver による True Cache アクセスの手順を記載していきます。
また Primary DB のノードを Primaryノード、True Cache のノードをキャッシュノードと呼称します。

23aiインストール

Primaryノードとキャッシュノードの双方に Oracle Database 23ai Free 本体をインストールします。

$ sudo su -

# プリインストールRPMを使ってOS設定
$ dnf -y install oracle-database-preinstall-23ai

# 本体RPMをダウンロード&インストール
$ wget https://download.oracle.com/otn-pub/otn_software/db-free/oracle-database-free-23ai-1.0-1.el8.x86_64.rpm
$ dnf install -y oracle-database-free*

DB作成

PrimaryノードだけDBを作成します。
キャッシュノードには作成しないようご注意ください。

$ /etc/init.d/oracle-free-23ai configure

firewall設定

Primaryノードとキャッシュノードの双方でファイアウォールを設定します。
Oracle Net通信で使うTCP 1521ポートを開けておきます。

$ sudo su -
# 1521ポート許可
$ firewall-cmd --add-port=1521/tcp
# 永続保存
$ firewall-cmd --runtime-to-permanent
# 設定リロード
$ firewall-cmd --reload
# 設定内容確認
$ firewall-cmd --list-all

環境変数設定

Primaryノードとキャッシュノードの双方でOracleユーザの環境変数を設定します。
.bashrcなどに追記して読み込ませます。

export ORACLE_SID=FREE 
export ORACLE_BASE=/opt/oracle 
export ORACLE_HOME=/opt/oracle/product/23ai/dbhomeFree 
export PATH=/home/oracle/.local/bin:/home/oracle/bin:/usr/share/Modules/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/opt/oracle/product/23ai/dbhomeFree/bin

True Cache 作成

アーカイブ・ログモード設定

True Cache を構成するにはPrimary DBがアーカイブ・ログモードとなっている必要があるため、Primary DBをアーカイブ・ログモードに変更します。

-- 設定事前確認
SQL> select log_mode from V$DATABASE;

LOG_MODE
------------
NOARCHIVELOG

-- アーカイブ・ログモードに変更して起動
SQL> shutdown immediate
SQL> startup mount
SQL> alter database archivelog;
SQL> alter database open;

-- 設定事後確認
SQL> select log_mode from V$DATABASE;

LOG_MODE
------------
ARCHIVELOG

パスワードファイルをコピー

Primaryノードにあるパスワードファイルをキャッシュノードへコピーします。
パスワードファイルはPrimaryノードの以下に配置されています。

/opt/oracle/product/23ai/dbhomeFree/dbs/orapwFREE

こちらをキャッシュノードの以下に配置する想定でこれ以降の手順を記載します。

/opt/oracle/tmp/

True Cache 作成

DBCAを使ってキャッシュノード上に True Cache を作成します。
your_easy_connect_stringにはご自分のPrimary DBの簡易接続識別子を記入します。

$ dbca -createTrueCache -gdbName FREE -sourceDBConnectionString your_easy_connect_string -passwordFileFromSourceDB /opt/oracle/tmp/orapwFREE -silent

Enter Remote DB SYS user password:

Session ID of the current execution is: 1
Log file location: /opt/oracle/cfgtoollogs/dbca/FREE/trace.log_2024-08-06_08-27-11AM_51824
-----------------
Running Extract_password_file_from_blob_file job
Completed Extract_password_file_from_blob_file job
25% complete
-----------------
Running Create_static_listener job
Completed Create_static_listener job
38% complete
-----------------
Running Register_listener job
Completed Register_listener job
50% complete
-----------------
Running Extract_tde_wallet_from_blob_file job
Skipping. Job is detected as not applicable.
54% complete
-----------------
Running Setup_required_directories job
Completed Setup_required_directories job
57% complete
-----------------
Running Create_pfile job
Completed Create_pfile job
61% complete
-----------------
Running Start_nomount_instance job
Completed Start_nomount_instance job
64% complete
-----------------
Running Create_TDE_wallet job
Skipping. Job is detected as not applicable.
68% complete
-----------------
Running Create_truecache_instance job
Completed Create_truecache_instance job
71% complete
-----------------
Running Add_oratab_entry job
Completed Add_oratab_entry job
75% complete
-----------------
Running Reopen_wallet job
Skipping. Job is detected as not applicable.
100% complete
---------- PLUGIN NOTES ----------
Successfully created True Cache.
In order to complete the operation,configure the True Cache database application services from the primary database.
---------- END OF PLUGIN NOTES ----------
Look at the log file "/opt/oracle/cfgtoollogs/dbca/FREE/FREE.log" for further details.

事後確認

キャッシュノード上に作成された True Cache インスタンスにログインし、問題なく構成されていることを確認します。

SQL> select name, open_mode, database_role from v$database;

NAME	  OPEN_MODE	           DATABASE_ROLE
--------- -------------------- ----------------
FREE	  READ ONLY WITH APPLY TRUE CACHE

SQL> select controlfile_type from v$database;

CONTROLFIL
----------
TRUE CACHE

SQL> col TRUE_CACHE_NAME format a15
SQL> col status format a10 
SQL> col primary_name format a15
SQL> select TRUE_CACHE_NAME, PRIMARY_NAME, STATUS, REMOTE_VERSION, CON_ID from v$true_cache;

TRUE_CACHE_NAME PRIMARY_NAME	STATUS	   REMOTE_VERSION	  CON_ID
--------------- --------------- ---------- ------------------ ----------
FREE		    FREE		    HEALTHY    23.0.0.0.0	      0

Primaryノードとキャッシュノード双方のtnsnames.oraにサービス名を登録しておきます。
your_primary_node_hostyour_cache_node_hostにはご自分のPrimaryノード、キャッシュノードのホスト名を記入します。

freepdb1 =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = your_primary_node_host)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = freepdb1)
    )
  )

free_tc =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = your_cache_node_host)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = freepdb1)
    )
  )

True Cache 向けサービスの作成

ここから Primary DB と True Cache の接続を集約するためのアプリケーション用サービスを作成していきます。
まずはPrimaryノードで Valid Node Checking for Registration (VNCR) を有効化します。
VNCRは別サーバ上で稼働するDBインスタンス (今回で言う True Cache) をリスナーに登録許可するための設定です。

VNCRの詳細はMy Oracle Supportの下記ドキュメントが参考になります。
登録のための有効ノードのチェック (VNCR) (Doc ID 1900587.1)

以下をPrimaryノードのlistener.oraに追記します。
your_host_nameにはご自分のPrimaryノードのホスト名を記載します。

VALID_NODE_CHECKING_REGISTRATION_LISTENER = SUBNET
REGISTRATION_INVITED_NODES_LISTENER=(your_host_name)

設定を有効化するため、listenerをリロードします。

$ lsnrctl reload

Primary DB上のPDBにアプリケーション接続用のサービスを作成します。
今回はサンプル・スキーマのHRを使うため、HRというサービス名にしています。

BEGIN
   DBMS_SERVICE.CREATE_SERVICE('HR', 'HR');
   DBMS_SERVICE.START_SERVICE('HR');
END;
/

作成したサービスの設定を確認します。
まだ True Cache 用サービスは紐づいていないため、true_cache_serviceは空欄です。

SQL> SELECT service_id, name, true_cache_service FROM v$active_services WHERE name='HR';

SERVICE_ID NAME
---------- ----------------------------------------------------------------
TRUE_CACHE_SERVICE
----------------------------------------------------------------
	 1     HR

DBCAを使い、True Cache用サービスをPrimary DBに登録します。
Primaryノード上で以下コマンドを実行します。
your_tc_easy_connect_stringには True Cache インスタンスに対する簡易接続識別子を記入します。
また True Cache 接続用のサービス名はhr_tcとしています。

$ dbca -configureDatabase -configureTrueCacheInstanceService -sourceDB FREE -trueCacheConnectString your_tc_easy_connect_string -trueCacheServiceName hr_tc -serviceName hr -pdbName freepdb1 -silent

Primaryノード上でリモートリスナーの設定を表示し、正しく構成されたことを確認します。

$ lsnrctl services
...()...
Service "hr_tc" has 1 instance(s).
  Instance "FREE", status READY, has 1 handler(s) for this service...
    Handler(s):
      "DEDICATED" established:0 refused:0 state:ready
         REMOTE SERVER
         (ADDRESS=(PROTOCOL=TCP)(HOST=tc_host_name)(PORT=1521))
...()...

なおTrue Cache用サービスhr_tcですが、DBCA実行後にTrue Cacheインスタンス上で自動起動されていました。

Primaryノード上で再度HRサービスの設定を確認します。
今回はtrue_cache_servicehr_tcと表示されます。

SELECT service_id, name, true_cache_service FROM v$active_services WHERE name='HR';

SERVICE_ID NAME
---------- ----------------------------------------------------------------
TRUE_CACHE_SERVICE
----------------------------------------------------------------
	 1     HR
hr_tc

サンプル・スキーマのインストール

Javaプログラムからアクセスするサンプル・スキーマをインストールします。
まずはPrimaryノード上でサンプル・スキーマをダウンロードします。

$ wget https://github.com/oracle-samples/db-sample-schemas/archive/refs/tags/v23.2.zip
$ unzip v23.2.zip

Primary DBにサンプル・スキーマをインストールします。
今回はHRスキーマを使いました。

@hr_install.sql 

Javaインストール

Javaプログラムを使うため、コンパイラjavacをインストールします。
今回はPrimaryノード上にインストールし、Primaryノードからプログラム実行する構成としました。

$ dnf -y install java-11-openjdk-devel

JDBC Thin driverを使ったTrue Cacheへの接続テスト

JavaプログラムからHRスキーマへアクセスする前に、True Cacheへの接続テスト用プログラムを実行してみます。
以下はマニュアルに掲載されていたサンプルコードです。
ポイントは 46 行目の setReadOnly(true) です。
接続時に使うサービス名はhrのみですが、setReadOnly(true)の指定有無で接続先がPrimary DB/True Cacheに自動で振り分けられます。
setReadOnly(true)の設定により Trun Cache へ接続され、なければ Primary DB に接続します。
なおコード中の以下の記述はご自分の環境に合わせて修正してください。

  • your_primary_db_host: Primaryノードのホスト名
  • your_system_pw: sys/systemのパスワード

本サンプル・コードを本番環境で利用しないでください。

import java.sql.*;
import java.io.*;
import java.util.*;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

import oracle.jdbc.driver.OracleLog;
import oracle.jdbc.pool.OracleDataSource;

public class TrueCache {
    static String url_primary = "jdbc:oracle:thin:@your_primary_db_host:1521/hr";
    static String user = "SYSTEM";
    static String password = "your_system_pw";

    public static void main(String args[]) {
        try {
            TrueCache t = new TrueCache();
            if(args != null && args.length >0 ) {
                url_primary = args[0];
                user = args[1];
                password = args[2];
            }
            t.test1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void test1() throws SQLException {
        show("Basic test to connect to primary and True Cache");
        try {
            show("Get Connection from True Cache instance using primary db and properties");
            OracleDataSource ods1 = new OracleDataSource();
            show("URL = " + url_primary);
            ods1.setURL(url_primary);
            ods1.setUser(user);
            ods1.setPassword(password);
            ods1.setConnectionProperty("oracle.jdbc.useTrueCacheDriverConnection", "true");

            Connection conn1 = ods1.getConnection();

            show("isReadonly " + conn1.isReadOnly());
            verifyConnection(conn1);  // This is connected to Primary

            conn1.setReadOnly(true);
            show("isReadonly " + conn1.isReadOnly());
            verifyConnection(conn1); // This is connected to True Cache

            conn1.close();
        } catch (SQLException sqex) {
            show("test1 -- failed" + sqex.getMessage()+":"+sqex.getCause());
            sqex.printStackTrace();
        }
        show("The end");
    }

    public void verifyConnection(Connection conn) {
        try {
            Statement statement = conn.createStatement();
            ResultSet rs = statement.executeQuery("SELECT database_role from v$database");
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnsNumber = rsmd.getColumnCount();
            rs.next();
            show("Database role : " + rs.getString(1));
            rs.close();
            ResultSet resultSet = statement.executeQuery("SELECT SYS_CONTEXT('userenv', 'instance_name') as instance_name"
                    + ", SYS_CONTEXT('userenv', 'server_host')" + " as server_host" + ", SYS_CONTEXT('userenv', 'service_name')"
                    + " as service_name" + ", SYS_CONTEXT('USERENV','db_unique_name')" + " as db_unique_name" + " from sys.dual");
            resultSet.next();
            show("instance_name : " + resultSet.getString("instance_name"));
            show("server_host : " + resultSet.getString("server_host"));
            show("service_name : " + resultSet.getString("service_name"));
            show("db_unique_name : " + resultSet.getString("db_unique_name"));
            resultSet.close();
            statement.close();
        } catch (SQLException sqex) {
            show("verifyConnection failed " + sqex.getMessage());
        }
    }

    public void show(String msg) {
        System.out.println(msg);
    }
}

上記をTrueCache.javaというファイル名で保存し、以下のようにコンパイルします。

$ javac -classpath /opt/oracle/product/23ai/dbhomeFree/jdbc/lib/ojdbc11.jar TrueCache.java

実行してTrue Cacheインスタンスへ接続できることを確認します。
出力のprimary_hostcache_hostはご自分のPrimaryノード、キャッシュノードのホスト名が表示されます。

$ java -classpath /opt/oracle/product/23ai/dbhomeFree/jdbc/lib/ojdbc11.jar TrueCache.java
Basic test to connect to primary and True Cache
Get Connection from True Cache instance using primary db and properties
URL = jdbc:oracle:thin:@your_primary_node_host:1521/hr
isReadonly false
Database role : PRIMARY
instance_name : FREE
server_host : primary_host
service_name : HR
db_unique_name : FREE
isReadonly true
Database role : TRUE CACHE
instance_name : FREE
server_host : cache_host
service_name : hr_tc
db_unique_name : FREE
The end

JDBC Thin driverを使ったHRスキーマへのアクセス

JDBC Thin driverを使って先ほど作成したサービスHRに接続し、HRスキーマのデータにアクセスしてみます。
setReadOnly(true)を設定してアクセスすることで、True Cache インスタンスのバッファ・キャッシュに該当データがキャッシュされます。
まずは事前のキャッシュ状況をTrue Cacheインスタンス上のPDBで確認します。

SQL> set trimspool on
SQL> set linesize 180
SQL> col object_name format a30
SQL> col object_type format a20
SQL> col owner format a25
SQL> col number_of_blocks format 9999999999999

SQL> SELECT o.OBJECT_NAME, o.OBJECT_TYPE, o.OWNER, COUNT(*) NUMBER_OF_BLOCKS,
substr(d.database_role,1,10) role
FROM DBA_OBJECTS o, V$BH bh,  v$database d
WHERE o.DATA_OBJECT_ID = bh.OBJD
AND o.OWNER in ('HR') 
GROUP BY o.OBJECT_NAME, o.OWNER, o.OBJECT_TYPE,d.database_role;

no rows selected

まだ何もキャッシュされていないことが分かります。

次にHRスキーマのデータへアクセスするJavaプログラムを作成します。
今回は以下のコードを利用しました。
なおコード中の以下の記述はご自分の環境に合わせて修正してください。

  • your_primary_db_host: Primaryノードのホスト名
  • your_hr_pw: HRユーザのパスワード

本サンプル・コードを本番環境で利用しないでください。

import java.sql.*;
import java.io.*;
import java.util.*;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

import oracle.jdbc.driver.OracleLog;
import oracle.jdbc.pool.OracleDataSource;

public class SelectHR {
    static String url_primary = "jdbc:oracle:thin:@your_primary_node_host:1521/hr";
    static String user = "HR";
    static String password = "your_hr_pw";

    public static void main(String args[]) {
        try {
            OracleDataSource ods1 = new OracleDataSource();
            ods1.setURL(url_primary);
            ods1.setUser(user);
            ods1.setPassword(password);
            ods1.setConnectionProperty("oracle.jdbc.useTrueCacheDriverConnection", "true");

            Connection conn = ods1.getConnection();
            conn.setReadOnly(true);
            
            Statement statement = conn.createStatement();
            ResultSet resultSet = statement.executeQuery("select /*+FULL(c)*/ count(*) emp_num from hr.employees e");
            resultSet.next();
            System.out.println("emp_num : " + resultSet.getString("emp_num"));
            resultSet.close();
            statement.close();
            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

本コードはHRスキーマのEMPLOYEES表の行数をカウントする内容となっています。
接続テストのコードと同様、25 行目にあるsetReadOnly(true)の設定によりPrimary DB/True Cacheどちらに接続するかが制御されます。
こちらのコードをSelectHR.javaというファイル名で保存した場合、以下のようにコンパイルします。

$ javac -classpath /opt/oracle/product/23ai/dbhomeFree/jdbc/lib/ojdbc11.jar SelectHR.java

プログラムを実行します。

$ java -classpath /opt/oracle/product/23ai/dbhomeFree/jdbc/lib/ojdbc11.jar SelectHR.java
emp_num : 107

最後にTrue Cache インスタンスのPDBにログインし、キャッシュされているデータを再度確認します。

SQL> set trimspool on
SQL> set linesize 180
SQL> col object_name format a30
SQL> col object_type format a20
SQL> col owner format a25
SQL> col number_of_blocks format 9999999999999

SQL> SELECT o.OBJECT_NAME, o.OBJECT_TYPE, o.OWNER, COUNT(*) NUMBER_OF_BLOCKS,
substr(d.database_role,1,10) role
FROM DBA_OBJECTS o, V$BH bh,  v$database d
WHERE o.DATA_OBJECT_ID = bh.OBJD
AND o.OWNER in ('HR') 
GROUP BY o.OBJECT_NAME, o.OWNER, o.OBJECT_TYPE,d.database_role;

OBJECT_NAME                    OBJECT_TYPE          OWNER                     NUMBER_OF_BLOCKS ROLE
------------------------------ -------------------- ------------------------- ---------------- ----------------------------------------
EMP_MANAGER_IX                 INDEX                HR                                       1 TRUE CACHE
EMPLOYEES                      TABLE                HR                                       6 TRUE CACHE
EMP_NAME_IX                    INDEX                HR                                       1 TRUE CACHE
EMP_EMP_ID_PK                  INDEX                HR                                       2 TRUE CACHE
EMP_EMAIL_UK                   INDEX                HR                                       1 TRUE CACHE
EMP_DEPARTMENT_IX              INDEX                HR                                       1 TRUE CACHE
EMP_JOB_IX                     INDEX                HR                                       1 TRUE CACHE

7 rows selected.

setReadOnly(true)を設定してTrue Cacheに接続した状態でEMPLOYEES表をフルスキャンしましたので、EMPLOYEES表および依存関係にあるオブジェクトがTrue Cacheインスタンスのバッファ・キャッシュに読み込まれています。

以上、JDBC Thin driverを使ったTrue Cacheへの接続例でした。
接続時に指定するサービスはそのまま、setReadOnly(true)の追記だけでTrue Cacheへアクセスできますので、お手軽にキャッシュ層を導入できるのではないでしょうか。
Oracle Database 23ai Freeを使えば無償で構成できますので、是非お試しください。

6
5
0

Register as a new user and use Qiita more conveniently

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?