【IRIS/Cache】ジャーナルファイルを使ったリストア方法について

本記事は、コマンドによるジャーナルファイルを利用したリストア方法についての解説になります。

※この記事は下記の方向けになります。
  • ジャーナルファイルのパスを指定してリストアしたい方
  • コマンドを使用してジャーナルのリストアを行いたい方

はじめに

システム障害や予期せぬトラブルが発生した際、どこまでデータを安全に復旧できるかは、運用体制の信頼性に直結します。

こうした状況に備えて「ジャーナルファイル」は自動的に生成・管理されており、トラブル発生時にこれを活用することで、データの一貫性を保ちながら復旧作業を行うことが可能です。

つまり、論理的に言えば、ジャーナルファイルを保存しておくことで、いつでも元の状態に復旧できる事になります。

本記事では、「ジャーナルファイル」をコマンドから呼び出してストアを行う手順について解説します。

Journal.Restore.clsを使用したリストア

ジャーナルのリストアを行うためだけに用意されたクラスです。
このクラスを利用して、ジャーナルファイルからデータをリストアしてみましょう。

概要

今回想定する、ジャーナルファイルのリストアの概要は下記になります。
 ※自端末に対しリストアも可能です。

リストアに必用なもの

No項目説明
ジャーナル・ファイルリストア対象のグローバルを含んだジャーナル・ファイル
データベースADATファイルが格納されているパス名
データベースBリストア対象データベースのジャーナルをONにしておく

リストアを行うジャーナルファイルにトランザクション中の処理がある場合、リストア時にエラー文が表示されます。

↓エラー文
*** Missing previous 2 files that may contain TSTARTs
(TSTART が含まれている可能性のある前の 2 つのファイルが見つかりません)

ERROR: ScanForOpenTrans+60^JRNRESTB

There is a problem with searching for open transactions in older files.
(古いファイル内のオープン トランザクションの検索に問題があります。)
Restore will continue, but transactions started in files earlier than
the first one to be restored might remain open after the restore.
(復元は続行されますが、復元される最初のファイルより前に開始されたトランザクションは、復元後もオープンのままになる可能性があります。)

① リストア用のジャーナルファイルの作成

リストアサンプル用に下記3つのグローバルを用意しました。

グローバル名説明
^sampleSET文のみで、下位ノードも含め残っている
^testSET文後にZKILL文を実行し、下位ノードしか存在していない
^delGblSET文後にKILL文実行し、グローバルはなし
zn "sample"

// 余計なデータを除きたいので、切り替えを行う
w ##class(%SYS.Journal.System).SwitchFile()

// グローバルをクリア
k ^sample
k ^test
k ^delGbl

// グローバル作成開始
s ^sample = "サンプルグローバル"
s ^sample(1) = "サンプルグローバル下位ノード"
s ^test = "テストグローバル"
s ^test(1) = "テストグローバル下位ノード"
zk ^test
s ^delGbl = "削除用グローバル"
s ^delGbl = "削除用グローバル下位ノード"
k ^delGbl

// 持ち運びたいので、ジャーナルファイルを切り替える
w ##class(%SYS.Journal.System).SwitchFile()

ジャーナルファイルの中身を確認します。

問題なく出力されているのが分かります。

② DATファイルが格納されているパス名

①の画像にある通り、今回のジャーナリングされたデータベースのパスは「d:\irisdb\sample-data\」です。

③ リストア対象データベースのジャーナルをONにしておく

リストア対象のデータベースのジャーナルがONになっていないと、一切リストアがされないので要注意です。

下記チェックボックス「グローバルジャーナル状態」がONになっている事を確認してください。

他サーバのDBに対しリストア実行

先ほど作成したジャーナルファイルをリストアを行うサーバに移し、下記コマンドを実行します。
 ※他サーバに持ち出すのは、①で作成したジャーナルファイルのみです。

zn "%SYS"

s dir = "D:\Temp\jorn\"
s file = "test20250326.013"
s orgDB = "d:\irisdb\sample-data\" // ジャーナルにあるDATパス
s tgtDB = "d:\irisdb\sample\" // リストア対象のDATパス


// リストア開始
s jrn = ##class(Journal.Restore).%New()

// ジャーナルファイルの設定
d jrn.UseJournalLocation(dir) // ディレクトリ
s jrn.FirstFile = file // 連続でリストアする際の最初のジャーナルファイル名
s jrn.LastFile = file // 連続でリストアする際の最後のジャーナルファイル名

s jrn.JournalLog = -1
d jrn.CheckJournalIntegrity(0)

// ジャーナル対象のDATパスとリストア対象のDATパスを設定
d jrn.RedirectDatabase(orgDB, tgtDB)

// リストア対象のグローバル
d jrn.SelectUpdates(orgDB, "sample")
d jrn.SelectUpdates(orgDB, "test")
d jrn.SelectUpdates(orgDB, "delGbl")

w jrn.Run()

実行します。
今回は、「トランザクション中」のエラーが表示されました・・・

しゃーなし
次行きましょう。

リストアコマンドが完了したので、対象グローバルを確認します。

正常にリストアされました!

自身のDBに対しリストアを実行

先ほど作成したジャーナルの中でグローバル「^delGbl」は、最後にKILL文を実行してグローバルを全削除してしまいました。

でも、このグローバル。実は消しちゃいけないグローバルだったのです!
と言う建前で「^delGbl」を復活させましょう。

