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