【IRIS/Cache】常駐プロセスを手軽に作ろう-%SYSTEM.Event

本記事は、常駐プロセスを手軽に作成する「%SYSTEM.Event.cls」について解説します。

※この記事は下記の方向けになります。
  • 常駐型の定期監視プロセスを作成したい方
  • 特定の処理を別プロセスで実行したい方

はじめに

定期的に監視を行う「常駐プロセス」を作成したい場合、どのような方法を選びますか?

常駐プロセスを実装する手段は、下記3点程考えられます。

常駐プロセス作成の選択肢
  • タスクスケジューラに登録し、定期的に実行する
  • for(while)ループを行いつつ、一定時間hangコマンドで処理を停止させる
  • クラス「%SYSTEM.Event」を利用する

今回は、3番目の選択肢「%SYSTEM.Event.cls」を使った常駐プロセスの作成方法について解説します。

%SYSTEM.Eventクラスを利用すると、次の2種類の常駐プロセスを容易に実装できます。

  1. 定期的に処理を実行するタイプ
  2. 普段は待機し、必要な時に起動して処理を実行するタイプ

この記事では、それぞれのタイプを具体的なサンプルプログラムとともに紹介していきます。

定期的に処理を実行するタイプ

定期的に処理を実行する処理のサンプルになります。
 ※ 定期処理代わりに、^Sample.logグローバルを登録する処理をいれています。

この関数「periodicExe」を$job実行すれば、立派な常駐監視プロセスの誕生です。

【サンプルPG】