今度は、自端末に対しリストアしてみたいと思います。

フィルタルーチンの作成

全てをジャーナルレコードをリストアしてしまうと、グローバル「^delGbl」は削除されてしまうので、特定のレコードのみをリストアしたいと思います。

そこで、ネームスペース「%SYS」にルーチン「^ZJRNFILT」を作成します。
 ※ルーチン名は固定です。

フィルタルーチンのサンプル
ByRef 引数の resmodeに「0」を設定する事で、対象のジャーナルレコードはリストアされません。

ZJRNFILT(jidsys,dir,glo,type,restmode,addr,time)
	s restmode=1
	
	// リストアしない restmode=0
	s:(type="K")&&(glo["delGbl") restmode = 0 // KILL文をリストア回避

	q
	;-
	;
引数説明サンプルデータ
jidsysコンマで区切られた 2 つのコンポーネント
jid (ジョブ ID), remsysid (ECP の場合のみ)
“5664,1073741824”
dirDATのフルパス名“d:\irisdb\sample-data\”
gloグローバル名“^delGbl”
typeコマンド・タイプ(SET文→”S”, KILL文=”K”)“S”とか”K”等
restmode0 – レコードをリストアしない
1 – レコードをリストアする
1 or 0
addrジャーナル・レコードのアドレス196960
timeレコードのタイムスタンプ ($horolog 形式)

ジャーナル・バッファの作成時刻
 →Set, Killを実行した時刻ではない
“67290,70790”

コマンド実行

先ほどのフィルタルーチンを踏まえて、リストアを実行します。

zn "%SYS"

s dir = "D:\Temp\jorn\"
s file = "test20250326.013"
s orgDB = "d:\irisdb\sample-data\" // ジャーナルにあるDATパス


// リストア開始
s jrn = ##class(Journal.Restore).%New()

// ジャーナルファイルの設定
d jrn.UseJournalLocation(dir) // ディレクトリ
s jrn.FirstFile = file // 連続でリストアする際の最初のジャーナルファイル名
s jrn.LastFile = file // 連続でリストアする際の最後のジャーナルファイル名

s jrn.JournalLog = -1
d jrn.CheckJournalIntegrity(0)

// 自DBのリストアなので両方同じパスを設定
d jrn.RedirectDatabase(orgDB, orgDB)

// リストア対象のグローバル
d jrn.SelectUpdates(orgDB, "delGbl")

// フィルタルーチン
s jrn.Filter = "^ZJRNFILT"


w jrn.Run()

実行します。

コマンドを実行すると下記質問が2回問われるので、両方とも「n」を入力します。
 ・Do you want to rename your journal filter?
 ・Do you want to delete your journal filter?

リストアされたグローバルを確認します。

KILL文のみをリストア対象外としたので、最後に登録したコマンドが反映されています。
フィルタルーチンを下記に修正すれば、最初のレコードも反映可能です。

ZJRNFILT(jidsys,dir,glo,type,restmode,addr,time)
	s restmode=1
	
	// リストアしない restmode=0
	s:(addr>196960) restmode = 0

	q
	;-
	;

これで、うっかりグローバルを削除してしまった場合でも、データの復旧が可能になりました。
とはいえ、何かあった時の為にこまめにバックアップは行った方が精神衛生上良いです。

おまけ

フィルタの引数から、ジャーナルレコードのデータが取れたりします。

ZJRNFILT(jidsys,dir,glo,type,restmode,addr,time)
	s restmode=1
	d LOG
	q
	;-
	;
LOG
	s jrnO = ##class(%SYS.Journal.Record).%OpenId(addr)
	
	new $namespace
	s $namespace = "sample"
	
	s num = $seq(^jrnRecode)
	s ^jrnRecode(addr,"TypeName")=jrnO.TypeName
	, ^jrnRecode(addr,"ProcessID")=jrnO.ProcessID
	, ^jrnRecode(addr,"JobID")=jrnO.JobID

%SYS.Journal.File.clsを使用したリストア

関数「Restore()」を使用する

それっぽい名前があるのですが、関数説明に「virtually」と記載されています。

関数の中身を見ると・・・
forループなのに、jrec.Restore()の引数は「Detail-1」で固定かよ・・・

真っ当に動く気がしない。
ダメだこりゃ

%SYS.Journal.SetKillRecord.Restore()を確認する

先ほどs rc=jrec.Restore()を実行していたので、該当の関数を念のため確認してみます。

んん・・?
リストアを行っている箇所が見当たりませんね。

これもダメだ。

結論

%SYS.Journal.File.clsを使用したリストアは、まだ未完成ですね。
動きません。

こんな中途半端な仕上がりもあるんですね。

リストアの度にネームスペースを「%SYS」に切り替えるのが面倒なので、早く実装して欲しいものです。

おわりに

いかがだったでしょうか。

ジャーナルのリストアをコマンドで実行する方法は、今のところ「Journal.Restore.cls」以外には無さそうな雰囲気です。
 ※対話式であれば「d ^JRNRESTO」があります。

ジャーナルファイルを使ったリストアは、万が一のトラブル時に大切なデータを守るための心強い仕組みです。

「備えあれば憂いなし」
リストアの方法も知っておけば、いざと言うときに慌てなくても済みます。

安心してシステムを運用していくための一助になれば幸いです。