ClassMethod periodicExe(resource As %String = "sample", timeout As %Integer = 10)
{
	k ^Stop
	
	// イベント追加
	q:('$system.Event.Create(resource))
	
	f { q:($d(^Stop))
		// resourceが存在しているかチェック
		i ($system.Event.Defined(resource)){
			// 存在していた場合、10秒待機する
			s flg = $system.Event.Wait(resource, timeout)
			, msg = ""					
		}else{
			// 存在しない場合は、処理を止める
			s ^Sample.log($i(^Sample.log))="リソースなし!:"_$zdt($h,3,1)
			q
		}
		
		// $system.Event.Waitの戻り値を判定する
		i (flg = -1){
			// リソースが削除された時の処理
			s ^Sample.log($i(^Sample.log))="リソース削除:"_$zdt($h,3,1)	
			q
		}else{
			// 定期実行処理はここにコーディングする
			s ^Sample.log($i(^Sample.log))="定期実行:"_$zdt($h,3,1)
		}
	}
	
	w !,"処理完了"
}

では下記コマンドで、リソース名(第1引数)を「sample」と命名、ループ時に10秒待機(第2引数)する処理を実行します。

j ##class(developer.event.Sample).periodicExe("sample")

実行結果は、管理ポータルから確認します。

プロセスの停止方法

先ほど実行したプロセスを停止させます。

プロセスを停止するには、下記コマンドを実行して下さい。
引数は、関数「periodicExe」実行時に命名した「リソース名(resource)」です。

今回は「sample」になります。

w $system.Event.Delete("sample")

コマンドの実行結果を確認します。

無事停止しています。

末尾のグローバル「^Sample.log(25)」を確認すると、プロセスが停止した際に、23行目の処理が実行された事がわかります。

また、「s ^Stop = 1」をターミナルで入力する事で、ループ自体を止める事も可能です。

解説

サンプルプログラムで登場した関数です。

No関数鵜説明
1Createリソースを作成する
リソース名の重複はNGで、重複した場合(すでに同名で実行している場合)は0が返る
2Definedリソースの存在チェックを行う
リソースが存在している場合1が返る
3Wait処理を指定秒待機する
4Deleteリソースを削除する

では、各関数の動作を確認していきましょう。

Create

任意の名称のリソースを作成します。

s flg = $system.Event.Create([resourceName]) // 1:作成成功

引数の「resource as %String」は、作成するリソースの名称となります。
作成が成功したら「1」が返り、リソース名が重複した時は「0」が返ります。

【実行中のプロセスの確認】

基本的にプロセスは待機している事が多いと思います、プロセスの状態は「EVTW(イベント待機中)」となっていました。

Defined

リソースが存在するか確認します。
後続の関数「Wait」は、リソースが存在しないとエラーとなるので、「Defined」で存在確認を行う方が事故がありません。

s flg = $system.Event.Defined([resourceName]) // 1:存在する

Wait

処理を指定時間(秒)待機します。

s flg = $system.Event.Wait([resourceName], [timeout])

引数の「timeout」は、処理の待機時間を%Decimalで設定します。
1/100秒まで設定可能で、「-1」を設定するとずっと待機中になります。

■戻り値

説明
-1待機中にリソースが削除された時の値
0タイムアウトが発生した時の値
1関数「Signal」によって待機中が解除された時の値

Delete

実効中のリソースを削除します。

s flg = $system.Event.Delete([resourceName]) // 1:削除実施

存在しないリソースを削除した時は、「0」が返ります。

普段は待機し、必要な時に起動して処理を実行するタイプ

常に待機状態で、別プロセスから呼び出される(ウェイクアップイベント<以降イベント>)時に1度起きて処理を行う設定が可能です。
 ※ 常に待機中なので、死活確認が行えないデメリットがあります。

クラス「%SYSTEM.Event」は、他プロセスからの呼び出し(イベント)をQueueとして管理し、順次処理を行うことできます。

処理が重く非同期でも問題ない場合は、とても有効な手段だと思います。
 ※ Queueの保存は不可

【サンプルPG】

ClassMethod queueExe(resource As %String = "sample")
{
	// イベント追加
	q:('$system.Event.Create(resource)) "停止"
	
	f {
		// 第2引数に「-1」を渡すと、常に待機状態となります。
		s msgDt = $system.Event.WaitMsg(resource, -1)
		
		i ($lg(msgDt, 1)=-1){
			// リソースが削除された時の処理
			s ^Sample.log($i(^Sample.log))="リソース削除:"_$zdt($h,3,1)	
			q
		}else{
			// キューイング処理はここにコーディングする
			s msg = $lg(msgDt, 2)
			s ^Sample.log($i(^Sample.log))="Queue実行:"_$zdt($h,3,1)_", msg:"_msg
			h 1
		}
	}
	
	q "処理完了"
}

では、「常時待機、呼び出された時だけ処理する」プロセスを実行します。

j ##class(developer.event.Sample).queueExe("sample")

キューイングさせる

先ほど作成したリソースにキューを投げるには、下記コマンドを実行します。

w $system.Event.Signal("sample", "test")

コマンドの実行結果を確認します。

渡したメッセージを含め、ログが作成されました。

イベントの数を数える

キューに「イベント」がどの程度たまっているか、確認してみましょう。
下記コマンドをターミナルから実行します。

queueCount	;
	f pos=1:1:10 {
		d $system.Event.Signal("sample", "test:"_pos)
	}
	w "キューの数:", $system.Event.Count("sample")
d queueCount

実行結果です。
キューの数が出力されています。

キューに送った順に実行されているのが確認できます。

たまったイベントを削除する

たまったイベントを削除します。
下記コマンドで検証しましょう。

キューを数えたときと同様に、ループ2~10回目が削除される想定です。

queueDelete	;
	f pos=1:1:10 {
		d $system.Event.Signal("sample", "test:"_pos)
	}
	w $system.Event.Clear("sample")
d queueDelete

実行します。

ログが1レコードしか作成されていないため、2回目以降のイベントは削除された事が分ります。

解説

サンプルプログラムで登場した関数です。
 「Create」「Delete」に関しては、使用方法が変わらない為、割愛します。

No関数鵜説明
1Create割愛します
2Delete割愛します
3WaitMsgWait関数とほぼ使用感は変わらない。
Signalからメッセージを受け取る事が可能
4Signal待機しているリソースを起こす
5Countキューの数を数える
6Clearキューを削除する

では、各関数の動作を確認していきましょう。

WaitMsg

使用感は「Wait」関数とほぼ変わりません。

s mesDt = $system.Event.WaitMsg([resourceName], [timeout]) // $lb(flg, message)

Waitと異なる点は下記2つ

  1. リソースが無い状態で実行してもエラーにならない
  2. 「Signal」関数からメッセージを受け取る事が可能

特に「2.」のSignalからのメッセージ取得が重要で、戻り値(%List型)の2番目で受け取れます。
 ※ Waitで取得するフラグは、戻り値の1番目に格納される。

WaitとWaitMsgのどちらを利用しても問題ありません。
メッセージの「あり or なし」を基準に、どちらの関数を使うか判断すればよいと思います。

Signal

待機中の「Wait」や「WaitMsg」を起こす事ができる。

s flg = $system.Event.Signal([resourceName], [message]) // 1:成功

messageには、数値、文字列、リスト型(%List)、文字列にしたJSON(%DynamicObject系)等を渡す事ができます。

Count

たまっているイベントを数えます。

s qCount = $system.Event.Count([resourceName]) // キューの数

Clear

たまっているイベントを削除します。

s flg = $system.Event.Clear([resourceName]) // 1:成功

その他の関数

List

下記コマンドを実行する事で、定義済みのリソースを全て取得します。

s list = $system.Event.List()
f pos=1:1:$ll(list) { w !,$li(list,pos) }

内部で色々定義されているのが分かります。
下から2番目に、今回検証で使用しているリソースが表示されていました。

List ※Queries

関数「List」と動作は変わりません。

こちらはSQLを利用して、全リソースを取得します。

ClassMethod getList()
{
	s rset = ##class(%ResultSet).%New("%SYSTEM.Event:List")
	d rset.Execute()
	while ( rset.Next(.sts) ){
		w !,rset.Get("Event")
	}
}

おわりに

今回紹介した「%SYSTEM.Event」クラスは、常駐プロセスを制御する仕組みです。

単純なループ構造やタスクスケジューラでは実現しづらい、「イベント駆動型のバックグラウンド処理」を柔軟に構築できる点が大きな特徴です。

このクラスを利用することで、定期的なジョブの実行はもちろん、システム内部の状態変化や外部トリガーに応じて動作する処理も容易に実装できます。

さらに、イベント情報をキューイングしながら別プロセスで順次処理できるため、メインプロセスに負荷をかけず安定した処理が可能です。

安定したシステム運用とメンテナンス性の高いバックグラウンド処理を実現する上で、%SYSTEM.Eventクラスは非常に有効な手段と考えます。

システム要件に合わせた常駐処理設計の一助になれば幸いです。