クリアフィルター
記事
Megumi Kakechi · 2021年8月25日
これは InterSystems FAQ サイトの記事です。
リレーションシップが設定されており 1対n の n が多量の場合、そのリレーションシップの順次処理などで大量のメモリ消費となるケースがあります。
プログラムの中で多側オブジェクトを参照し内部的にスウィズル処理した後には、そのOREFを含む変数の解放(削除、他の値の設定など)だけでは、その多側オブジェクトとリレーションシップオブジェクトが解放されないことが原因です。
それらを完全にメモリから解放するためには、OREF変数の解放とRelationshipオブジェクトの%UnSwizzleAt<%Library.RelationshipObject >メソッドの実行による明示的なメモリ解放が必要です。
- 使用例 -
Do {
Set employee = company.Employees.GetNext(.key)
If (employee '= "") {
Write employee.Name,!
// remove employee from memory
Do company.Employees.%UnSwizzleAt(key)
}
} While (key '= "")
多側オブジェクトが数個しかない場合は特に問題はありませんが、紐づいている数が大量な場合に、ループ文で連続アクセスした場合、その分大量にメモリにオブジェクトが展開され続けることになり、メモリー圧迫要因のひとつになります。
その様な状況を避けるために明示的な開放処理を入れる必要があります。
Kakechi さん
貴重な情報を公開いただきありがとうございます!Relationshipオブジェクトの理解が浅く申し訳ないのですが、一点だけ教えてください。
今回の場合、Relationshipオブジェクトをループで回した際にのみ事象が発生するという理解なのですが、Interoperabilityで利用可能なRecordMap利用時に生成される、Batchクラス内のRecordsプロパティも上記に該当するのでしょうか?
今私が携わるシステムではRecordMapを多用しており、数万件のデータをこの仕組みを用いて取り込みをおこなっているため、上記の事象が発生するのではないかと懸念しております。 Ohata様
こんにちは、ご質問ありがとうございます。Interoperabilityの場合、RecordMapのBatchサービスでは内部で対応しているため明示的に%UnSwizzleAt() する必要はありません。また、オペレーションについても UnSwizzleRecordsプロパティ(デフォルトはTrue:チェックあり)により、処理後にメモリから解放されるため、こちらについても明示的に %UnSwizzleAt() する必要はありません。UnSwizzleRecordsプロパティを 0 (False:チェックなし) に指定し大量のデータを処理する場合には、正しく開放処理を行わないと <STORE> エラーになる場合がありますのでご注意ください。
Kakechi さん
回答いただきありがとうございます!BSもBOも標準のまま利用するのであれば開放処理がシステム内で行われると理解しました。
私が作成しているシステムでは、BS/BP/BOをそれぞれ標準を継承してカスタマイズしたり、Batch.RecordをObjectScript内でループさせて利用するような処理もあるのですが、この場合には%UnSwizzleAt()が必要との理解でよろしいでしょうか?
例)BSでファイル検知を行い、RecordMapの仕組みを利用してファイルからBatchクラスを生成。↓BOに連携し、BatchクラスをObjectScript内のクラスに譲与。↓取得したBatch.Recordの内容をループで回しながら処理を実行。
このループの中では%UnSwizzleAt()が必要?もしくはInteroperabilityのプロセスで実行する処理の中では、BatchクラスのRecordの内容は勝手に開放される? Ohata様
継承しているBOのクラスが、EnsLib.RecordMap.Operation.Batch* のクラスである場合は、 UnSwizzleRecords プロパティをオーバーライドして変更していない限り、デフォルトの True(=1) となるため、明示的に %UnSwizzleAt() する必要はありません。
「BatchクラスをObjectScript内のクラスに譲与」というのが、例えば %Persistent などのユーザ作成クラスに対してである場合は、別途 %UnSwizzleAt() していただく必要があります。
どのような構成にしているかによって変わってくるところはありますので、詳細についてのご相談があれば弊社サポートセンター(jpnsup@intersystems.com)までお気軽にお問い合わせください。 Kakechi さん
回答いただきありがとうございます。内容理解できました!私のケースでは対応必要そうです。
相談に乗っていただきありがとうございました! Kakechi さん
回答いただきありがとうございます。内容理解できました!私のケースでは対応必要そうです。
相談に乗っていただきありがとうございました!
記事
Toshihiko Minamoto · 2023年3月10日
糖尿病は、医学会でよく知られるいくつかのパラメーターから発見することが可能です。 この測定により、医学界とコンピューター化されたシステム(特に AI)を支援すべく、(米)国立糖尿病・消化器・腎疾病研究所(NIDDK)は、糖尿病の検出/予測における ML アルゴリズムをトレーニングするための非常に便利なデータセットを公開しました。 このデータセットは、ML の最大級のデータリポジトリとして最もよく知られている Kaggle に公開されています: https://www.kaggle.com/datasets/mathchi/diabetes-data-set。
糖尿病データセットには、以下のメタデータ情報が含まれています(出典: https://www.kaggle.com/datasets/mathchi/diabetes-data-set):
* Pregnancies: 妊娠回数
* Glucose: 経口ブドウ糖負荷試験における 2 時間後の血漿グルコース濃度
* BloodPressure: 拡張期血圧(mm Hg)
* SkinThickness: 上腕三頭筋皮下脂肪厚(mm)
* Insulin: 2 時間血清インスリン(mu U/ml)
* BMI: ボディマス指数(体重 kg/(身長 m)^2)
* DiabetesPedigreeFunction: 糖尿病血統要因(親族の糖尿病歴、およびこれらの親族と患者の遺伝的関係に関するいくつかのデータが提供されました。 この遺伝的影響の測定により、真性糖尿病の発症に伴う遺伝的リスクについての考えが得られました - 出典: https://machinelearningmastery.com/case-study-predicting-the-onset-of-diabetes-within-five-years-part-1-of-3/)
* Age: 年齢
* Outcome: クラス変数(0 または 1)
#### インスタンス数: 768
#### 属性数: 8 + class
#### 各属性について:(すべて数値)
1. 妊娠回数
2. 経口ブドウ糖負荷試験における 2 時間後の血漿グルコース濃度
3. 拡張期血圧(mm Hg)
4. 上腕三頭筋皮下脂肪厚(mm)
5. 2 時間血清インスリン(mu U/ml)
6. ボディマス指数(体重 kg/(身長 m)^2)
7. 糖尿病血統要因
8. 年齢
9. クラス変数(0 または 1)
#### 属性値の欠落: あり
#### クラス分布:(クラス値 1 は「糖尿病の検査で陽性」として解釈)
## Kaggle から糖尿病データを取得する
Kaggle の糖尿病データは、Health-Dataset アプリケーション(https://openexchange.intersystems.com/package/Health-Dataset)を使って IRIS テーブルに読み込めます。 これを行うには、module.xml プロジェクトから依存関係(Health Dataset 用の ModuleReference)を設定します。
Health Dataset アプリケーションリファレンスを含む Module.xml
<?
xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Document name="predict-diseases.ZPM">
<Module>
<Name>predict-diseases</Name>
<Version>1.0.0</Version>
<Packaging>module</Packaging>
<SourcesRoot>src/iris</SourcesRoot>
<Resource Name="dc.predict.disease.PKG"/>
<Dependencies>
<ModuleReference>
<Name>swagger-ui</Name>
<Version>1.*.*</Version>
</ModuleReference>
<ModuleReference>
<Name>dataset-health</Name>
<Version>*</Version>
</ModuleReference>
</Dependencies>
<CSPApplication
Url="/predict-diseases"
DispatchClass="dc.predict.disease.PredictDiseaseRESTApp"
MatchRoles=":{$dbrole}"
PasswordAuthEnabled="1"
UnauthenticatedEnabled="1"
Recurse="1"
UseCookies="2"
CookiePath="/predict-diseases"
/>
<CSPApplication
CookiePath="/disease-predictor/"
DefaultTimeout="900"
SourcePath="/src/csp"
DeployPath="${cspdir}/csp/${namespace}/"
MatchRoles=":{$dbrole}"
PasswordAuthEnabled="0"
Recurse="1"
ServeFiles="1"
ServeFilesTimeout="3600"
UnauthenticatedEnabled="1"
Url="/disease-predictor"
UseSessionCookie="2"
/>
</Module>
</Document>
</Export>
## 糖尿病を予測するための Web フロントエンドとバックエンドのアプリケーション
Open Exchange アプリのリンク(https://openexchange.intersystems.com/package/Disease-Predictor)に移動し、以下の手順に従います。
リポジトリを任意のローカルディレクトリに Git pull します。
$ git clone https://github.com/yurimarx/predict-diseases.git
このディレクトリで Docker ターミナルを開き、以下を実行します。
$ docker-compose build
IRIS コンテナを実行します。
$ docker-compose up -d
AI モデルをトレーニングするための Execute Query into Management Portal(http://localhost:52773/csp/sys/exp/%25CSP.UI.Portal.SQL.Home.zen?$NAMESPACE=USER)に移動します。
トレーニングに使用するビューを作成します。
CREATE VIEW DiabetesTrain AS SELECT Outcome, age, bloodpressure, bmi, diabetespedigree, glucose, insulin, pregnancies, skinthickness FROM dc_data_health.Diabetes
ビューを使用して AI モデルを作成します。
CREATE MODEL DiabetesModel PREDICTING (Outcome) FROM DiabetesTrain
モデルをトレーニングします。
TRAIN MODEL DiabetesModel
http://localhost:52773/disease-predictor/index.html に移動し、Disease Predictor フロントエンドを使用して、以下のように疾患を予測します。
## 背後の処理
### 糖尿病を予測するためのバックエンドのクラスメソッド
InterSystems IRIS では、前に作成されたモデルを使って予測するSELECT を実行することができます。
糖尿病を予測するためのバックエンドのクラスメソッド
/// Predict Diabetes
ClassMethod PredictDiabetes() As %Status
{
Try {
Set data = {}.%FromJSON(%request.Content)
Set qry = "SELECT PREDICT(DiabetesModel) As PredictedDiabetes, "
_"age, bloodpressure, bmi, diabetespedigree, glucose, insulin, "
_"pregnancies, skinthickness "
_"FROM (SELECT "_data.age_" AS age, "
_data.bloodpressure_" As bloodpressure, "
_data.bmi_" AS bmi, "
_data.diabetespedigree_" AS diabetespedigree, "
_data.glucose_" As glucose, "
_data.insulin_" AS insulin, "
_data.pregnancies_" As pregnancies, "
_data.skinthickness_" AS skinthickness)"
Set tStatement = ##class(%SQL.Statement).%New()
Set qStatus = tStatement.%Prepare(qry)
If qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
Set rset = tStatement.%Execute()
Do rset.%Next()
Set Response = {}
Set Response.PredictedDiabetes = rset.PredictedDiabetes
Set Response.age = rset.age
Set Response.bloodpressure = rset.bloodpressure
Set Response.bmi = rset.bmi
Set Response.diabetespedigree = rset.diabetespedigree
Set Response.glucose = rset.glucose
Set Response.insulin = rset.insulin
Set Response.pregnancies = rset.pregnancies
Set Response.skinthickness = rset.skinthickness
Set %response.Status = 200
Set %response.Headers("Access-Control-Allow-Origin")="*"
Write Response.%ToJSON()
Return 1
} Catch err {
write !, "Error name: ", ?20, err.Name,
!, "Error code: ", ?20, err.Code,
!, "Error location: ", ?20, err.Location,
!, "Additional data: ", ?20, err.Data, !
Return
}
}
これで、どの Web アプリケーションもこの予測を使用して、結果を表示できるようになりました。 predict-diseases アプリケーションのソースコードは、frontend フォルダをご覧ください。
記事
Mihoko Iijima · 2024年5月16日
この記事は、「インターシステムズ製品をバックアップする前に確認したいこと」に続く記事で、InterSystems製品のバックアップの手法の中の「オンラインバックアップ」の仕組みと、バックアップ・リストア手順について解説します。
オンラインバックアップは、InterSystems製品が用意するバックアップ機能を利用する方法で、バックアップ対象に設定した全データベースの使用済ブロックをバックアップする方法です。
InterSystems製品のデータベースには、サーバ側で記述したコード、テーブル定義/クラス定義、データ(レコード、永続オブジェクト、グローバル)が格納されていますので、これらすべてが1つのファイルにバックアップされます。
データ量が増えればバックアップファイルサイズも大きくなります。 また、データ量の増加に伴いバックアップ時間も長くなります。
バックアップ時間に制限のない環境や、ユーザからのアクセスがない環境(例:ディザスタリカバリの目的で配置しているミラーリングの非同期メンバ)のバックアップ方法としては最適ですが、バックアップ時間に制限がある場合は不向きです。
バックアップ時間をできるだけ短くしたい場合は、推奨方法である「外部バックアップ」や、手順が少し複雑になりますが「並行外部バックアップ」を取り入れるなどご検討ください。
外部バックアップもオンラインでバックアップが行えますが、バックアップ方法が異なります。
オンラインバックアップの仕組み
オンラインバックアップの種類
オンラインバックアップの事前準備
オンラインバックアップの取得方法
リストア方法
オンラインバックアップの仕組み
ユーザプロセスが停止しない仕組みを作るため、オンラインバックアップでは、3つのパスに分けてバックアップを実行しています。
データベースリストに複数のデータベースが含まれている場合は、パス毎にすべてのデータベースのバックアップを実行します。
✅最初のパス
バックアップ開始時点で使用されているすべてのブロックをバックアップします。開始以降に更新が発生したブロックは、バックアップ・ビットマップに記録して次のパス以降でバックアップできるようにします。
✅2番目~n番目のパス
バックアップ・ビットマップに記録された、前のパス開始以降に変更のあったブロックを処理します。このパスは変更ブロックが少なくなるまで実行されます。
✅最後のパス
前のパス開始以降に変更のあったブロックを処理しますが、最後のパスが完了するまでの間ライトデーモン(WRTDMN)を一時停止させてこれ以上の更新が発生しないようにしてすべての更新を処理します。
以下のログは、2つのデータベースをデータベースリストに設定したときのフルバックアップ時のログです。
最初のパス
*** The time is: 2024-04-25 09:42:06 ***
InterSystems IRIS Backup Utility
--------------------------------
Performing a Full backup.
Backing up to device: /usr/irissys/mgr/backup/FullDBList_20240425_001.cbk
Description
Full backup of all databases that are in the backup database list.
Backing up the following directories:
/usr/irissys/mgr/t1/
/usr/irissys/mgr/user/
Journal file switched to:
/usr/irissys/mgr/journal/20240425.002
Starting backup pass 1
Backing up /usr/irissys/mgr/t1/ at 04/25/2024 09:42:07
Copied 14083 blocks in 0.287 seconds
Finished this pass of copying /usr/irissys/mgr/t1/
Backing up /usr/irissys/mgr/user/ at 04/25/2024 09:42:07
Copied 194 blocks in 0.007 seconds
Finished this pass of copying /usr/irissys/mgr/user/
Backup pass 1 complete at 04/25/2024 09:42:07
それぞれデータベースの全てのバックアップ対象ブロックをバックアップファイルコピーしていることがわかります。
また、バックアップを開始する前にジャーナルファイルを切り替えていることが確認できます。
次に、2番目のパスです。
Starting backup pass 2
Backing up /usr/irissys/mgr/t1/ at 04/25/2024 09:42:08
Copied 1 blocks in 0.001 seconds
Finished this pass of copying /usr/irissys/mgr/t1/
Backing up /usr/irissys/mgr/user/ at 04/25/2024 09:42:08
Copied 1 blocks in 0.002 seconds
Finished this pass of copying /usr/irissys/mgr/user/
Backup pass 2 complete at 04/25/2024 09:42:08
2番目以降も、それぞれのデータベースの全ての変更ブロックをバックアップファイルにコピーしていることがわかります。
次は、最後のパスです。
Starting backup pass 3
Journal file '/usr/irissys/mgr/journal/20240425.002' and the subsequent ones are required for recovery purpose if the backup were to be restored
Journal marker set at
offset 198264 of /usr/irissys/mgr/journal/20240425.002
- This is the last pass - Suspending write daemon
Backing up /usr/irissys/mgr/t1/ at 04/25/2024 09:42:10
Copied 1 blocks in 0.002 seconds
Finished this pass of copying /usr/irissys/mgr/t1/
Backing up /usr/irissys/mgr/user/ at 04/25/2024 09:42:10
Copied 1 blocks in 0.002 seconds
Finished this pass of copying /usr/irissys/mgr/user/
Backup pass 3 complete at 04/25/2024 09:42:10
***FINISHED BACKUP***
Global references are enabled.
Backup complete.
最後のパスでは、- This is the last pass - Suspending write daemon のログがあり、ライトデーモン(WRTDMN)を一次停止させ全てのブロックをバックアップしていることがわかります。
3つのパスは以下で解説するオンラインバックアップの種類に関わらず必ず実行されます。
オンラインバックアップの種類
オンラインバックアップには、フルバックアップ/累積バックアップ/差分バックアップの3種類のバックアップ方法があります。
バックアップ時間については、フルバックアップよりも累積バックアップ、累積バックアップよりも差分バックアップが短くなりますが、累積バックアップと差分バックアップを行うためには、必ず事前にフルバックアップを取得する必要があります。
なるべくバックアップ時間が短くなるように3種類のバックアップ方法を組み合わせて利用する事もできます。
累積バックアップ・差分バックアップの違いや組み合わせ例については、コミュニティの記事「累積バックアップと差分バックアップの違いについて」をご参照ください。
オンラインバックアップの事前準備
データベースのバックアップリストを作成する必要があります。
作成は、管理ポータルから、またはAPIから行えます。
✅管理ポータルから作成する場合
管理ポータル > [システム管理] > [構成] > [データベースバックアップ] > [データベース・バックアップ・リスト] で設定します。
✅APIで作成する場合
BackUp.GeneralのAddDatabaseToList()/ClearDatabaseList()/RemoveDatabaseFromList()を利用します。
管理ポータルのデータベースリストを一旦クリアし、データベースUSERとT1を追加する例は以下の通りです。(%SYSネームスペースで実行します)
実行が成功するとステータスOKとして1が返ります($$$OK)。
// 既存のデータベースリストをクリアする
set status=##class(Backup.General).ClearDatabaseList()
write status
// T1とUSERをデータベースリストに追加する。
set status=##class(Backup.General).AddDatabaseToList("USER")
write status
set status=##class(Backup.General).AddDatabaseToList("T1")
write status
オンラインバックアップの取得方法
管理ポータルには、手動で行うバックアップメニューとタスクスケジュールに設定しておけるバックアップメニューがあります。
プログラムから実行する場合は、BACKUP^DBACKルーチンを使用します。
以下順序で解説します。
1. 管理ポータルのバックアップメニュー
2. 管理ポータルのタスクスケジュール
3. ^BACKUPルーチン
4. BACKUP^DBACKルーチン
※上記方法で実行する前にバックアップ対象データベースを「バックアップリスト」に指定する必要があります。詳しくはオンラインバックアップの事前準備をご参照ください。
1.管理ポータルのバックアップメニュー
管理ポータル > [システムオペレーション] > [バックアップ] からバックアップを実行できます。
メニューの「すべてのデータベースのフルバックアップ」は、インストール環境のすべてのデータベースをバックアップします。
データベースリストで指定したデータベースのバックアップを行う場合は「フルバックアップのリスト」のメニューを選択してください。
以下、実際のバックアップを実行する画面です。画面内の「バックアップを保存するデバイス」に出力可能なディレクトリを指定してから実行してください。
バックアップログは「ログファイル」の場所に配置されます。
2.管理ポータルのタスクジュール
新しいタスクを作成するときに、オンラインバックアップ用タイプを指定してタスクを設定します。
管理ポータル > [システムオペレーション] > [タスクマネージャ] > [新しいタスク]
タイプは以下の通りです。
リストデータベースのインクリメンタルバックアップ
リストデータベースのフルバックアップ
リストデータベースの累積差分バックアップ
後は日時指定を行えば、指定の時刻に対象のバックアップを開始できます。
3.^BACKUPルーチン
システムルーチン^BACKUPは%SYSネームスペースに移動して実行します。
【注意】 バックアップ実行結果のログ出力を指定できないため、画面ログなどをご利用ください。
メニュー詳細についてはドキュメント「^BACKUP によるバックアップおよびリストアのタスクの実行」をご参照ください。
以下ルーチン実行例です。
Start the Backup (y/n)? => y 以降は前述:オンラインバックアップの仕組み で説明した各バックアップパスのログが出力されます。
%SYS>do ^BACKUP
1) Backup
2) Restore ALL
3) Restore Selected or Renamed Directories
4) Edit/Display List of Directories for Backups
5) Abort Backup
6) Display Backup volume information
7) Monitor progress of backup or restore
Option? 1
*** The time is: 2024-04-25 17:18:19 ***
InterSystems IRIS Backup Utility
--------------------------------
What kind of backup:
1. Full backup of all in-use blocks
2. Incremental since last backup
3. Cumulative incremental since last full backup
4. Exit the backup program
1 => 1
Specify output device (type STOP to exit)
Device: /usr/irissys/mgr/backup/FullDBList_20240425_001.cbk => /usr/irissys/mgr/backup/FullDBList_20240425_002.cbk
Backing up to device: /usr/irissys/mgr/backup/FullDBList_20240425_002.cbk
Description: ^BACKUPルーチンを利用したフルバックアップの実行
Backing up the following directories:
/usr/irissys/mgr/t1/
/usr/irissys/mgr/user/
Start the Backup (y/n)? => y
Journal file switched to:
/usr/irissys/mgr/journal/20240425.003
Starting backup pass 1
Backing up /usr/irissys/mgr/t1/ at 04/25/2024 17:19:11
Copied 14083 blocks in 0.288 seconds
Finished this pass of copying /usr/irissys/mgr/t1/
Backing up /usr/irissys/mgr/user/ at 04/25/2024 17:19:12
Copied 371 blocks in 0.017 seconds
Finished this pass of copying /usr/irissys/mgr/user/
Backup pass 1 complete at 04/25/2024 17:19:12
Starting backup pass 2
Backing up /usr/irissys/mgr/t1/ at 04/25/2024 17:19:14
Copied 1 blocks in 0.002 seconds
Finished this pass of copying /usr/irissys/mgr/t1/
Backing up /usr/irissys/mgr/user/ at 04/25/2024 17:19:14
Copied 1 blocks in 0.002 seconds
Finished this pass of copying /usr/irissys/mgr/user/
Backup pass 2 complete at 04/25/2024 17:19:14
Starting backup pass 3
Journal file '/usr/irissys/mgr/journal/20240425.002' and the subsequent ones are required for recovery purpose if the backup were to be restored
Journal marker set at
offset 197596 of /usr/irissys/mgr/journal/20240425.003
- This is the last pass - Suspending write daemon
Backing up /usr/irissys/mgr/t1/ at 04/25/2024 17:19:15
Copied 1 blocks in 0.003 seconds
Finished this pass of copying /usr/irissys/mgr/t1/
Backing up /usr/irissys/mgr/user/ at 04/25/2024 17:19:15
Copied 1 blocks in 0.005 seconds
Finished this pass of copying /usr/irissys/mgr/user/
Backup pass 3 complete at 04/25/2024 17:19:15
***FINISHED BACKUP***
Global references are enabled.
Backup complete.
1) Backup
2) Restore ALL
3) Restore Selected or Renamed Directories
4) Edit/Display List of Directories for Backups
5) Abort Backup
6) Display Backup volume information
7) Monitor progress of backup or restore
Option? //Enter押下
%SYS>
4.BACKUP^DBACK
システムルーチン^DBACKルーチンのBACKUPプロシージャを利用して、プログラムからバックアップを実行することができます。
引数詳細はドキュメント「BACKUP^DBACK」をご参照ください。
✅フルバックアップ
第2引数はフルバックアップのタイプである"F"を指定します。
第4引数はバックアップファイル名
第6引数はバックアップログのファイル名
第5、8、9はバックアップ開始後にジャーナルを切り替えるための指定のため"Y"(切り替える)を推奨しています。
第7引数は、実行時のカレントデバイスへの出力指定
set modori=$$BACKUP^DBACK("","F","フルバックアップの実行","/usr/irissys/mgr/backup/FullBackUp-DBACK-20240425.cbk","Y","/usr/irissys/mgr/backup/FullBackUp-DBACK-20240425.log","QUIET","Y","Y")
✅累積バックアップ
フルバックアップの実行例の第2引数に"C"を指定します。
✅差分バックアップ
フルバックアップの実行例の第2引数に"I"を指定します。
リストア方法
リストアは、システムルーチン^DBRESTを利用する方法と、プログラムから実行する方法を選択できます。
リストアはリストア時の確認項目が多いため、テスト環境などでリストアテストを実施いただくことを強く推奨します。
リストアにかかる時間についても、HW構成、リストア対象データベース数、データ量に依存するためリストアテストを行った際の計測値から予測いただくこととなります。
それでは、具体的な方法をご説明します。
システムルーチンを利用したリストア(手動)
プログラムによるリストア
システムルーチンを利用したリストア(手動)
システムルーチン^DBRESTを利用します。
または、^BACKUPルーチンの 2) Restore ALL または 3) Restore Selected or Renamed Directories からも実行できます。
%SYS>do ^DBREST
Cache DBREST Utility
Restore database directories from a backup archive
Restore: 1. All directories
2. Selected and/or renamed directories
3. Display backup volume information
4. Exit the restore program
1 =>
リストアメニューについては、
1.All directories
バックアップファイルに含まれるデータベースすべてをリストアします。
画面表示例はドキュメント:^DBRESTによるすべてのデータベースのリストアをご参照ください。
2.Selected and/or renamed directories
バックアップファイルに含まれる一部のデータベースだけをリストアしたい場合、また、バックアップ時点のデータベースディレクトリと異なるディレクトリにリストアを行う場合に使用します。
参考ドキュメント:^DBREST による選択したデータベースまたは名前を変更したデータベースのリストア
以下例では、「2. Selected and/or renamed directories」を使用して、バックアップファイルに含まれるデータベースを選択し、別ディレクトリのデータベースにリストアする手順を説明します。
リストア実行例(データベースディレクトリ変更)
例ではバックアップファイルに以下データベースが含まれています。
USERデータベース= /usr/irissys/mgr/user
T1データベース=/usr/irissys/mgr/t1
以下例では、T1データベースのあるディスクが壊れたことを想定し、リストア時のT1データベースディレクトリがバックアップ時点とは異なるディレクトリにリストアしなくてはならない場合の流れで解説します。
なお、バックアップファイルのリストア後、ジャーナルファイルを利用したリストアも行う必要がありますので以下の流れで試していきます。
1、バックアップ前にT1データベースに任意データ登録する(^prebackup=1)
2、フルバックアップを実行する
3、切り替わったジャーナルファイル名を確認する
4、バックアップ後だとわかる任意データをT1データベースに登録する(^postbackup=1)
5、ジャーナルファイルに 4で登録した情報が含まれているか確認する
6、T1データベース(/usr/irissys/mgr/t1)を削除
7、T1データベースを別ディレクトリ(/usr/irissys/mgr/t1rest)に再作成
8、バックアップからのリストアを実行する
T1データベースのみリストアするように指定します。
バックアップ時点のディレクトリ:/usr/irissys/mgr/t1
リストア時に指定するディレクトリ:/usr/irissys/mgr/t1rest
9、^prebackup=1 が戻ることを確認する
10、ジャーナルリストアを実行
T1データベースのみリストアするように指定します。
バックアップ時点のディレクトリ:/usr/irissys/mgr/t1
リストア時に指定するディレクトリ:/usr/irissys/mgr/t1rest
11、^postbackup=1 が戻ることを確認する
1、バックアップ前にT1データベースに任意データ登録する(^prebackup=1)
ネームスペースT1に接続し、以下実行します。
Linuxやコンテナを利用されている場合は、iris session インスタンス名 -U T1でログインすると簡単です。
set $namespace="T1"
set ^prebackup=1
管理ポータルでデータを確認します。
[システムエクスプローラ] > [グローバル] > T1ネームスペース選択
グローバル変数名が多く一覧される場合、画面左端の「フィルタ」の「グローバル名」に pre* と書くとフィルタされた結果が表示されます。
2、フルバックアップを実行する
オンラインバックアップの取得方法のいずれかの方法を利用してフルバックアップを実行します。
例では管理ポータルメニューを利用しています。
以下の例で使用するバックアップファイル名は「/usr/irissys/mgr/Backup/FullDBList_20240426_001.cbk」です。
3、切り替わったジャーナルファイル名を確認する
管理ポータル > [システムオペレーション] > [ジャーナル] 「バックアップにより」切り替わったジャーナルファイルを確認します。
以下の例でジャーナルリストアの開始ファイルとして指定するファイル名は「/usr/irissys/mgr/journal/20240426.002」です。
4、バックアップ後だとわかる任意データをT1データベースに登録する(^postbackup=1)
ネームスペースT1に接続し、以下実行します。
Linuxやコンテナを利用されている場合は、iris session インスタンス名 -U T1でログインすると簡単です。
set $namespace="T1"
set ^postbackup=1
5、ジャーナルファイルに 4で登録した情報が含まれているか確認する
管理ポータル > [システムオペレーション] > [ジャーナル] で現在のジャーナルファイルを開き、^postbackupが記録されているか確認します。
画面右端のデータベースディレクトリ名を確認し、「/usr/irissys/mgr/t1」で記録されていることを確認します。
6、T1データベース(/usr/irissys/mgr/t1)を削除
管理ポータルメニューから削除します。
稼働中に削除する場合、一旦データベースをディスマウントすることをお勧めします。
管理ポータル > [システムオペレーション] > [データベース] > T1を選択 > ディスマウントボタンをクリック
ディスマウント後、構成メニューに移動しデータベースを削除します。
管理ポータル > [システム管理] > [構成] > [システム構成] > [ローカルデータベース] > T1を削除
この削除時に ”チェックしたネームスペースを削除します” の T1 にチェックを入れてT1ネームスペースも削除します。
7、T1データベースを別ディレクトリ(/usr/irissys/mgr/t1rest)に再作成
管理ポータルでT1ネームスペース、データベースを作成します。
このときのデータベースは最初に作成したディレクトリとは異なるディレクトリで作成します。
例では、/usr/irissys/mgr/t1rest としています。
8、バックアップからのリストアを実行する
システムルーチン^DBRESTの例でご紹介します。
InterSystems製品にログインし、%SYSネームスペースに移動します。
Linuxやコンテナの場合はは、iris session インスタンス名 -U %SYS で%SYSネームスペースにログインできます。
set $namespace="%SYS"
do ^DBREST
画面で指定する内容は以下の通りです。
%SYS>do ^DBREST
Cache DBREST Utility
Restore database directories from a backup archive
Restore: 1. All directories
2. Selected and/or renamed directories
3. Display backup volume information
4. Exit the restore program
1 => 2
バックアップファイルに含まれるデータベースを選択し、さらにディレクトリをリダイレクトしてリストアを実行するため、2を選択します。
続いて以下質問されます。
Do you want to set switch 10 so that other processes will be
prevented from running during the restore? Yes =>
リストア実行中 switch 10 を設定したいか?と聞かれています。switch 10 を設定するとカレントプロセス以外の他のプロセスからのRead/Writeを禁止します。
リストア実行中に他プロセスからのデータ参照・更新を防ぎたいときはYes(デフォルト)を指定します。(推奨)
この回答をYesにした場合、リストアが完了するまで管理ポータルを使用したり、新規のログインなどはできなくなります。
続いて、バックアップファイルを指定します。管理ポータルからバックアップを行ったため、バックアップ履歴より直近のフルバックアップファイル名を表示しています。
ディレクトリに変更がないか確認し正しい場合ははEnterを押下します。
異なる場合は、フルパスでフルバックアップのファイル名を指定します。
最終行にリストアを開始したいか?と質問されるので、Enter(またはYes)を押下します。
Specify input file for volume 1 of backup 1
(Type STOP to exit)
Device: /usr/irissys/mgr/Backup/FullDBList_20240426_001.cbk =>
This backup volume was created by:
IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2024.1
The volume label contains:
Volume number 1
Volume backup APR 26 2024 11:36AM Full
Previous backup APR 25 2024 05:34PM Full
Last FULL backup APR 25 2024 05:34PM
Description Full backup of all databases that are in the backup database list.
Buffer Count 0
Is this the backup you want to start restoring? Yes =>
続いて、バックアップファイルに含まれるデータベースディレクトリが出力されるので、同じディレクトリにリストアする場合はEnterを押下します。
今回は異なるディレクトリにリダイレクトしたいため、/usr/irissys/mgr/t1restを入力しています。
次に、もう1つのバックアップ対象である /usr/irissys/mgr/user のディレクトリが表示されます。リストア対象外に設定したいので、X(大文字)を入力します。
「(ここまで入力した)ディレクトリリストを変更したいか?」と質問されるので、変更不要の場合は、Noを入力します。
For each database included in the backup file, you can:
-- press RETURN to restore it to its original directory;
-- type X, then press RETURN to skip it and not restore it at all.
-- type a different directory name. It will be restored to the directory
you specify. (If you specify a directory that already contains a
database, the data it contains will be lost).
/usr/irissys/mgr/t1/ => /usr/irissys/mgr/t1rest
/usr/irissys/mgr/user/ => X
Do you want to change this list of directories? No => no
リストアによりデータベースをオーバーライドするけど良いか?と質問されます。 リストアを開始してよい場合は、yesを入力します。
Restore will overwrite the data in the old database. Confirm Restore? No => yes
リストアが開始されます。
/usr/irissys/mgr/t1 を /usr/irissys/mgr/t1rest にリストアし、/usr/irissys/mgr/userはスキップされることが出力されています。
***Restoring /usr/irissys/mgr/t1/ to /usr/irissys/mgr/t1rest/ at 13:39:00
14085 blocks restored in 0.4 seconds for this pass, 14085 total restored.
Starting skip of /usr/irissys/mgr/user/.
skipped 371 blocks in .011176 seconds.
***Restoring /usr/irissys/mgr/t1/ to /usr/irissys/mgr/t1rest/ at 13:39:00
1 blocks restored in 0.0 seconds for this pass, 14086 total restored.
Starting skip of /usr/irissys/mgr/user/.
skipped 1 blocks in .000005 seconds.
***Restoring /usr/irissys/mgr/t1/ to /usr/irissys/mgr/t1rest/ at 13:39:00
1 blocks restored in 0.0 seconds for this pass, 14087 total restored.
Starting skip of /usr/irissys/mgr/user/.
skipped 1 blocks in .000005 seconds.
今回のバックアップファイルに続きがあるかどうか確認されます。ない場合は STOP を入力します。
Specify input file for volume 1 of backup following APR 26 2024 11:36AM
(Type STOP to exit)
Device: STOP
リストアしたいバックアップファイルがあるか再度確認されます。
利用例として、フルバックアップをリストア後に続けてインクリメンタル(差分)バックアップがあり、続けてリストアしたい場合にファイル名を指定できます。
リストアしたいファイルがある場合はYES(デフォルト)、ない場合は、Noを入力します。
Do you have any more backups to restore? Yes => no
Mounting /usr/irissys/mgr/t1rest/
/usr/irissys/mgr/t1rest/ ... (Mounted)
/usr/irissys/mgr/t1rest がマウントされました。
通常、リストア対象データベースにはジャーナルファイルのリストアも行いますが、一旦、バックアップ時点に戻ったかどうか確認のため、練習の流れでは 4を選択し、ユーティリティを一旦終了しています。
Restoring a directory restores the globals in it only up to the
date of the backup. If you have been journaling, you can apply
journal entries to restore any changes that have been made in the
globals since the backup was made.
What journal entries do you wish to apply?
1. All entries for the directories that you restored
2. All entries for all directories
3. Selected directories and globals
4. No entries
Apply: 1 => 4
%SYS>
リストア中 switch 10 の設定により、カレントプロセス以外のREAD/WRITEが禁止されます。
リストアを終了する場合、必ずシステムルーチンを終了し、%SYSのプロンプトが表示されている状態に戻してください。
9、^prebackup=1 が戻ることを確認する
管理ポータル > [システムエクスプローラ] > [グローバル] > T1ネームスペース選択
^prebackupは表示されますが、^postbackupがまだ戻っていないことを確認します。
10、ジャーナルリストアを実行
ジャーナルリストアの流れは、「外部バックアップの仕組みとバックアップとリストア方法について:10、ジャーナルリストア」をご参照ください。
11、^postbackup=1 が戻ることを確認する
管理ポータル > [システムエクスプローラ] > [グローバル] > T1ネームスペース選択
^postbackupが存在するか確認します。
プログラムによるリストア
✅EXTALL^DBREST
バックアップファイルに含まれるすべてのデータベースをリストアできます。
また、例では、バックアップファイルに含まれるデータベースのバックアップファイルからのリストアとジャーナルファイルのリストアを行っています。
引数詳細については、ドキュメントの^DBRESTによる自動リストアをご参照ください。
例では、以下の引数を指定します。
第1引数:1を指定(非インタラクティブモードであることを指定)
第2引数:0を指定(リストア処理中に更新を許可しない)
第3引数:バックアップファイル名を指定
第4引数:このシナリオでは指定なし
第5引数:1を指定(ジャーナルをリストアするためのオプションで、1はバックアップをリストアしたすべてのディレクトリを指定)
第6引数:バックアップリストア後にリストアしたいジャーナルファイル名
実行例は以下の通りです。
%SYS>do EXTALL^DBREST(1,0,"/usr/irissys/mgr/Backup/FullDBList_20240426_001.cbk",,1,"/usr/irissys/mgr/journal/20240426.002",1)
The following directories will be restored:
/usr/irissys/mgr/t1/ =>
/usr/irissys/mgr/user/ =>
Expanding /usr/irissys/mgr/t1/ from 1 MB to 114 MB
***Restoring /usr/irissys/mgr/t1/ at 14:45:30
14085 blocks restored in 0.8 seconds for this pass, 14085 total restored.
***Restoring /usr/irissys/mgr/user/ at 14:45:31
371 blocks restored in 0.0 seconds for this pass, 371 total restored.
***Restoring /usr/irissys/mgr/t1/ at 14:45:31
1 blocks restored in 0.0 seconds for this pass, 14086 total restored.
***Restoring /usr/irissys/mgr/user/ at 14:45:31
1 blocks restored in 0.0 seconds for this pass, 372 total restored.
***Restoring /usr/irissys/mgr/t1/ at 14:45:31
1 blocks restored in 0.0 seconds for this pass, 14087 total restored.
***Restoring /usr/irissys/mgr/user/ at 14:45:31
1 blocks restored in 0.0 seconds for this pass, 373 total restored.
Mounting /usr/irissys/mgr/t1/
/usr/irissys/mgr/t1/ ... (Mounted)
Mounting /usr/irissys/mgr/user/
/usr/irissys/mgr/user/ ... (Mounted)
We know something about where journaling was at the time of the backup:
0: offset 196976 in /usr/irissys/mgr/journal/20240426.002
/usr/irissys/mgr/journal/20240426.002
Journal reads completed. Applying changes to databases...
20.00% 40.00% 60.00% 80.00% 100.00%100.00%
***Journal file finished at 14:45:33
✅EXTSELCT^DBREST
バックアップファイルに含まれるデータベースディレクトリを指定したリストア、またディレクトリ先を指定したリストアが行えます。
以下の実行例は、/usr/irissys/mgr/t1 で記録されていた情報を /usr/irissys/mgr/t1rest にリストアしています。
このラベル名からのジャーナルリストアは、リダイレクト先が指定できません。以下の例では、バックアップからのリストアだけで終了するようにしています。
例では、以下の引数を指定します。
第1引数:1を指定(非インタラクティブモードであることを指定)
第2引数:0を指定(リストア処理中に更新を許可しない)
第3引数:バックアップファイル名を指定
第4引数:リストア先ディレクトリを含むファイル名 ファイルには、以下指定します。
ソースディレクトリ,ターゲットディレクトリ,ターゲットディレクトリが存在しないとき作成するかどうかのY/Nのどちらか
第5引数:4を指定(ジャーナルリストア指定しない)
事前に第4引数のファイルを準備します。ファイルには以下の記載をしています。
$ cat restdir.txt
/usr/irissys/mgr/t1/,/usr/irissys/mgr/t1rest/,N
以下実行例です。バックアップファイルから /usr/irissys/mgr/t1 を /usr/irissys/mgr/t1rest にリダイレクトしてリストアしていますが、ジャーナルリストアは行われていません。
%SYS>do EXTSELCT^DBREST(1,0,"/usr/irissys/mgr/Backup/FullDBList_20240426_001.cbk","/usr/irissys/mgr/Backup/restdir.txt",4)
***Restoring /usr/irissys/mgr/t1/ to /usr/irissys/mgr/t1rest/ at 15:06:39
14085 blocks restored in 0.2 seconds for this pass, 14085 total restored.
Starting skip of /usr/irissys/mgr/user/.
skipped 371 blocks in .006246 seconds.
***Restoring /usr/irissys/mgr/t1/ to /usr/irissys/mgr/t1rest/ at 15:06:39
1 blocks restored in 0.0 seconds for this pass, 14086 total restored.
Starting skip of /usr/irissys/mgr/user/.
skipped 1 blocks in .00001 seconds.
***Restoring /usr/irissys/mgr/t1/ to /usr/irissys/mgr/t1rest/ at 15:06:39
1 blocks restored in 0.0 seconds for this pass, 14087 total restored.
Starting skip of /usr/irissys/mgr/user/.
skipped 1 blocks in .000008 seconds.
Mounting /usr/irissys/mgr/t1rest/
/usr/irissys/mgr/t1rest/ ... (Mounted)
[Journal not applied to any directory]
%SYS>
現時点では、/usr/irissys/mgr/t1rest のデータベースには ^postbackupが存在しませんん。
選択したデータベースディレクトリをリダイレクトしながらジャーナルリストアを行うには、Journal.Restoreクラスを利用します。
ジャーナルファイルのリストア
ジャーナルファイルに記録されている /usr/irissys/mgr/t1 の情報を /usr/irissys/mgr/t1rest にリストアする例でご紹介します。
サンプルコード:ZRestore.Journal
(メソッド、プロパティの使い方についてはサンプルコードのコメント文をご確認ください。)
Class ZRestore.Journal
{
ClassMethod RedirectTest() As %Status
{
#dim ex As %Exception.AbstractException
set status=$$$OK
try {
#;ジャーナルリストア用インスタンス生成
set jrnrest=##class(Journal.Restore).%New()
#;リストアのジャーナルファイルを指定します
#; CurrentFileは以下メソッドで取得可
#; ##class(%SYS.Journal.System).GetCurrentFile().Name
set jrnrest.FirstFile="/usr/irissys/mgr/journal/20240426.002"
#;LastFileの指定がない場合は最後のファイルまでリストアします
set jrnrest.LastFile="/usr/irissys/mgr/journal/20240426.002"
#;ジャーナルディレクトリの設定(カレントインスタンスのジャーナルを利用する場合の例)
#; UseJournalLocation() ジャーナルディレクトリを指定するメソッド
#; 現在のプライマリディレクトリの場所を指定する場合は、メソッドで場所を特定できる
set location1=##class(%SYS.Journal.System).GetPrimaryDirectory()
do jrnrest.UseJournalLocation(location1)
set location2=##class(%SYS.Journal.System).GetAlternateDirectory()
do jrnrest.UseJournalLocation(location2)
#; ソースDBとターゲットDBの指定
#; ディレクトリは全て小文字で記載する+末尾のパスのマーク必須
set source="/usr/irissys/mgr/t1/"
set target="/usr/irissys/mgr/t1rest/"
#; リストア対象データベースの指定
$$$ThrowOnError(jrnrest.SelectUpdates(source))
#; リダイレクト先の指定
$$$ThrowOnError(jrnrest.RedirectDatabase(source,target))
#; ジャーナルの整合性チェック
$$$ThrowOnError(jrnrest.CheckJournalIntegrity(1))
#; リストア実行
$$$ThrowOnError(jrnrest.Run())
}
catch ex {
set status=ex.AsStatus()
}
return status
}
}
サンプルコード実行例
%SYS>set status=##class(ZRestore.Journal).RedirectTest()
Journal file being applied: /usr/irissys/mgr/journal/20240426.002
/usr/irissys/mgr/journal/20240426.002
2.91% 100.00%
[Journal restore completed at 20240426 15:38:32]
The following databases have been updated:
1. /usr/irissys/mgr/t1rest/
%SYS>
記事
Hiroshi Sato · 2020年7月27日
初めに
Caché ActiveX Bindingは、Visual Basicでクライアント・サーバー型のアプリケーション開発を支援するためにInterSystemsが提供してきたツールです。
CacheActiveX.dllとCacheObject.dllの2種類のバージョンが存在します。
IRISでは、CacheActiveX.dllは動作可能です。
CacheObject.dllはサポートしていません。
いずれにしろ誕生から既に20年以上が経過した非常に古いテクノロジーでマイクロソフト社も非推奨の古い規格ですので、今後も使い続けるのは得策ではありません。
Caché ActiveX Bindingの機能はIRISに用意されている.Net Native APIと.Net Managed Providerの機能を使って書き換え可能です。
ここでは、Caché ActiveX Bindingを使って書かれていたサンプルアプリケーションをIRISで動作するように移植した作業内容について解説します。
このサンプルは、以下のgithubサイトから入手可能です。
ADBKサンプル
ADBKアプリケーション
このサンプルアプリケーションは、20年以上も前にVB6サンプルとして作成されました。
VB6プロジェクトを.Netプロジェクトに変換
この作業は、VisM.OCXを利用したアプリケーションをIRISに移行する方法という記事にも同じ内容が記載されていますので、そちらをご参考ください。VisM.OCXを利用したアプリケーションをIRISに移行する方法
CacheObject.dllの参照
Visual Studioを起動すると右側に表示されるソリューションエクスプローラーから参照の部分を開きます。
CacheObjectというのが見えるので、それを選択して、右クリックのメニューから削除をクリックします。
アプリケーション修正
それでは、IRISで動作するようにサンプルアプリケーションを修正していきましょう。
参照の追加
IRIS Native APIと.Net Managed Providerを利用するためにIRISの.Netライブラリーの参照を追加します。
プロジェクトメニューから参照の追加をクリックして参照ボタンを押して以下のファイルを選択します。
c:\intersystems\IRIS\dev\dotnet\bin\v4.5
InterSystems.Data.Gateway64.exe
InterSystems.Data.IRISClient.dll
ADBKMain.vbの修正
それではADBKMain.vbの修正を行いましょう。
先ほど参照設定したライブラリーをImportします。
Option Explicit Onの後ろに以下の行を追加します。
Imports InterSystems.Data.IRISClientImports InterSystems.Data.IRISClient.ADO
class宣言の後ろの変数宣言の所を以下のように変更します。
Dim iris As IRIS Dim iris_object As IRISObject Dim iris_conn As IRISConnection = New IRISConnection 'Dim m_factory As CacheObject.Factory 'Dim m_object As CacheObject.ObjInstance 'Const m_classname As String = "User.ADBK" Const iris_classname As String = "User.ADBK"
CacheObjectの関連の変数をIRIS関連の変数に置き換えます。IRISの場合は、サーバーとの通信のコネクションが別オブジェクトになっているため追加の変数設定が必要です。
次にフォームのロード処理の修正を行います。
'm_factory = CreateObject("CacheObject.Factory") iris_conn.ConnectionString = "Server = localhost; Log File=cprovider.log;Port=1972; Namespace=USER; Password = SYS; User ID = _system;" iris_conn.Open() iris = IRIS.CreateIRIS(iris_conn)
IRISの場合は、まずコネクションオブジェクトを作成する必要があります。そして、そのConnectionStringプロパティにサーバー接続に必要な情報を設定します。次にOpen()メソッドで接続を確立します。接続が確立したら、IRISのインスタンスを生成します。CacheObjectではFactoryと呼んでいたものと同等のものとなります。
'sdir = m_factory.ConnectDlg 'If sdir <> "" Then 'm_factory.Connect(sdir)
CacheObjectの場合は、接続情報が指定されていない場合は、接続情報を尋ねるダイアログボックスが表示されていましたが、IRISのコネクションオブジェクトにはその機能がないので、すべてコメントアウトします。
次に新規ボタンが押された時の処理を変更しましょう。
CmdNew_Clickの処理になります。
以下のように書き換えます。
iris_object = iris.ClassMethodObject(iris_classname, "%New") If iris_object Is Nothing Then MsgBox("新しいオブジェクトを作成できません。") End If 'm_object = m_factory.New(m_classname) 'If m_object Is Nothing Then 'MsgBox("新しいオブジェクトを作成できません。") 'End If
CacheObjectの場合は、FactoryのNewメソッドで新しいオブジェクトの生成を行いましたが、IRISでは、ClassMethodObjectメソッドで第一パラメータで指定したクラスのクラスメソッド%Newメソッドを呼び出すように変更します。%Newメソッドは、OREFを返すので、ClassMethodObjectメソッドを使います。ClassMethodXXXは、戻り値のタイプ別に用意されています。
次に保存ボタンが押された時の処理を変更します。
CmdUpdate_Clickの処理になります。
'm_object.sys_Save() iris_object.Invoke("%Save")
インスタンスメソッドを呼び出す方法は、InVokeメソッドでパラメータに呼び出すメソッドの名前を指定します。次に削除ボタンが押された時の処理を変更します。
CmdDelete_Clickの処理になります。
'm_object.sys_DeleteId(id) iris_object.Invoke("%DeleteId", id)
同様にInVokeメソッドを呼び出すように変更します。
次に検索ボタンが押された時の処理を変更します。
CmdFind_Clickの処理になります。
'm_object = FindByName.ShowDialog_Renamed(m_factory) iris_object = FindByName.ShowDialog_Renamed(iris, iris_conn)
検索用のダイアログを表示する際に渡すパラメータを追加する必要がありました。コネクションオブジェクトを追加で渡す必要があります。また戻り値もIris_objectに変更します。次に CmdUpdate_Clickの処理を変更します。
'UPGRADE_WARNING: オブジェクト m_object.ANAME の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 'm_object.ANAME = TxtNAME.Text iris_object.Set("ANAME", TxtNAME.Text) 'UPGRADE_WARNING: オブジェクト m_object.ASTREET の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 'm_object.ASTREET = TxtADDRESS.Text iris_object.Set("ASTREET", TxtADDRESS.Text) 'UPGRADE_WARNING: オブジェクト m_object.AZIP の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 'm_object.AZIP = TxtZIP.Text iris_object.Set("AZIP", TxtZIP.Text) 'UPGRADE_WARNING: オブジェクト m_object.ABTHDAY の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 'm_object.ABTHDAY = TxtDOB.Text iris_object.Set("ABTHDAY", TxtDOB.Text) 'UPGRADE_WARNING: オブジェクト m_object.APHHOME の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 'm_object.APHHOME = TxtTELH.Text iris_object.Set("APHHOME", TxtTELH.Text) 'UPGRADE_WARNING: オブジェクト m_object.APHOTH1 の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 'm_object.APHOTH1 = TxtTELO.Text iris_object.Set("APHWORK", TxtTELO.Text) On Error GoTo actionSaveError 'UPGRADE_WARNING: オブジェクト m_object.sys_Save の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 'm_object.sys_Save() iris_object.Invoke("%Save")
IRISObjectでは、プロパティに直接アクセスができず代わりにSetメソッドで設定する必要があります。また保存は、インスタンスメソッドの%Save()をInvokeメソッドで起動します。これでADBKMain.vbの修正は終了です。FindByNme.vbの修正続いてFindByNme.vbの内容を変更していきましょう。まずはIRISライブラリーのインポートが必要です。
Option Explicit Onの後ろに以下の行を追加します。
Imports InterSystems.Data.IRISClientImports InterSystems.Data.IRISClient.ADO
次に変数宣言の所を以下のように変更します。
'Dim RS As CacheObject.ResultSet 'Dim m_factory As CacheObject.Factory 'Dim m_object As CacheObject.ObjInstance Dim iris As IRIS Dim iris_object As IRISObject Dim iris_conn As IRISConnection 'Const m_classname As String = "User.ADBK" Const iris_classname As String = "User.ADBK"
IRISにはResultSetオブジェクトがありませんので、代替の方法で処理する必要があります。詳細は、後程説明します。
次にShowDialog_Renamed関数の処理を変更します。
これはADBKMainから検索ボタンを押したときに呼ばれる処理になります。
'Public Function ShowDialog_Renamed(ByRef factory As CacheObject.Factory) As CacheObject.ObjInstance Public Function ShowDialog_Renamed(ByRef iris_factory As IRIS, ByRef iris_connection As IRISConnection) As IRISObject
パラメータとしてiris_connectionを追加する必要があるので、追加します。
'm_factory = factory iris = iris_factory iris_conn = iris_connection
コネクションオブジェクトの設定を追加します。
'Set m_factory = Nothing 'ShowDialog_Renamed = m_object ShowDialog_Renamed = iris_object
戻り値をIRISObjectを返すように変更します。
次に検索ボタンが押された時の処理を変更します。
CmdFind_Clickの処理になります。
'RS = m_factory.ResultSet("User.ADBK", "ByName") 'RS.Execute(TxtSNAME.Text) ' ByName takes a single argument Dim SQLtext As String = "call sqluser.ADBK_byname(?)" Dim Command As IRISCommand = New IRISCommand(SQLtext, iris_conn) Dim Name_param As IRISParameter = New IRISParameter("Name_col", IRISDbType.NVarChar) Name_param.Value = TxtSNAME.Text Command.Parameters.Add(Name_param) Dim Reader As IRISDataReader = Command.ExecuteReader()
While Reader.Read() ListLookupName.Items.Add(Reader.Item(Reader.GetOrdinal("ANAME"))) End While Reader.Close() Command.Dispose()
' 取得した名前リストをListBoxに展開 'While RS.Next 'UPGRADE_WARNING: オブジェクト RS.GetDataByName() の既定プロパティを解決できませんでした。 詳細については、'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6A50421D-15FE-4896-8A1B-2EC21E9037B2"' をクリックしてください。 'ListLookupName.Items.Add(RS.GetDataByName("ANAME")) 'End While
IRISではResultSetメソッドがありませんので、代替手段で書き換えます。クラスクエリーはSQLのcall文で置き換え可能です。但し、サーバー側のクエリー定義にsqlProc属性をつける必要があります。
Query ByName(Name As %String) As %SQLQuery(CONTAINID = 1) [ SqlProc ]
IRISCommandオブジェクトとIRISDataReaderオブジェクトを使用してクエリーを処理します。パラメータは、IRISParameterオブジェクトを使って定義します。
このあたりは、MicrosoftのADO.NETの仕様に基づき実装されているので、詳細はドキュメントを確認してください。
次にOKボタンが押された時の処理を変更します。
CmdOK_Clickの処理になります。
'name_Renamed = VB6.GetItemString(ListLookupName, ListLookupName.SelectedIndex) name_Renamed = ListLookupName.Items(ListLookupName.SelectedIndex).ToString()
これはIRIS対応とは関係ないのですが、動作しなかったので、変更しました。VB6との互換性がない部分だと想定しています。
'RS = m_factory.DynamicSQL("SELECT * FROM ADBK WHERE ANAME = ?") 'RS.Execute(name_Renamed) ' 値を'?'にバインド 'RS.Next() Dim SQLtext As String = "SELECT * FROM ADBK WHERE ANAME = ?" Dim Command As IRISCommand = New IRISCommand(SQLtext, iris_conn) Dim Name_param As IRISParameter = New IRISParameter("Name_col", IRISDbType.NVarChar) Name_param.Value = name_Renamed Command.Parameters.Add(Name_param) Dim Reader As IRISDataReader = Command.ExecuteReader() Reader.Read()
先ほどと同様にIRISCommandとIRISDataReaderを使って書き換えます。
'id = RS.GetDataByName("AID") id = Reader.Item(Reader.GetOrdinal("AID"))
フィールドの値の取得もさきほどと同様に書き換えます。
'm_object = m_factory.OpenId(m_classname, id) 'If m_object Is Nothing Then iris_object = iris.ClassMethodObject(iris_classname, "%OpenId", id) If iris_object Is Nothing Then
次は、検索の結果取得されたidを使って、オブジェクトインスタンスをオープンする処理もClassMethodObjectメソッドを使って実装します。
'CType(ADBKMain.Controls("TxtNAME"), Object).Text = m_object.ANAME CType(ADBKMain.Controls("TxtNAME"), Object).Text = iris_object.Get("ANAME") 'CType(ADBKMain.Controls("TxtZIP"), Object).Text = m_object.AZIP CType(ADBKMain.Controls("TxtZIP"), Object).Text = iris_object.Get("AZIP") 'CType(ADBKMain.Controls("TxtADDRESS"), Object).Text = m_object.ASTREET CType(ADBKMain.Controls("TxtADDRESS"), Object).Text = iris_object.Get("ASTREET") 'CType(ADBKMain.Controls("TxtTELH"), Object).Text = m_object.APHHOME CType(ADBKMain.Controls("TxtTELH"), Object).Text = iris_object.Get("APHHOME") 'CType(ADBKMain.Controls("TxtTELO"), Object).Text = m_object.APHOTH1 CType(ADBKMain.Controls("TxtTELO"), Object).Text =iris_object.Get("APHWORK") 'CType(ADBKMain.Controls("TxtAGE"), Object).Text = m_object.AAGE CType(ADBKMain.Controls("TxtAGE"), Object).Text = iris_object.Get("AAGE") 'CType(ADBKMain.Controls("TxtDOB"), Object).Text = m_object.ABTHDAY CType(ADBKMain.Controls("TxtDOB"), Object).Text = iris_object.InvokeString("ABTHDAYLogicalToOdbc", iris_object.Get("ABTHDAY"))
IRISObjectでは、プロパティに直接アクセスができず代わりにGetメソッドで取得する必要があります。
DOB(誕生日)は、内部値($Horolog)をODBC形式の日付に変換する処理を加えています。
以上で修正は終了です。
最後に
既存の資産を生かしつつ、ActiveX Bindingを使用したアプリケーションの移行が簡単にできるということをご理解いただき、アプリケーションの移行にチャレンジしていただきたいと思います。
もう一つ、サーバー側の処理は全く修正していない点も強調しておきたいと思います。
記事
Toshihiko Minamoto · 2021年4月22日
## はじめに (および本記事を書いた動機) {#RobustErrorHandlingandCleanupinObjectScript-IntroductionandMotivation}
ObjectScript コードのユニット (ClassMethod など) を実行する場合、そのスコープ外にあるシステムの諸部分と対話するときに適切なクリーンアップを行えないことが原因で、様々な予期せぬ副作用が発生することがあります。 以下にその一部を紹介します。
* トランザクション
* ロック
* I/O デバイス
* SQL のカーソル
* システムフラグと設定
* $Namespace
* 一時ファイル
ObjectScript のこういった重要な機能を、クリーンアップのコーディングや防御的なコーディングを適切に行わずに使用すると、普段は正常に動作しても、予期せぬかたちで、またデバッグが困難なかたちで失敗し得るアプリケーションができてしまう可能性があります。 想定できるすべてのエラーケースにおいてクリーンアップコードが正常に動作することは、極めて重要です。表面的なテストではエラーを見落とす可能性が高いことを考えるとなおさらです。 この記事では、既知の落とし穴をいくつかご紹介し、信頼性の高いエラー処理とクリーンアップを実現するための 2 種類の対処法について説明いたします。
_確実にすべてのエッジケースをテストしたい方は、 私が Open Exchange に掲載している [Test Coverage Tool](https://openexchange.intersystems.com/package/Test-Coverage-Tool) をご覧ください!_
_注記: この記事は私が元々 2018年 6月 に InterSystems 社内で掲載したものです。 開発者コミュニティに投稿しようと思って To-Do リストに加えてから、もう 1 年半になります。 [ありきたりな言い訳で恐縮です。。。](https://natethesnake.com/)_
## 避けたい落とし穴 {#RobustErrorHandlingandCleanupinObjectScript-PitfallstoAvoid}
### トランザクション {#RobustErrorHandlingandCleanupinObjectScript-Transactions}
トランザクションを自然にかつシンプルに処理する方法として、以下のように try/catch ブロックを使い、catch の中で TRollback を使います。
Try {
TSTART
// ... データを処理するコード ...
TCOMMIT
} Catch e {
TROLLBACK
// e.AsStatus(), e.Log(), etc.
}
ここだけを見た場合、エラーが発生したときに TStart と TCommit の間のコードが早々と Quit を出すのではなく、エラーを投げてくれることを考えると、このコードに問題はありません。 しかし、このコードにはリスクがあります。理由は以下の 2 つです。
* もし他のデベロッパーが try ブロックに「Quit」を追加した場合、このトランザクションは開いた状態で放置される。 そのような変更内容は、コードレビューの際につい見落としてしまうことがあるでしょう。現在のコンテキストにトランザクションが含まれていることが明らかでない場合は一層見落としやすくなります。
* このブロックのメソッドが外部のトランザクションの中から呼び出されることがあれば、トランザクションのすべてのレベルが TRollback によりロールバックされてしまう。
より好適なアプローチとしては、トランザクションのレベルをメソッドの初めから追跡し、最後にトランザクションのそのレベルにロールバックします。 下の例をご覧ください。
Set tInitTLevel = $TLevel
Try {
TSTART
// ... データを処理するコード...
// tStatus は例外として投げる必要がないので、次のコードはこれで問題ありません。
If $$$ISERR(tStatus) {
Quit
}
// ... データを処理する他のコード...
TCOMMIT
} Catch e {
// e.AsStatus(), e.Log(), etc.
}
While $TLevel > tInitTLevel {
// トランザクションのレベルを一度に一つずつロールバックします。
TROLLBACK 1
}
### ロック {#RobustErrorHandlingandCleanupinObjectScript-Locks}
インクリメントロックを使用するコードでは、ロックが必要でなくなったら、クリーンアップコードの実行時にロックをデクリメントする必要があります。これをしないと、そのようなロックはプロセスが終了するまで保持されることになります。 ロックがメソッドの外にリークすることはありませんが、そのようなロックを取得することがメソッドの副作用として確認されている場合は除きます。
### I/O デバイス {#RobustErrorHandlingandCleanupinObjectScript-I/ODevices}
同じように、現在の I/O デバイス (特殊変数 $io) の変更もメソッドの外にリークすることはありませんが、メソッドが現在のデバイスを変更することを目的としている場合は除きます (I/O リダイレクトを有効にするなど)。 ファイルを操作するときは、OPEN / USE / READ / CLOSE を使うシーケンシャルファイルのダイレクト I/O よりも、%Stream パッケージを使用することをおすすめします。 これ以外の場合で、I / O デバイスを使用する必要があるときは、メソッドの終わりにデバイスを元のデバイスに戻すよう注意が必要です。 例えば、次のコードにはリスクがあります。
Method ReadFromDevice(pSomeOtherDevice As %String)
{
Open pSomeOtherDevice:10
Use pSomeOtherDevice
Read x
// ... x を使って複雑なことを実行します...
Close pSomeOtherDevice
}
pSomeOtherDevice がクローズする前に例外が投げられることがあれば、$io は pSomeOtherDevice のままとなります。これにより、カスケードエラーが発生する可能性があります。 また、デバイスがクローズされると、$io はプロセスのデフォルトデバイスにリセットされますが、メソッドが呼び出された前の同じデバイスにはリセットされない場合があります。
### SQL のカーソル {#RobustErrorHandlingandCleanupinObjectScript-SQLCursors}
カーソルベースの SQL を使用する場合にエラーが発生したら、カーソルをクローズする必要があります。 カーソルをクローズしなければ、リソースがリークする可能性があります (ドキュメントにその旨の記載あり)。 また、コードを再度実行して、カーソルをオープンしようとすると、「already open」(既にオープン状態) エラー (SQLCODE -101) が発生する場合もあります。
### システムフラグと設定 {#RobustErrorHandlingandCleanupinObjectScript-SystemFlagsandSettings}
アプリケーションコードがプロセスレベルのフラグやシステムレベルのフラグを変更する必要があるということは滅多にありません。例えば、これらの多くは %SYSTEM.Process と %SYSTEM.SQL に定義されています。 その必要があるという場合は、初期値を保存してから、メソッドの終わりに再度保存しなおす必要があるので注意が必要です。
### $Namespace {#RobustErrorHandlingandCleanupinObjectScript-$Namespace}
ネームスペースの変更がメソッドのスコープ外にリークするのを防ぐために、ネームスペースを変更するコードは、常に新しい $Namespace を最初に記述する必要があります。
### 一時ファイル {#RobustErrorHandlingandCleanupinObjectScript-TemporaryFiles}
%Library.File:TempFilename などを使って一時ファイルを作成するアプリケーションコードでは、その一時ファイルが不要になれば、その時点で削除するよう心掛ける必要があります (ちなみに、%Library.File:TempFilename を使うと、主に InterSystems IRIS では、実際にファイルが作成されます)。
## お薦めの対処法: Try-Catch (-Finally) {#RobustErrorHandlingandCleanupinObjectScript-RecommendedPattern:Try-Catch(-Finally)}
多くのプログラミング言語には、try/catch 構造に「finally」ブロックを追加できるという機能が備わっています。「finally」ブロックは、try/catch 文が完了した後に、例外が発生したかどうかに関係なく実行されます。 ObjectScript にこの機能はありませんが、似たようなことができます。 その一般的なパターンは、以下のとおりです。上述した潜在的な多くの問題をご確認いただけます。
ClassMethod MyRobustMethod(pFile As %String = "C:\foo\bar.txt") As %Status
{
Set tSC = $$$OK
Set tInitialTLevel = $TLevel
Set tMyGlobalLocked = 0
Set tDevice = $io
Set tFileOpen = 0
Set tCursorOpen = 0
Set tOldSystemFlagValue = ""
Try {
// グローバルをロックする。但し、5 秒以内にロックを取得できることが条件。
Lock +^MyGlobal(42):5
If '$Test {
$$$ThrowStatus($$$ERROR($$$GeneralError,"Couldn't lock ^MyGlobal(42)."))
}
Set tMyGlobalLocked = 1
// ファイルを開く
Open pFile:"WNS":10
If '$Test {
$$$ThrowStatus($$$ERROR($$$GeneralError,"Couldn't open file "_pFile))
}
Set tFileOpen = 1
// [ カーソル MyCursor を宣言 ]
&;SQL(OPEN MyCursor)
Set tCursorOpen = 1
// このプロセスにシステムフラグを設定する。
Set tOldSystemFlagValue = $System.Process.SetZEOF(1)
// 重要なアクションを実行...
Use tFile
TSTART
// [ ... ここでデータを変更する重要で複雑なコードをたくさん実行... ]
// すべて完了!
TCOMMIT
} Catch e {
Set tSC = e.AsStatus()
}
// Finally {
// クリーンアップ: システムフラグ
If (tOldSystemFlagValue '= "") {
Do $System.Process.SetZEOF(tOldSystemFlagValue)
}
// クリーンアップ: デバイス
If tFileOpen {
Close pFile
// pFile が現在のデバイスなら、CLOSE コマンドは $io をプロセスのデフォルトのデバイスに戻す
// メソッドが呼び出されたときの $io の値とは違う可能性あり。
// 念のために:
Use tDevice
}
// クリーンアップ: ロック
If tMyGlobalLocked {
Lock -^MyGlobal(42)
}
// クリーンアップ: トランザクション
// トランザクションのレベルを最初のレベルまで一つずつロールバックする。
While $TLevel > tInitialTLevel {
TROLLBACK 1
}
// } // "finally" 終了
Quit tSC
}
注意: このアプローチでは、try ... ブロックで「Return」の変わりに「Quit」を使用することが極めて重要です。「Return」だとクリーンアップをバイパスしてしまいます。
## お薦めの対処法: Registered Objects および Destructors {#RobustErrorHandlingandCleanupinObjectScript-RecommendedPattern:RegisteredObjectsandDestructors}
クリーンアップコードは複雑になる場合があります。 そのような場合は、クリーンアップコードを Registered Object の中でカプセル化し、その再利用を促進することが理に適っているかもしれません。 システムの状態は、オブジェクトが初期化されたときや、システムの状態を変化させる (オブジェクトの) メソッドが呼び出された時点から追跡され、そのオブジェクトがスコープから外れたときに、元の値に戻されます。 では、以下のシンプルな例について考えます。ここでは、トランザクション、現在のネームスペース、$System.Process.SetZEOF の状態が管理されています。
/// このクラスのインスタンスがスコープから外れると、そのインスタンスが作成された時点で存在していたネームスペース、トランザクションのレベル、および $System.Process.SetZEOF() の値が復元されます。
Class DC.Demo.ScopeManager Extends %RegisteredObject
{
Property InitialNamespace As %String [ InitialExpression = {$Namespace} ];
Property InitialTransactionLevel As %String [ InitialExpression = {$TLevel} ];
Property ZEOFSetting As %Boolean [ InitialExpression = {$System.Process.SetZEOF()} ];
Method SetZEOF(pValue As %Boolean)
{
Set ..ZEOFSetting = $System.Process.SetZEOF(.pValue)
}
Method %OnClose() As %Status [ Private, ServerOnly = 1 ]
{
Set tSC = $$$OK
Try {
Set $Namespace = ..InitialNamespace
} Catch e {
Set tSC = $$$ADDSC(tSC,e.AsStatus())
}
Try {
Do $System.Process.SetZEOF(..ZEOFSetting)
} Catch e {
Set tSC = $$$ADDSC(tSC,e.AsStatus())
}
Try {
While $TLevel > ..InitialTransactionLevel {
TROLLBACK 1
}
} Catch e {
Set tSC = $$$ADDSC(tSC,e.AsStatus())
}
Quit tSC
}
}
以下のクラスは、今お見せした登録クラスを使って、メソッドの最後で実行するクリーンアップを簡素化する方法を示すものです。
Class DC.Demo.Driver
{
ClassMethod Run()
{
For tArgument = "good","bad" {
Do ..LogState(tArgument,"before")
Do ..DemoRobustMethod(tArgument)
Do ..LogState(tArgument,"after")
}
}
ClassMethod LogState(pArgument As %String, pWhen As %String)
{
Write !,pWhen," calling DemoRobustMethod("_$$$QUOTE(pArgument)_"):"
Write !,$c(9),"$Namespace=",$Namespace
Write !,$c(9),"$TLevel=",$TLevel
Write !,$c(9),"$System.Process.SetZEOF()=",$System.Process.SetZEOF()
}
ClassMethod DemoRobustMethod(pArgument As %String)
{
Set tScopeManager = ##class(DC.Demo.ScopeManager).%New()
Set $Namespace = "%SYS"
TSTART
Do tScopeManager.SetZEOF(1)
If (pArgument = "bad") {
// 通常ならこれは大問題となるところですが、 tScopeManager があるので大丈夫です。
Quit
}
TCOMMIT
}
}
記事
Toshihiko Minamoto · 2021年9月14日
## より産業向けのグローバルストレージスキーム
この連載の第1回では、リレーショナルデータベースにおけるEAV(Entity-Attribute-Value)モデルを取り上げ、テーブルにエンティティ、属性、および値を保存することのメリットとデメリットについて確認しました。 このアプローチには柔軟性という点でメリットがあるにもかかわらず、特にデータの論理構造と物理ストレージの基本的な不一致などによりさまざまな問題が引き起こされるという深刻なデメリットがあります。
こういった問題を解決するために、階層情報の保存向けに最適化されたグローバル変数を、EAVアプローチが通常処理するタスクに使用できるかどうかを確認することにしました。
[パート1](https://jp.community.intersystems.com/node/501181)では、オンラインストア向けのカタログをテーブルを使って作成し、その後で1つのグローバル変数のみで作成しました。 それでは、複数のグローバル変数で同じ構造を実装してみることにしましょう。
最初のグローバル変数`^catalog`には、ディレクトリ構造を保存します。 2つ目のグローバル変数`^good`には、店の商品を保存します。 `^index`グローバルには、店のインデックスを保存します。 プロパティは階層的なカタログに関連付けられているため、プロパティ用の個別のグローバル変数は作成しません。
このアプローチでは、エンティティごとに(プロパティを除く)、個別のグローバル変数を使用しているため、論理の観点では優れています。 グローバルカタログ構造は次のようになります。
.png)
Set ^сatalog(root_id, "Properties", "capacity", "name") = "Capacity, GB"
Set ^сatalog(root_id, "Properties", "capacity", "sort") = 1
Set ^сatalog(root_id, sub1_id, "Properties", "endurance", "name") = "Endurance, TBW"
Set ^сatalog(root_id, sub1_id, "Properties", "endurance", "sort") = 2
Set ^сatalog(root_id, sub1_id, "goods", id_good1) = 1
Set ^сatalog(root_id, sub1_id, "goods", id_good2) = 1
Set ^сatalog(root_id, sub2_id, "Properties", "avg_seek_time", "name") = "Rotate speed, ms"
Set ^сatalog(root_id, sub2_id, "Properties", "avg_seek_time", "sort") = 3
Set ^сatalog(root_id, sub2_id, "goods", id_good3) = 1
Set ^сatalog(root_id, sub2_id, "goods", id_good4) = 1
商品のグローバル変数は、次のようになります。

Set ^good(id_good, property1) = value1
Set ^good(id_good, property2) = value2
Set ^good(id_good, property3) = value3
Set ^good(id_good, "catalog") = catalog_id
もちろん、商品のあるすべてのカタログセクションで、必要なプロパティで並べ替えを行えるようにインデックスが必要となります。 インデックスグローバルは、次のような構造になります。

Set ^index(id_catalog, property1, id_good) = 1
; To quickly get the full path to concrete sub-catalog
Set ^index("path", id_catalog) = "^catalog(root_id, sub1_id)"
したがって、カタログのすべてのセクションで、リストを並べ替えることができます。 インデックスグローバルはオプションです。 カタログのこのセクションの商品数が多い場合にのみ役立ちます。
## デモデータを操作するためのObjectScriptコード
では、データを操作するために、ObjectScriptを使用しましょう。 まず、特定の商品のプロパティを取得することから始めます。 特定の商品のIDがあり、そのプロパティを並べ替えの値で指定された順序で表示する必要があります。 そのためのコードは次のようになります。
get_sorted_properties(path, boolTable)
{
; remember all the properties in the temporary global
While $QLENGTH(@path) > 0 {
if ($DATA(@path("Properties"))) {
set ln=""
for {
Set ln = $order(@path("Properties", ln))
Quit: ln = ""
IF boolTable & @path("Properties", ln, "table_view") = 1 {
Set ^tmp(@path("Properties", ln, "sort"), ln) = @path("Properties", ln, "name")
}
ELSE {
Set ^tmp(@path("Properties", ln, "sort"), ln) = @path("Properties", ln, "name")
}
}
}
}
print_sorted_properties_of_good(id_good)
{
Set id_catalog = ^good(id_good, "catalog")
Set path = ^index("path", id_catalog)
Do get_sorted_properties(path, 0)
set ln =""
for {
Set ln = $order(^tmp(ln))
Quit: ln = ""
Set fn = ""
for {
Set fn = $order(^tmp(ln, fn))
Quit: fn = ""
Write ^tmp(ln, fn), " ", ^good(id_good, fn),!
}
}
}
次に、`id_catalog`に基づいて、カタログセクションの商品を表形式で取得します。
print_goods_table_of_catalog(id_catalog)
{
Set path = ^index("path", id_catalog)
Do get_sorted_properties(path, 1)
set id=""
for {
Set id = $order(@path("goods"), id)
Quit: id = ""
Write id," ", ^good(id, "price"), " "
set ln =""
for {
Set ln = $order(^tmp(ln))
Quit: ln = ""
Set fn = ""
for {
Set fn = $order(^tmp(ln, fn))
Quit: fn = ""
Write ^tmp(ln, fn), " ", ^good(id, fn)
}
Write !
}
}
}
## 可読性: EAV SQLとグローバル変数
では、EAVとSQLの使用をグローバル変数の使用と比較してみましょう。 コードの明確さについては、これが主観的なパラメーターであることは明らかです。 しかし、例として新しい商品の作成方法を見てみましょう。
SQLを使用したEAVアプローチから確認します。 まず、オブジェクトのプロパティリストを取得する必要があります。 これは別のタスクであり、非常に時間がかかります。 `capacity`、`weight`、および`endurance`という3つのプロパティのIDがすでに分かっているとします。
START TRANSACTION
INSERT INTO good (name, price, item_count, catalog_id) VALUES ('F320 3.2TB AIC SSD', 700, 10, 15);
SET @last_id = LAST_INSERT_ID ();
INSERT INTO NumberValues Values(@last_id, @id_capacity, 3200);
INSERT INTO NumberValues Values(@last_id, @id_weight, 0.4);
INSERT INTO NumberValues Values(@last_id, @id_endurance, 29000);
COMMIT
この例ではプロパティが3つしかないため、例にはそれほど圧倒されません。 一般的なケースでは、トランザクション内のテキストテーブルにいくつかの挿入があります。
INSERT INTO TextValues Values(@last_id, @ id_text_prop1, 'Text value of property 1');
INSERT INTO TextValues Values(@last_id, @ id_text_prop2, 'Text value of property 2');
...
INSERT INTO TextValues Values (@last_id, @id_text_propN, 'Text value of property N');
もちろん、数値の代わりに「capacity」を使うというように、IDプロパティの代わりにテキスト表記を使用すれば、SQLバージョンをもう少し簡略することも可能ですが、 SQLの世界では、これは受け入れられません。 エンティティのインスタンスを列挙するには、数値IDを使用するのが慣例です。 このため、インデックス処理が高速化し(インデックス処理のバイトが少なくなるため)、一意性を追跡しやすくなり、新しいIDを自動的に作成しやすくなります。 この場合、挿入フラグメントは次のようになります。
INSERT INTO NumberValues Values(@last_id, 'capacity', 3200);
INSERT INTO NumberValues Values(@last_id, 'weight', 0.4);
INSERT INTO NumberValues Values(@last_id, 'endurance', 29000);
次は、同じ例をグローバル変数を使用した場合のコードです。
TSTART
Set ^good(id, "name") = "F320 3.2TB AIC SSD"
Set ^("price") = 700, ^("item_count") = 10, ^("reserved_count") = 0, ^("catalog") = id_catalog
Set ^("capacity") = 3200, ^("weight") = 0.4, ^("endurance") = 29000
TCOMMIT
では、EAVアプローチで商品を削除してみましょう。
START TRANSACTION
DELETE FROM good WHERE id = @ good_id;
DELETE FROM NumberValues WHERE good_id = @ good_id;
DELETE FROM TextValues WHERE good_id = @ good_id;
COMMIT
そして、グローバル変数でも同じことを行います。
Kill ^good(id_good)
2つのアプローチをコードの長さの観点から比較することもできます。 上記の例からわかるように、グローバル変数を使用した方が、コードは短くなります。 これはメリットです。 コードが短くなるほど、エラーの数も減り、コードを理解して管理するのも容易になります。
一般に、コードが短いほど処理が高速化します。 そして、この場合には、グローバル変数はリレーショナルテーブルよりも低位データ構造であるため、確かにそのとおりです。
## EAVとグローバル変数におけるデータのスケーリング
次に、水平方向のスケーリングを見てみましょう。 EAVアプローチでは、少なくとも3つの最も大きなテーブル(Good、NumberValues、TextValues)を複数のサーバーに分散する必要があります。 エンティティと属性のあるテーブルにはほとんど情報がないため、これらのテーブルは単純にすべてのサーバーに丸ごとコピーすることができます。
各サーバーでは、水平方向のスケーリングにより、さまざまな商品がGood、NumberValues、およびTextValuesテーブルに保存されます。 異なる商品でIDが重複しないように、各サーバーの商品に対して特定のIDブロックを割り当てる必要があります。
グローバルを使って水平方向のスケーリングを行う場合、グローバルでID範囲を構成し、グローバル範囲を各サーバーに割り当てる必要があります。
複雑さは、EAVとグローバルであまり変わりませんが、EAVアプローチの場合は、3つのテーブルにID範囲を構成しなければなりません。 グローバルの場合は、1つのグローバル変数のみにIDを構成するだけで済みます。 つまり、水平方向のスケーリングを調整するには、グローバル変数の方が簡単と言えます。
## EAVとグローバル変数におけるデータ損失
最後に、データベースファイルの破損によるデータ損失のリスクを検討してみましょう。 5つのテーブルか3つのグローバル(インデックスグローバルを含む)のどちらにすべてのデータを保存する方が簡単でしょうか。
3つのグローバルの方が簡単だと思います。 EAVアプローチでは、さまざまな商品のデータがテーブルに混在しているのに対し、グローバルでは情報がより全体的に保存されています。 基盤のブランチは、保存されて順に並べ替えられています。 そのため、データがパスタが絡み合うように保存されるEAVアプローチに比べれば、グローバルの一部の破損によってダメージにつながる可能性は低くなります。
データ回復におけるもう1つの悩みの種は、情報の表示方法です。 EAVアプローチでは、情報は複数のテーブルに分割されているため、1つにまとめるには特別なスクリプトが必要です。 グローバルの場合は、ZWRITEコマンドを使用するだけで、ノードのすべての値と基盤のブランチを表示することができます。
## InterSystems IRISのグローバル: より優れたアプローチ?
EAVアプローチは、階層データを保存するためのトリックとして出現しました。 テーブルは元々、ネストされたデータを保存するようには設計されてはいなかったため、 テーブルでグローバルをエミュレーションするのがEAVの事実上のアプローチです。 テーブルがグローバルよりも高位で低速のデータストレージ構造であることを考えると、EAVアプローチは、グローバルと比較した場合に失敗となります。
個人的な意見を言えば、階層データ構造の場合、グローバルの方がプログラミングの点でより利便性が高く理解しやすいと思います。また、より高速でもあります。
プロジェクトでEAVアプローチを計画している場合は、InterSystems IRISのグローバルを使用して階層データを保存することを検討するようお勧めします。
記事
Mihoko Iijima · 2023年3月23日
開発者の皆さん、こんにちは。
AWSのEC2インスタンス(Ubuntu 20.04を選択)にIRISをインストールした環境を事前に用意した状態からの流れですが、AWS Lambda 関数からPyODBC経由でIRISに接続するまでの流れを試してみました。
Native APIを利用する流れについては、「AWS Lambda の IRIS Python Native API IRIS」をご参照ください。
参考にしたAWSドキュメント:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-python.html
以下の流れでご紹介します。
1. レイヤーを作成する
2. Lambda関数を作成する。
3. 1,2の流れをCloudformationで行う例
例で使用しているコード一式はこちらにあります👉https://github.com/Intersystems-jp/iris-pyodbc-lambda
流れに入る前に事前準備についてご紹介します。
事前準備
1) IRISの準備
EC2インスタンスを用意し(例ではUbuntu20.04)IRISをインストールした環境を用意し、IRISのスーパーサーバーポート(1972番)にアクセスできるようにします。
IRISに以下テーブルとデータを用意しておきます(USERネームスペースで作成する例)。
IRISにログインします。
iris session iris
続いて、SQL用シェルに切り替え(:sql と入力するとSQLシェルに切り替えできます)、CREATE文とINSERT文を実行します。
:sql
CREATE TABLE Test.Person (Name VARCHAR(50),Email VARCHAR(50))
INSERT INTO Test.Person (Name,Email) VALUES('山田','taro@mai.com')
INSERT INTO Test.Person (Name,Email) VALUES('武田','takeda@mai.com')
SQLシェルを終了するには quit を入力します。
quit
IRISからログアウトするには、haltコマンドを使用します。
halt
2) Lambda関数の実行ロールの準備
必要であれば、Lambda関数からEC2にアクセスするときのロールを作成しておきます。
《参考にしたページ》https://dev.classmethod.jp/articles/tsnote-lambda-the-provided-execution-role-does-not-have-permissions-to-call-createnetworkinterface-on-ec2/
準備ができたら、さっそくLambda関数を作成してみましょう!
1. レイヤ―を作成する
《参考にしたページ》https://qiita.com/Todate/items/03c5d3911b52b93d39af
レイヤーは、Lambda関数で使用するライブラリとその他依存関係ファイルをZipファイルでアーカイブしたものでコードやデータも含めることができるそうですが、以下の流れでは、IRISの接続に必要なunixODBC用ファイル、pyodbcモジュール用ファイルを含めたレイヤ―作成の流れでご紹介します。
参考にしたAWSドキュメント:Lambda レイヤーの作成と共有
(1) unixODBC用soファイルの用意
(2) IRIS用ドライバのダウンロード
(3) pyodbcのインストール
(4) ODBCデータソース用ファイルの作成
(5) zip作成(レイヤーの作成)
(6) レイヤーの追加
(1) unixODBC用soファイルの用意
unixODBC-2.3.7 の soファイルを入手するため、以下ページを参考にしています。
https://qiita.com/Todate/items/03c5d3911b52b93d39af
以下任意ディレクトリ上で実行します。
curl ftp://ftp.unixodbc.org/pub/unixODBC/unixODBC-2.3.7.tar.gz -O
tar xvzf unixODBC-2.3.7.tar.gz
cd unixODBC-2.3.7
./configure --sysconfdir=/opt --disable-gui --disable-drivers --enable-iconv --with-iconv-char-enc=UTF8 --with-iconv-ucode-enc=UTF16LE --prefix=/opt
make
sudo make install
ここまでで /opt/lib/*.so ができるので、libディレクトリをレイヤー作成用ディレクトリに全コピーします。
以降、レイヤー作成用ディレクトリを ~/pyodbc_lambda として記述します。
sudo cp -r /opt/lib ~/pyodbc_lambda/
(2) IRIS用ドライバのダウンロード
IRIS用ドライバlibirisodbcur6435.soをレイヤー作成用ディレクトリ以下libディレクトリにダウンロードします。
cd ~/pyodbc_lambda/lib
wget https://github.com/Intersystems-jp/IRISModules/raw/master/python/wheel/linux/libirisodbcur6435.so
(3) pyodbcのインストール
lambda関数で使用するPythonモジュールは、レイヤー内pythonディレクトリ以下に配置します。
cd ~/pyodbc_lambda
mkdir python
cd python
pip3 install pyodbc -t .
(4) ODBCデータソース用ファイルの作成
odbc.iniとodbcinst.iniを、レイヤー作成用ディレクトリ以下pythonディレクトリに配置します。
レイヤー用ディレクトリ以下にあるファイルは以下の通りです。
$ tree
.
├── lib
│ ├── libirisodbcur6435.so
│ ├── libodbc.la
│ ├── libodbc.so -> libodbc.so.2.0.0
│ ├── libodbc.so.2 -> libodbc.so.2.0.0
│ ├── libodbc.so.2.0.0
│ ├── libodbccr.la
│ ├── libodbccr.so -> libodbccr.so.2.0.0
│ ├── libodbccr.so.2 -> libodbccr.so.2.0.0
│ ├── libodbccr.so.2.0.0
│ ├── libodbcinst.la
│ ├── libodbcinst.so -> libodbcinst.so.2.0.0
│ ├── libodbcinst.so.2 -> libodbcinst.so.2.0.0
│ ├── libodbcinst.so.2.0.0
│ └── pkgconfig
│ ├── odbc.pc
│ ├── odbccr.pc
│ └── odbcinst.pc
└── python
├── odbc.ini
├── odbcinst.ini
├── pyodbc-4.0.35.dist-info
│ ├── INSTALLER
│ ├── LICENSE.txt
│ ├── METADATA
│ ├── RECORD
│ ├── WHEEL
│ └── top_level.txt
├── pyodbc.cpython-38-x86_64-linux-gnu.so
└── pyodbc.pyi
4 directories, 26 files
(5) zip作成(レイヤーの作成)
レイヤー用ディレクトリで以下実行します。
cd ~/pyodbc_lambda
zip -r9 ../iris_pyodbc_lambda.zip *
ご参考:この手順で作ったZipの例 iris_pyodbc_lambda.zip
(6) レイヤーの追加
AWS Lambdaでレイヤーを追加します。
図例では、レイヤーの動作するアーキテクチャに x86_64 を指定し、ランタイムに Python3.8 を選択しています。
以上でレイヤーの作成が完了です。
次はいよいよ、lambda関数の作成です。
2. Lambda関数を追加する
以下の順序で追加します。
(1) 確認:IRISへの接続情報について
(2) 確認:Lambda関数ハンドラー名について
(3) Lambda関数の追加
(4) 環境変数の追加
(5) ランタイム設定の変更
(6) レイヤーの追加
(7) コード類のアップロード
(8) テスト実行
サンプルのpythonスクリプト:index.pyを使用して登録します。
(1) 確認:IRISへの接続情報について
サンプルのpythonスクリプト:index.pyでは、以下いずれかの方法でIRISに接続できるように記述しています。
環境変数を使用する index.pyには、lambda関数作成時に設定する環境変数を利用するように記述しています(18~22行目) 。なお、環境変数は、Lambda関数登録後、画面で追加/変更できます。
connection.config を使用する index.py の9行目と11~15行目のコメントを外し18~22行目をコメント化して利用します。 接続するIRISの情報に合わせてconnection.configを変更してください。
(2) 確認:Lambda関数ハンドラー名について
サンプルのpythonスクリプト:index.py 6行目に記載の関数 lambda_handler を今回登録するLambda関数ハンドラーとして設定します。
Lambda関数登録時のハンドラー名として、 "ファイルの名称"."Pythonの関数名称" とするルールがあるため、今回登録するハンドラー名は、index.lambda_hander となります。
(3) Lambda関数の追加
AWS Lambdaの関数メニューから登録します。
図例では、関数が動作するアーキテクチャに x86_64 を指定し、ランタイムに Python3.8 を選択しています。
必要であれば、Lambda関数がEC2にアクセスできるようにロールを作成します。
※ロール作成の参考ページ:https://dev.classmethod.jp/articles/tsnote-lambda-the-provided-execution-role-does-not-have-permissions-to-call-createnetworkinterface-on-ec2/
(4) 環境変数の追加
作成した関数の設定タブ:環境変数 で、ODBCSYSINI に ./ を設定します。
この他、IRISへの接続情報に環境変数を利用する場合は以下追加します。
例)
環境変数名
値
IRISHOST
13.231.153.242
IRISPORT
1972
NAMESPACE
USER
USERNAME
SuperUser
PASSWORD
SYS
(5) ランタイム設定の変更
サンプルのpythonスクリプト:index.py を実行時に使用したいので、ハンドラ名をデフォルト名称lambda_function.lambda_handlerから index.lambda_handler に変更します。
コードタブを選択し画面下のほうにある「ランタイム設定」の「編集」をクリックして変更保存します。
(6) レイヤーの追加
レイヤーを作成する の手順で作成したレイヤーをLambda関数に追加します。
コードタブを選択した状態で画面一番下の「レイヤー」から「レイヤーの追加」ボタンで追加します。
(7) コード類のアップロード
以下のファイルをZipファイルに含めてアップロードします。
connection.config (IRISへの接続情報を記載したファイル)
index.py
odbcinst.ini
※IRISへ接続情報を環境変数から取得する場合は、connection.configは不要です。
ご参考:iris_pyodbc_code.zip
コードタブの右端のボタン「アップロード元」をクリックし、Zipを選択してアップロードします。
(8) テスト実行
テストタブを使用します。
新しいイベントを作成します。(何度もテストする場合は保存しておくと便利です)
サンプルは特に引数入力がないので、指定する引数は{}と指定していますが、引数がある場合はJSONできるようです。
接続ができ、SELECT文が実行できると以下のような結果を表示します。
画面上には実行した関数の戻り値の表示(JSON配列)
画面下のほうにスクリプト内で記述したprint()の結果が表示されています。
最後にご紹介するのは、レイヤーとLambda関数追加の流れをCloudformationを利用して自動的に作成する方法です。
メモ:事前に準備したEC2の情報を指定する流れで試しています。
3. 1,2の流れをCloudformationで行う例
サンプル:cloudformation.xmlを使用して「1. レイヤーを作成する 」と「2. Lambda関数を作成する」の流れを自動化します。
「1. レイヤーを作成する」 の流れで作成したZip(例:iris_pyodbc_lambda.zip)と、「2. Lambda関数を作成する」 の流れで作成したZip(例:iris_pyodbc_code.zip)を cloudformation.xml の中で指定します。
S3にZipを配置する
S3のバケットを作成し、作成したZipファイル(例:iris_pyodbc_lambda2.zip)を配置します。
バケット名:iijimas3 にコピーしている例
aws s3 cp iris_pyodbc_lambda.zip s3://iijimas3
aws s3 cp iris_pyodbc_code.zip s3://iijimas3
※ AWS CLI https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html
cloudformationを使ってlambda関数作成
例)cloudformation.yml
ymlでは、lambda関数で使用するレイヤーの作成(リソース名:LambdaLayer)と、
LambdaLayer:
Type: AWS::Lambda::LayerVersion
Properties:
CompatibleArchitectures:
- x86_64
CompatibleRuntimes:
- python3.8
Content:
S3Bucket: iijimas3
S3Key: iris_pyodbc_lambda.zip
Description: "iris python layer"
LayerName: "IRISPyODBCLayer"
lambda関数を作成しています。
IRISPyODBCFunction:
Type: "AWS::Lambda::Function"
Properties:
Environment:
Variables:
IRISHOST: "13.231.153.242"
IRISPORT: "1972"
NAMESPACE: "USER"
USERNAME: "SuperUser"
PASSWORD: "SYS"
ODBCSYSINI: "./"
Code:
S3Bucket: iijimas3
S3Key: iris_pyodbc_code.zip
Description: "IRIS pyodbc Function"
FunctionName: iris-pyodbc
Handler: "index.lambda_handler"
Layers:
- !Ref LambdaLayer
MemorySize: 128
Role: "arn:aws:iam::109671571309:role/lambda_vpc_basic_execution_IRIS"
Runtime: "python3.8"
Timeout: 30
lambda関数の中で使用する環境変数の設定や、
Properties:
Environment:
Variables:
IRISHOST: "13.231.153.242"
IRISPORT: "1972"
NAMESPACE: "USER"
USERNAME: "SuperUser"
PASSWORD: "SYS"
ODBCSYSINI: "./"
関数が使用するレイヤーの指定
Layers:
- !Ref LambdaLayer
ハンドラー名の指定
Handler: "index.lambda_handler"
必要であれば、Lambda関数がEC2にアクセスするときに使用するロール名を指定します。
※ロール作成の参考ページ:https://dev.classmethod.jp/articles/tsnote-lambda-the-provided-execution-role-does-not-have-permissions-to-call-createnetworkinterface-on-ec2/
Role: "arn:aws:iam::109671571309:role/lambda_vpc_basic_execution_IRIS"
※各設定値を適宜変更してください。
後は、cloudformationの画面でスタックを作成し、ymlを実行するだけです。
ymlのアップロード手順は以下の通りです。
https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/
cloudformation.ymlをアップロードし、スタック名を任意に決定します。
この後、2画面表示されますが、すべてデフォルトで「次へ」と「送信」ボタンをクリックします。
正しく作成できるとこの表示になります。
作成したスタックの「リソース」タブをクリックすると、lambda関数へのリンクが表示されます。
テスト実行が成功すると以下のような出力が表示されます。
接続できない場合は、環境変数の値をご確認ください(「設定」タブで確認できます)。
Cloudformation便利ですね
もっと良い方法があるよ!や、こんな風にもできるよ!などありましたら、ぜひ返信欄で教えて下さいよろしくお願いします!
記事
Hiroshi Sato · 2020年12月16日
この文書では、インターシステムズの製品の中で、InterSystems CachéおよびIRIS data platformに関するライセンスポリシーを説明します。
インターシステムズのライセンスポリシーは、ソリューション・パートナー契約の際の契約書一式あるいはエンドユーザーに直接販売する際の契約書一式に含まれる製品に関する条件(この文書は製品別に存在します)という文書で規定されています。
なおこの文書は一般には公開していません。
ここでは、CachéおよびIRIS data platformのライセンスポリシーについてこの文書に書かれていることを要約および補足して説明します。
まずCachéシステムおよびIRIS data platformはこの文書で規定されているライセンスポリシーにでき得る限り忠実にそうようにライセンスチェック機構を実装しています。
しかしながら様々な技術的な制約によりライセンスポリシーとこれら製品のライセンスシステムを完全に一致させることはできません。
そしてもしシステム上のライセンスチェック機構の動作とライセンスポリシー上に不一致が発生した場合には、いかなる場合でもライセンスポリシーが優先されます。
つまりライセンスシステム上許容されている動作であっても、ライセンスポリシーに合致していない場合には、ライセンスポリシーに合うような運用を行わなければなりません。
次にライセンスの形態ですが、Cachéは、ユーザー単位の同時ユーザーライセンスとなっています。
IRIS data platformは、同時ユーザーライセンスに加えて、CPUコア数単位に課金するライセンスタイプも提供しています。
同時ユーザーライセンスの場合、同時ユーザー1名が課金の単位となり、その課金の単位をLU(License Unit)と呼びます。
システムの稼働に必要なライセンス容量を決定する際に、システムのピーク時に同時アクセスする最大ユーザー数を見積もることで必要なLU数が求められます。
さらにこの同時という概念も少し説明が必要です。
ここで言う同時とは、サーバーとユーザーが使用するクライアントデバイス間のソフトウェア的な接続(TCPセッションなど)が確立されているかどうかは関係なく、その仮想的な接続を通してサーバー上のインターシステムズデータプラットフォームの機能を実行できる準備がクライアントデバイス側にできている状態を意味しています。
例えば、接続プールやHTTPのように毎回接続を確立して終了後切断するような技術を使い、クライアント側とサーバー側のコネクションが切れたとしても、クライアント側のアプリケーションが何等かの状態で動作している限り(画面表示され、次のアクションを待っているなど)は、その同時接続が維持されていると考えます。
(これを技術的に完全に検知するのは不可能です。)
そして1LU当たり一人のユーザーの1つのデバイス当たり最大12個の接続あるいはプロセスを(合算して)同時に持つことができます。
同一人物が同時に複数のデバイスを使う場合にはデバイス毎にLUを消費します。
また人と直接結びつかずに単独でサーバーと接続するような1デバイス(医療検査機器など)も1ユーザーとみなします。
さらに人と直接結びつかないバッチプロセス(日時、週時、月時処理など)もLUの1つの形態とみなし最大12同時プロセスまで1LUとしてカウントできます。
エンドポイント(固有の IP アドレス)が同時に 25 以上の接続を処理する場合、そのアクセスは、プロキシと見なされ、各接続は個別のユーザーとして1LUとカウントされます。
IRIS data platformのCPUコアライセンスは、CPUコアがライセンスユニット(LU)の単位となります。
物理サーバー上でIRIS data platformを稼働する場合は、IRISインスタンスまたはIRISクラスタを構成する全てのIRISインスタンスが稼働する物理サーバー上のCPUコア数の総計をカウントします。
仮想サーバー上でIRIS data platformを稼働する場合は、IRISインスタンスまたはIRISクラスタを構成する全てのIRISインスタンスが稼働する仮想サーバー上のCPUコア数の総計をカウントします。
なおインテル社のCPUに採用されているハイパースレッディングのようなSMT(Simultaneous Multithreading Technology)は物理サーバー上ではCPUのコア数としてはカウントしません。
しかし一方で仮想環境やAWSなどのクラウド環境では仮想CPU (製品やベンダー、クラウドサービスによって呼び方が異なるが一般的にはvCPUと表現される)がコア数の単位になります。
記事
Toshihiko Minamoto · 2021年3月22日
Caché 2013.1 より、InterSystems は特殊な値を持つフィールドが使われるクエリプランのセレクションを改善する目的で Outlier Selectivity (外れ値の選択性) を導入しました。
この記事では、「Project」テーブルを例に使い、Outlier Selectivity の概要やそれが SQL のパフォーマンスを向上させる仕組み、またクエリを書く際の注意点などについて解説したいと思います。
# Selectivity (選択性)
まずは、Selectivity についてさっと説明します。 Selectivity とは、テーブル内の 1 つの列の中にある値に関するメタ情報のことです。 データが典型的なかたちで分布されていると想定した場合、「このテーブル内のこの列に特定の値を持つすべての行を要求するとしたら、通常取得できるのはテーブル内のどの程度の割合であろうか?」という疑問の答えとなる情報です。
Owener と Status という 2 つのフィールドを持つ「Project」という架空のテーブルについて考えます。 Owner にはプロジェクトを担当する従業員が入り、Status には PREP、OPEN、REVIEW、COMPLETE という 4 つのオプションの 1 つが入ります。 [Tune Table](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_opttable#GSQLOPT_opttable_tunetable) を実行すると、クラスのストレージに Selectivity の値があるのが確認できます。
3.3726%
25.000%
では、次の 2 つのクエリについて考えます。
SELECT * FROM Projects WHERE Owner = ?
SELECT * FROM Projects WHERE Status = ?
1 つ目のクエリが返すプロジェクトの割合は、平均で「Project」テーブルにあるすべてのプロジェクトの 3% をわずかに超える程度です。 2 つ目のクエリの平均は 25% です。 こうしたテーブルが JOIN や複数の WHERE 条件を伴うクエリで使用されるとなれば、3% と 25% では実行時間に大きな差が生じるほか、Caché が実行するクエリプランも変更される可能性があります。
# Outlier Selectivity (外れ値の選択性)
Selectivity を見ればすべてが分かるという訳ではありません! フィールド内の潜在的な値は特殊なかたちで分布される場合があります。 Outlier Selectivity を使用することで、特殊な値、すなわち、外れ値を 1 つ持つフィールドを賢く取り扱うことができます。
「Project」テーブルでは、プロジェクトのステータスは先ほどふれた 4 つのうちの 1 つになりますが、数年ほど経てば COMPLETE のプロジェクトの数が他のステータスのプロジェクトよりも大分多くなります。
先ほども言いましたが、次のクエリは*平均で*「Project」テーブルの 25% を返します。
SELECT * FROM Projects WHERE Status = ?
ですが、もっと細かく推測できるはずです! もし、WHERE 節が「WHERE Status = 'COMPLETE'」であれば、テーブルのほぼすべてを取得できますが、 「WHERE Status = 'PREP'」だと、取得できる割合はごくわずかです。
保管する前の WHERE:
0.25
Outlier Selectivity の導入により、以下を格納できます。
0.9:"COMPLETE"
0.03333
これで、以下の 2 つのクエリを区別することができます。
SELECT * FROM Projects WHERE Status = 'COMPLETE'
SELECT * FROM Projects WHERE Status = 'PREP'
1 つ目のクエリはテーブル内にあるすべてのプロジェクトの 90% を返し、2 つ目はわずか 3% しか返さないと推測できます。
複数のテーブルや複数のインデックスから選べる選択肢があるクエリの場合、90% と 3% ではパフォーマンスに大きな差が生じるほか、この場合も SQL エンジンが選択するクエリプランが変更される可能性があります。
# Outlier Selectivity を使ったクエリ
Outlier Selectivity には間違いなくメリットがあり、アプリケーションに変更を加える必要もありません。しかし、フル活用するには注意すべき点がいくつかあります。 デフォルトで、Caché は同じ形式が使われたすべてのクエリに対し、クエリプランを 1 つだけ生成します。 (先ほどの WHERE Status = 'COMPLETE' や WHERE Status = 'PREP' など)
デフォルトで、Caché は、クエリのパラメーターの値は外れ値ではないと想定します。 クエリに強制的に外れ値を考慮させるには、丸かっこを二重にして、外れ値のリテラル置換を抑制します。
SELECT * FROM Projects WHERE Status = (('COMPLETE'))
SELECT * FROM Projects WHERE Status = 'PREP'
丸かっこを二重にすると、SQL エンジンがクエリ内のパラメーターの特定の値に対してプランを生成することを強制できます。 これで Caché は、プロジェクトの 90% が取得されるときと、3% が取得されるときが分かるため、このクエリに対して 2 種類のプランを使うことができます。
また、BiasQueriesAsOutlier の値を 1 か 0 に設定すれば、Caché がデフォルトで外れ値以外の値を想定するかどうかも制御できます。 以下を実行すると、Caché は、外れ値を使用するクエリは稀なクエリではないと想定します。
1
0.9:"COMPLETE"
0.03333
以上の例は、Outlier Selectivity の概要、およびそれがクエリのパフォーマンスを向上させる仕組みについて理解する手掛かりとしてお役に立ちましたでしょうか? この情報の別のプレゼン資料や SQL の他の統計に関する詳細は、[Selectivity と Outlier Selectivity](http://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_opttable#GSQLOPT_opttable_tunetable_selectivity) と題した DocBook 文書をご覧ください。
記事
Toshihiko Minamoto · 2020年12月8日
インスタンスのデータに基づくビジネスインテリジェンスを実装しようと計画中です。 DeepSee を使うには、データベースと環境をどのようにセットアップするのがベストですか?

このチュートリアルでは、DeepSee の 3 つのアーキテクチャ例を示しながら、上記の質問を解決します。 基本的なアーキテクチャモデルを、その制限を重点に説明するところから始めましょう。 以降のモデルは、複雑さが中程度のビジネスインテリジェンスアプリケーションに推奨されており、ほとんどのユースケースで十分なはずです。 チュートリアルの最後には、高度な実装を管理できるように、アーキテクチャの柔軟性を強化する方法を説明します。
このチュートリアルに含まれる例では、新しいデータベースとグローバルマッピングを紹介し、それらをセットアップする理由とタイミングについて説明します。 アーキテクチャを構築する際には、より柔軟な例から得られるメリットについて説明します。
始める前に
プライマリサーバーと分析サーバー
データの高可用性を実現する場合、InterSystems では一般的にミラーリングとシャドウイングを使用して、ミラー/シャドウサーバーに DeepSee を実装することをお勧めしています。 データの元のコピーをホストするマシンを「プライマリサーバー」と呼び、データとビジネスインテリジェンスアプリケーションのコピーをホストするマシンを「分析(またはレポーティング)サーバー」と呼びます。
プライマリサーバーと分析サーバーを用意しておくことは非常に重要です。これは主に、いずれのサーバーにおいてもパフォーマンスに関する問題を回避するためです。 推奨アーキテクチャに関するドキュメントをご覧ください。
データとアプリケーションコード
ソースデータとコードを同じデータベースに保存することは、通常、規模の小さなアプリケーションでのみうまく機能します。 より大規模なアプリケーションでは、ソースデータとコードをそれぞれの専用データベースに保存することが推奨されます。専用のデータベースを使用することで、データを分離しながらも、DeepSee が実行するすべてのネームスペースでコードを共有することができます。 ソースデータ用のデータベースは、本番サーバーからミラーリングできるようにしておく必要があります。 このデータベースは、読み取り専用または読み取り/書き込みのいずれかです。 このデータベースでは、ジャーナリングを有効にしておくことをお勧めします。
ソースクラスとカスタムアプリケーションは、本番サーバーと分析サーバーの両方にある専用データベースに保存します。 これら 2 つのソースコード用データベースは同期している必要がなく、同じバージョンの Caché を実行している必要もありません。 コードが別の場所で定期的にバックアップされているのであれば、ジャーナリングは通常必要ではありません。
このチュートリアルでは、次の構成を使用しています。 分析サーバーの APP ネームスペースには、デフォルトのデータベースとして APP-DATA と APP-CODE があります。 APP-DATA データベースは、プライマリサーバーにある
ソースデータ用データベースのデータ(ソーステーブルのクラスとファクト)にアクセスできます。 APP-CODE データベースは、Caché コード(.cls と .INT ファイル)とほかのカスタムコードを保存します。 このようにデータとコードを分離するのは典型的なアーキテクチャであり、ユーザーは、DeepSee コードとカスタムアプリケーションを効率的にデプロイすることができます。
異なるネームスペースでの DeepSee の実行
DeepSee を使用したビジネスインテリジェンス実装は、異なるネームスペースから実行されることがよくあります。 この記事では単一の APP ネームスペースのセットアップ方法を示しますが、同じ手順を使えば、ビジネスインテリジェンスアプリケーションが実行するすべてのネームスペースをセットアップすることも可能です。
ドキュメント
ドキュメントに含まれる初回セットアップの実行に関するページの内容を理解しておくことをお勧めします。 このページには、Web アプリケーションのセットアップ、DeepSee グローバルを個別のデータベースに配置する方法、および DeepSee グローバルの代替マッピングのリストが含まれています。
* * *
このシリーズの第 2 部では、基本的なアーキテクチャモデルの実装について説明します。
記事
Tomohiro Iwamoto · 2021年5月18日
## 目的
Japan Virtual Summit 2021で、Kubernetesに関するセッションを実施させていただいたのですが、AzureのアカウントやIRIS評価用ライセンスキーをお持ちの方が対象になっていました。もう少し手軽に試してみたいとお考えの開発者の方もおられると思いますので、本記事では仮想環境でも利用可能なk8sの軽量実装である[mirok8s](https://microk8s.io/)で、IRIS Community Editionを稼働させる手順をご紹介いたします。
2022/1/7 若干の加筆・修正しました
マルチノード化する手順は[こちら](https://github.com/IRISMeister/iris_mk8s/blob/main/microk8s%E3%83%9E%E3%83%AB%E3%83%81%E3%83%8E%E3%83%BC%E3%83%89%E5%8C%96.md)に記載しています。
参考までに私の環境は以下の通りです。
|用途|O/S|ホストタイプ|IP|
|:--|:--|:--|:--|
|クライアントPC|Windows10 Pro|物理ホスト|172.X.X.30/24, (vmware NAT)192.168.11.1/24|
|mirok8s環境|ubuntu 20.04.1 LTS|上記Windows10上の仮想ホスト(vmware)|192.168.11.49/24|
ubuntuは、[ubuntu-20.04.1-live-server-amd64.iso](http://old-releases.ubuntu.com/releases/20.04.1/ubuntu-20.04.1-live-server-amd64.iso)を使用して、最低限のサーバ機能のみをインストールしました。
## 概要
IRIS Community EditionをKubernetesのStatefulSetとしてデプロイする手順を記します。
IRISのシステムファイルやユーザデータベースを外部保存するための永続化ストレージには、microk8s_hostpathもしくはLonghornを使用します。
使用するコードは[こちら](https://github.com/IRISMeister/iris_mk8s)にあります。
## インストレーション
microk8sをインストール・起動します。
```
$ sudo snap install microk8s --classic --channel=1.20
$ sudo usermod -a -G microk8s $USER
$ microk8s start
$ microk8s enable dns registry storage metallb
・
・
Enabling MetalLB
Enter each IP address range delimited by comma (e.g. '10.64.140.43-10.64.140.49,192.168.0.105-192.168.0.111'):192.168.11.110-192.168.11.130
```
ロードバランサに割り当てるIPのレンジを聞かれますので、適切な範囲を設定します。私の環境はk8sが稼働しているホストのCIDRは192.168.11.49/24ですので適当な空いているIPのレンジとして、[192.168.11.110-192.168.11.130]と指定しました。
この時点で、シングルノードのk8s環境が準備されます。
```
$ microk8s kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
metallb-system speaker-gnljw 1/1 Running 0 45s
metallb-system controller-559b68bfd8-bkrdz 1/1 Running 0 45s
kube-system hostpath-provisioner-5c65fbdb4f-2z9j8 1/1 Running 0 48s
kube-system calico-node-bwp2z 1/1 Running 0 65s
kube-system coredns-86f78bb79c-gnd2n 1/1 Running 0 57s
kube-system calico-kube-controllers-847c8c99d-pzvnb 1/1 Running 0 65s
container-registry registry-9b57d9df8-bt9tf 1/1 Running 0 48s
$ microk8s kubectl get node
NAME STATUS ROLES AGE VERSION
ubuntu Ready 10d v1.20.7-34+df7df22a741dbc
```
kubectl実行時に毎回microk8sをつけるのは手間なので、下記コマンドでエリアスを設定しました。以降の例ではmicrok8sを省略しています。
> 注意
> すでに"普通の"kubectlがインストールされていると、そちらが優先されてしまいますので、alias名をkubectl2にするなど衝突しないようにしてください。
```
$ sudo snap alias microk8s.kubectl kubectl
$ kubectl get node
NAME STATUS ROLES AGE VERSION
ubuntu Ready 10d v1.20.7-34+df7df22a741dbc
```
> 元の状態に戻すには sudo snap unalias kubectl
環境が正しく動作することを確認するためにIRISを起動してみます。下記コマンドの実行で、USERプロンプトが表示されるはずです。
```
$ kubectl run iris --image=intersystemsdc/iris-community:2022.1.0.209.0-zpm
$ watch kubectl get pod
$ kubectl exec -ti iris -- iris session iris
USER>
```
今後の作業に備えて、作成したPODを削除しておきます。
```
$ kubectl delete pod iris
```
## 起動
```
$ kubectl apply -f mk8s-iris.yml
```
> IRIS Community版なので、ライセンスキーもコンテナレジストリにログインするためのimagePullSecretsも指定していません。
しばらくするとポッドが2個作成されます。これでIRISが起動しました。
```
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
data-0 1/1 Running 0 107s
data-1 1/1 Running 0 86s
$ kubectl get statefulset
NAME READY AGE
data 2/2 3m32s
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.152.183.1 443/TCP 30m
iris ClusterIP None 52773/TCP 8m55s
iris-ext LoadBalancer 10.152.183.137 192.168.11.110 52773:31707/TCP 8m55s
```
この時点で、下記コマンドでirisのREST/APIで提供されているメトリックスを取得できるはずです。
```
$ curl http://192.168.11.110:52773/api/monitor/metrics
```
ポッドのSTATUSがrunningにならない場合、下記コマンドでイベントを確認できます。イメージ名を間違って指定していてPullが失敗したり、なんらかのリソースが不足していることが考えられます。
```
$ kubectl describe pod data-0
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 4m (x3 over 4m3s) default-scheduler 0/1 nodes are available: 1 pod has unbound immediate PersistentVolumeClaims.
Normal Scheduled 3m56s default-scheduler Successfully assigned default/data-0 to ubuntu
Normal Pulling 3m56s kubelet Pulling image "containers.intersystems.com/intersystems/iris-community:2021.1.0.215.3"
Normal Pulled 69s kubelet Successfully pulled image "containers.intersystems.com/intersystems/iris-community:2021.1.0.215.3" in 2m46.607639152s
Normal Created 69s kubelet Created container iris
Normal Started 68s kubelet Started container iris
```
下記コマンドでirisにO/S認証でログインできます。
```
$ kubectl exec -it data-0 -- iris session iris
Node: data-0, Instance: IRIS
USER>
```
下記で各IRISインスタンスが使用するPVCが確保されていることが確認できます。
```
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
dbv-mgr-data-0 Bound pvc-fbfdd797-f90d-4eac-83a8-f81bc608d4bc 5Gi RWO microk8s-hostpath 12m
dbv-data-data-0 Bound pvc-b906a687-c24c-44fc-acd9-7443a2e6fec3 5Gi RWO microk8s-hostpath 12m
dbv-mgr-data-1 Bound pvc-137b0ccf-406b-40ac-b8c5-6eed8534a6fb 5Gi RWO microk8s-hostpath 9m3s
dbv-data-data-1 Bound pvc-4f2be4f1-3691-4f7e-ba14-1f0461d59c76 5Gi RWO microk8s-hostpath 9m3s
```
dfを実行すると、データべースファイルを配置するための/vol-dataがマウント対象に表示されていなくて、一瞬、?となりますが、--all指定すると表示されます。
```
irisowner@data-0:~$ df --all
Filesystem 1K-blocks Used Available Use% Mounted on
・
・
/dev/sda2 205310952 26925908 167883056 14% /iris-mgr
/dev/sda2 205310952 26925908 167883056 14% /vol-data
/dev/sda2 205310952 26925908 167883056 14% /irissys/cpf
/dev/sda2 205310952 26925908 167883056 14% /etc/hosts
/dev/sda2 205310952 26925908 167883056 14% /dev/termination-log
/dev/sda2 205310952 26925908 167883056 14% /etc/hostname
/dev/sda2 205310952 26925908 167883056 14% /etc/resolv.conf
・
・
```
/dev/sda2はコンテナ内のデバイスではなく、ホスト上のデバイスなので、microk8s-hostpathの仕組み上、そのような表示になるのでしょう。
## 個別のポッド上のIRISの管理ポータルにアクセスする
下記コマンドで各ポッドの内部IPアドレスを確認します。
```
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
data-0 1/1 Running 0 46m 10.1.243.202 ubuntu
data-1 1/1 Running 0 45m 10.1.243.203 ubuntu
$
```
私の仮想環境のLinuxはGUIがありませんので、下記のコマンドを実行することで、Windowsのブラウザから管理ポータルにアクセスできるようにしました。
```
$ kubectl port-forward data-0 --address 0.0.0.0 9092:52773
$ kubectl port-forward data-1 --address 0.0.0.0 9093:52773
```
|対象|URL|ユーザ|パスワード|
|:--|:--|:--|:--|
|ポッドdata-0上のIRIS|http://192.168.11.49:9092/csp/sys/%25CSP.Portal.Home.zen|SuperUser|SYS|
|ポッドdata-1上のIRIS|http://192.168.11.49:9093/csp/sys/%25CSP.Portal.Home.zen|SuperUser|SYS|
> パスワードはCPFのPasswordHashで指定しています
データベースの構成を確認してください。下記のデータベースがPV上に作成されていることを確認できます。
|データベース名|path|
|:--|:--|
|IRISSYS|/iris-mgr/IRIS_conf.d/mgr/|
|TEST-DATA|/vol-data/TEST-DATA/|
## 停止
作成したリソースを削除します。
```
$ kubectl delete -f mk8s-iris.yml --wait
```
これで、IRISのポッドも削除されますが、PVCは保存されたままになっていることに留意ください。これにより、次回に同じ名前のポッドが起動した際には、以前と同じボリュームが提供されます。つまり、ポッドのライフサイクルと、データベースのライフサイクルの分離が可能となります。次のコマンドでPVCも削除出来ます(データベースの内容も永久に失われます)。
```
$ kubectl delete pvc -l app=iris
```
O/Sをシャットダウンする際には下記を実行すると、k8s環境を綺麗に停止します。
```
$ microk8s stop
```
O/S再起動後には下記コマンドでk8s環境を起動できます。
```
$ microk8s start
```
microk8s環境を完全に消去したい場合は、microk8s stopを「実行する前」に下記を実行します。(やたらと時間がかかりました。日頃は実行しなくて良いと思います)
```
$ microk8s reset --destroy-storage
```
## 観察
### ストレージの場所
興味本位の観察ではありますが、/iris-mgr/はどこに存在するのでしょう?microk8sはスタンドアロンで起動するk8s環境ですので、storageClassNameがmicrok8s-hostpathの場合、ファイルの実体は同ホスト上にあります。まずはkubectl get pvで、作成されたPVを確認します。
```
$ kubectl apply -f mk8s-iris.yml
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-ee660281-1de4-4115-a874-9e9c4cf68083 20Gi RWX Delete Bound container-registry/registry-claim microk8s-hostpath 37m
pvc-772484b1-9199-4e23-9152-d74d6addd5ff 5Gi RWO Delete Bound default/dbv-data-data-0 microk8s-hostpath 10m
pvc-112aa77e-2f2f-4632-9eca-4801c4b3c6bb 5Gi RWO Delete Bound default/dbv-mgr-data-0 microk8s-hostpath 10m
pvc-e360ef36-627c-49a4-a975-26b7e83c6012 5Gi RWO Delete Bound default/dbv-mgr-data-1 microk8s-hostpath 9m55s
pvc-48ea60e8-338e-4e28-9580-b03c9988aad8 5Gi RWO Delete Bound default/dbv-data-data-1 microk8s-hostpath 9m55s
```
ここで、data-0ポッドのISC_DATA_DIRECTORYに使用されている、default/dbv-mgr-data-0 をdescribeします。
```
$ kubectl describe pv pvc-112aa77e-2f2f-4632-9eca-4801c4b3c6bb
・
・
Source:
Type: HostPath (bare host directory volume)
Path: /var/snap/microk8s/common/default-storage/default-dbv-mgr-data-0-pvc-112aa77e-2f2f-4632-9eca-4801c4b3c6bb
```
このpathが実体ファイルのありかです。
```
$ ls /var/snap/microk8s/common/default-storage/default-dbv-mgr-data-0-pvc-112aa77e-2f2f-4632-9eca-4801c4b3c6bb/IRIS_conf.d/
ContainerCheck csp dist httpd iris.cpf iris.cpf_20210517 _LastGood_.cpf mgr
$
```
> storageClassNameにhostpathは使用しないでください。microk8s_hostpathとは異なり、同じフォルダに複数IRISが同居するような状態(破壊された状態)になってしまいます。
### ホスト名の解決
StatefulSetでは、各ポットにはmetadata.nameの値に従い、data-0, data-1などのユニークなホスト名が割り当てられます。
ポッド間の通信に、このホスト名を利用するために、[Headless Service](https://kubernetes.io/ja/docs/concepts/services-networking/service/#headless-service)を使用しています。
```
kind: StatefulSet
metadata:
name: data
kind: Service
spec:
clusterIP: None # Headless Service
```
> この特徴は、ノード間で通信をするShardingのような機能を使用する際に有益です。本例では直接の便益はありません。
nslookupを使いたいのですが、kubectlやk8sで使用されているコンテナランタイム(ctr)にはdockerのようにrootでログインする機能がありません。また、IRISのコンテナイメージはセキュリティ上の理由でsudoをインストールしていませんので、イメージのビルド時以外のタイミングで追加でソフトウェアをapt install出来ません。ここではbusyboxを追加で起動して、そこでnslookupを使ってホスト名を確認します。
```
$ kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
/ # nslookup data-0.iris
Server: 10.152.183.10
Address 1: 10.152.183.10 kube-dns.kube-system.svc.cluster.local
Name: data-0.iris
Address 1: 10.1.243.202 data-0.iris.default.svc.cluster.local
/ #
```
10.152.183.10はk8sが用意したDNSサーバです。data-0.irisには10.1.243.202というIPアドレスが割り当てられていることがわかります。FQDNはdata-0.iris.default.svc.cluster.localです。同様にdata-1.irisもDNSに登録されています。
## 独自イメージを使用する場合
現在のk8sはDockerを使用していません。ですので、イメージのビルドを行うためには別途Dockerのセットアップが必要です。
> k8sはあくまで運用環境のためのものです
### サンプルイメージを使用する場合
イメージはどんな内容でも構いませんが、ここでは例として[simple](https://github.com/IRISMeister/simple)を使用します。このイメージはMYAPPネームスペース上で、ごく簡単なRESTサービスを提供します。データの保存場所をコンテナ内のデータベース(MYAPP-DATA)から外部データベース(MYAPP-DATA-EXT)に切り替えるために、cpfのactionにModifyNamespaceを使用しています。
mk8s-simple.ymlとしてご用意しました(mk8s-iris.ymlとほとんど同じです)。これを使用して起動します。
### 自分でイメージをビルドする場合
ご自身でビルドを行いたい場合は、下記の手順でmicrok8sが用意した組み込みのコンテナレジストリに、イメージをpushします。
> 内容のわからない非公式コンテナイメージって...ちょっと気持ち悪いかも、ですよね。
(Docker及びdocker-composeのセットアップが済んでいること)
```
$ git clone https://github.com/IRISMeister/simple.git
$ cd simple
$ ./build.sh
$ docker tag dpmeister/simple:latest localhost:32000/simple:latest
$ docker push localhost:32000/simple:latest
```
このイメージを使用するように、ymlを書き換えます。
```
mk8s-simple.ymlを編集
前) image: dpmeister/simple:latest
後) image: localhost:32000/simple
```
### 起動方法
既にポッドを起動しているのであれば、削除します。
```
$ kubectl delete -f mk8s-iris.yml
$ kubectl delete pvc -l app=iris
```
```
$ kubectl apply -f mk8s-simple.yml
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.152.183.1 443/TCP 3h36m
iris ClusterIP None 52773/TCP 20m
iris-ext LoadBalancer 10.152.183.224 192.168.11.110 52773:30308/TCP 20m
$
$ curl -s -H "Content-Type: application/json; charset=UTF-8" -H "Accept:application/json" "http://192.168.11.110:52773/csp/myapp/get" --user "appuser:SYS" | python3 -mjson.tool
{
"HostName": "data-1",
"UserName": "appuser",
"Status": "OK",
"TimeStamp": "05/17/2021 19:34:00",
"ImageBuilt": "05/17/2021 10:06:27"
}
```
curlの実行を繰り返すと、HostName(RESTサービスが動作したホスト名)がdata-0だったりdata-1だったりしますが、これは(期待通りに)ロードバランスされているためです。
> まれにログインに失敗したり、待たされることがあります。Community EditionはMAX 5セッションまでですが、以前の何らかの操作によりその上限を超えてしまっている可能性があります。その場合、ライセンス上限を超えた旨のメッセージがログされます。
```
$ kubectl logs data-0
・
・
05/17/21-19:21:17:417 (2334) 2 [Generic.Event] License limit exceeded 1 times since instance start.
```
## Longhornを使用する場合
> 分散KubernetesストレージLonghornについては、[こちら](https://www.rancher.co.jp/pdfs/doc/doc-02-Hajimete_Longhorn.pdf)を参照ください。
IRISのようなデータベース製品にとってのメリットは、クラウド環境でアベーラビリティゾーンをまたいだデータベースの冗長構成を組めることにあります。アベーラビリティゾーン全体の停止への備えは、ミラー構成を組むことで実現しますが、Kubernetes環境であれば、分散Kubernetesストレージの採用という選択子が増えます。
> ミラー構成とは異なり、データベース以外のファイルも保全できるというメリットもあります。ただしパフォーマンスへの[負のインパクト](https://longhorn.io/blog/performance-scalability-report-aug-2020/)には要注意です。
### 起動方法
longhornを起動し、すべてのポッドがREADYになるまで待ちます。
```
$ kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.2.3/deploy/longhorn.yaml
$ kubectl -n longhorn-system get pods
NAME READY STATUS RESTARTS AGE
longhorn-ui-5b864949c4-72qkz 1/1 Running 0 4m3s
longhorn-manager-wfpnl 1/1 Running 0 4m3s
longhorn-driver-deployer-ccb9974d5-w5mnz 1/1 Running 0 4m3s
instance-manager-e-5f14d35b 1/1 Running 0 3m28s
instance-manager-r-a8323182 1/1 Running 0 3m28s
engine-image-ei-611d1496-qscbp 1/1 Running 0 3m28s
csi-attacher-5df5c79d4b-gfncr 1/1 Running 0 3m21s
csi-attacher-5df5c79d4b-ndwjn 1/1 Running 0 3m21s
csi-provisioner-547dfff5dd-pj46m 1/1 Running 0 3m20s
csi-resizer-5d6f844cd8-22dpp 1/1 Running 0 3m20s
csi-provisioner-547dfff5dd-86w9h 1/1 Running 0 3m20s
csi-resizer-5d6f844cd8-zn97g 1/1 Running 0 3m20s
csi-resizer-5d6f844cd8-8nmfw 1/1 Running 0 3m20s
csi-provisioner-547dfff5dd-pmwsk 1/1 Running 0 3m20s
longhorn-csi-plugin-xsnj9 2/2 Running 0 3m19s
csi-snapshotter-76c6f569f9-wt8sh 1/1 Running 0 3m19s
csi-snapshotter-76c6f569f9-w65xp 1/1 Running 0 3m19s
csi-attacher-5df5c79d4b-gcf4l 1/1 Running 0 3m21s
csi-snapshotter-76c6f569f9-fjx2h 1/1 Running 0 3m19s
```
mk8s-iris.ymlの全て(2箇所あります)のstorageClassNameをlonghornに変更してください。
もし、microk8s_hostpathで既に起動しているのであれば、ポッド、PVCともに全て削除したうえで、上述の手順を実行してください。つまり...
```
$ kubectl delete -f mk8s-iris.yml --wait
$ kubectl delete pvc -l app=iris
mk8s-iris.yml編集
前)storageClassName: microk8s-hostpath
後)storageClassName: longhorn
$ kubectl apply -f mk8s-iris.yml
```
> マウントしたLonghorn由来のボリュームのオーナがrootになっていたのでsecurityContext:fsGroupを指定しています。これ無しでは、データベース作成時にプロテクションエラーが発生します。
> fsGroup指定なしの場合
> ```
> $ kubectl exec -it data-0 -- ls / -l
> drwxr-xr-x 3 root root 4096 May 18 15:40 vol-data
> ```
> fsGroup指定ありの場合
> ```
> $ kubectl exec -it data-0 -- ls / -l
> drwxrwsr-x 4 root irisowner 4096 Jan 5 17:09 vol-data
> ```
> 2021.1まではfsGroup:52773を指定すると動きましたが、2022.1以後はfsGroup:51773を指定すると動きました。
下記を実行すれば、Windowsのブラウザから、[Longhorn UI](http://192.168.11.49/)を参照できます。
```
$ microk8s enable ingress
$ kubectl apply -f longhorn-ui-ingress.yml
```
ポート80を他の用途に使ってる場合、下記のようにport-forwardを使う方法もあります。この場合ポートは8080なので、URLは[こちら](http://192.168.11.49:8080/)になります。
```
$ kubectl -n longhorn-system port-forward svc/longhorn-frontend 8080:80 --address 0.0.0.0
```
> UIで、VolumeのStateが"Degraded"になっていますが、これはReplicaの数がnumberOfReplicasの既定値3を満たしていないためです。
以降の操作は、同様です。不要になれば削除します。
```
$ kubectl delete -f mk8s-iris.yml
$ kubectl delete pvc --all
```
### 削除方法
Longhorn環境が不要になった場合は、下記のコマンドで削除しておくと良いようです(いきなりdeleteしてはダメ)。
```
$ kubectl create -f https://raw.githubusercontent.com/longhorn/longhorn/v1.2.3/uninstall/uninstall.yaml
$ kubectl get job/longhorn-uninstall -n default -w
NAME COMPLETIONS DURATION AGE
longhorn-uninstall 1/1 79s 97s
^C
$ kubectl delete -f https://raw.githubusercontent.com/longhorn/longhorn/v1.2.3/deploy/longhorn.yaml
$ kubectl delete -f https://raw.githubusercontent.com/longhorn/longhorn/v1.2.3/uninstall/uninstall.yaml
```
### apply時のエラー
Longhornの前回の使用時に綺麗に削除されなかった場合に、apply時に下記のようなエラーが出ることがあります。
```
$ kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
・
・
Error from server (Forbidden): error when creating "https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml": serviceaccounts "longhorn-service-account" is forbidden: unable to create new content in namespace longhorn-system because it is being terminated
Error from server (Forbid
```
上記のuninstall.yamlを使った削除手順をもう一度実行したら回復しました。
### その他気づいた事
storageClassにmicrok8s_hostpathを指定した場合、[マルチノード環境](https://microk8s.io/docs/clustering)ではsecurityContext:fsGroupが正しく機能しないようです。その結果、下記のようなエラーが発生して、データベースの作成に失敗します(Error=-13はPermission denieです)。longhornは問題なく動作しました。
```
01/07/22-23:11:32:729 (1205) 1 [Utility.Event] ERROR #503: Error executing [Actions] section in file /iris-mgr/IRIS_conf.d/merge_actions.cpf
01/07/22-23:11:32:729 (1205) 1 [Utility.Event] ERROR #507: Action 'CreateDatabase' failed at line 2, Method Config.CPF:CreateOneDatabase(), Error=ERROR #5032: Cannot create directory '/vol-data/db/TEST-DATA/, Error=-13'
```
## InterSystems Kubernetes Operator
IKOもmicrok8sで動作しますが、Community向けの機能ではないので、今回のご紹介は見送りました。
記事
Minoru Horita · 2020年4月30日
データを格納するための魔法の剣であるグローバルは、かなり前から存在しています。しかしながら、これを効率的に使いこなせる人や、この素晴らしい道具の全貌を知る人はそう多くありません。 グローバルを本当に効果を発揮できるタスクに使用すると、パフォーマンスの向上やソリューション全体の劇的な単純化といった素晴らしい結果を得ることができます(1、2)。
グローバルは、SQLテーブルとはまったく異なる特別なデータの格納・処理方法を提供します。 グローバルは1966年にM(UMPS)プログラミング言語で初めて導入され、医療データベースで使用されていました。 また、現在も同じように使用されていますが、金融取引など信頼性と高いパフォーマンスが最優先事項である他のいくつかの業界でも採用されています。
M(UMPS)は後にCaché ObjectScript(COS)に進化しました。 COSはInterSystemsによってMの上位互換として開発されました。 元の言語は現在も開発者コミュニティに受け入れられており、いくつかの実装で生き残っています。 ウェブ上では、MUMPS Googleグループ、Mumpsユーザーグループ、ISO規格といった複数の活動が見られます。
最新のグローバルベースのDBMSは、トランザクション、ジャーナリング、レプリケーション、パーティショニングをサポートしています。 つまり、現代的で、信頼性が高く、高速な分散システムの構築に使用できます。
グローバルは、リレーショナルモデルの限界に制限されません。 特定のタスクに最適化されたデータ構造を自由に作成できます。 多くのアプリケーションにとって、グローバルの合理的な使用は、従来のリレーショナルアプリケーション開発者の理想でしかなかった速度を実現する真の特効薬になるかもしれません。
グローバルは、高レベルおよび低レベル両方の多くの最新のプログラミング言語でデータ保存方法として使用できます。 そのため、この記事ではグローバルの由来となった言語ではなく、グローバルに限定して焦点を当てることにします。
2. グローバルの仕組み
まずはグローバルの仕組みと、そのメリットを理解しましょう。 グローバルはさまざまな視点から見ることができます。 このパートでは、グローバルをツリーまたは階層型データストレージと見なします。
簡単に言えば、グローバルは永続的配列です。 つまり、自動的にディスクに保存される配列ということです。
これ以上簡単にデータを保存する方法を想像するのは困難です。 プログラムコード(COS/M言語で記述)では、その名前の前に^記号があるという点だけが通常の連想配列とは異なっています。
必要なコマンドはすべて非常に簡単で、1時間で習得が可能で、データをグローバルに保存するのにSQLの知識は必要ありません
最も単純な例である、2つの分岐を持つ単一階層のツリーから始めましょう。 以下の例はCOSで記述されています。
Set ^a("+7926X") = "John Sidorov"
Set ^a("+7916Y") = "Sergey Smith"
データがグローバルに挿入されると(Setコマンド)、次の3つの処理が自動的に行われます。
ディスクへのデータ保存。
インデックスの構築。 括弧内にあるのは添え字、等号の右側にあるのはノードの値です。
ソート処理。 データはキーでソートされます。 データの探索を行うと「Sergey Smith」が最初に返され、その後に「John Sidorov」が返されます。 グローバルからユーザーのリストを取得する場合、データベースはソートに時間を費やしません。 実在しないキーも含め、任意のキーから始まるソート済みのリストをリクエストできます(出力は実在しないキーの後に続く最初の実在するキーから始まります)。
これらの処理はすべて驚異的な速度で実行されます。 筆者宅のシステム(i5-3340、16GB、HDD WD 1TB Blue)では、1回のプロセスで毎秒1,050,000件のレコードが挿入されました。 マルチコアシステムでは、毎秒数億件のレコードを挿入できる可能性があります。
もちろん、レコードの挿入速度自体にはあまり意味がありません。 噂ですが、例えばVisaではデータをテキストファイルに書き込んでいるそうです。 ただし、グローバルを使用すれば、高速で使いやすく、構造化され、インデックス化されたストレージを得ることができます。
グローバルの最大の強みは、新しいノードをグローバルに挿入する速度です。
データは常にグローバル内でインデックス化されています。 単一階層や階層を下るツリー探索は常に大変高速です。
グローバルの第2階層と第3階層にいくつか枝を追加してみましょう。
Set ^a("+7926X", "city") = "Moscow"
Set ^a("+7926X", "city", "street") = "Req Square"
Set ^a("+7926X", "age") = 25
Set ^a("+7916Y", "city") = "London"
Set ^a("+7916Y", "city", "street") = "Baker Street"
Set ^a("+7916Y", "age") = 36
、グローバルを使用して複数階層のツリーを構築することができます。 挿入が発生するたびに自動インデックス付けが行われるため、どのノードにもほぼ瞬時にアクセスできます。 ツリー内のどの階層の枝も、キーでソートされます。
ご覧のとおり、キーと値との両方にデータを保存できます。 Cachéではキーを組み合わせた長さ(すべてのインデックスの長さの合計)は511バイトまで保存でき、値は最大3.6 MB まで保存できます。 ツリーの階層数(次元数)の上限は31です。
もう1つのすばらしい点は、上位階層のノードの値を定義せずにツリーを構築できることです。
Set ^b("a", "b", "c", "d") = 1
Set ^b("a", "b", "c", "e") = 2
Set ^b("a", "b", "f", "g") = 3
空の円は値のないノードを表しています。 グローバルをより深く理解するため、グローバルを庭木やファイルシステム名ツリーと比較してみましょう。
グローバルを庭や畑で育つ普通の木やファイルシステムといった、よく見慣れた階層構造と比較してみます。
ご覧のとおり、普通の木では葉と果実は枝の先端でのみ育ちます。 ファイルシステムの場合も、情報は完全ファイル名とも呼ばれる枝の先端に保存されます。
そして、こちらがグローバルのデータ構造です。 違い:
内部ノード:グローバルの情報は枝の先端だけでなく、すべてのノードに保存できます。
外部ノード: グローバルにはファイルシステムと庭木には必須ではありませんが、定義された枝の端(値のある先端)が必要です。
内部ノードに関しては、グローバルの構造をファイルシステム名ツリーと庭木構造の上位セットとして扱うことができます。 したがって、グローバルの構造はより柔軟になっています。
大まかに言うと、グローバルは各ノードでのデータ保存に対応した構造化ツリーです。
グローバルの仕組みをよりよく理解するため、ファイルシステムの作成者が情報の格納にグローバルと同じ手法を採用した場合にどうなるかを想像してみましょう。
あるフォルダー内の最後のファイルが削除された場合、そのフォルダー自体だけでなく、その削除対象のフォルダーのみを含むすべての上位階層のフォルダーも削除されることになります。
この場合、フォルダーはまったく必要ありません。 サブファイルを含むファイルとサブファイルのないファイルがあるとします。 普通の木と比較して見ると、一つ一つの枝が実になることが分かります。
README.txtのようなものは不要になりそうです。 ただし、フォルダーの内容に関して言及したいすべての情報はフォルダーファイル自身に書き込まれる可能性があります。 通常、ファイル名はフォルダー名と区別されません(例えば、 /etc/readme はファイルにもフォルダーにもなり得ます)。つまり、ファイルのみを操作することで十分ということになります。
サブフォルダーとファイルを含むフォルダーは、はるかに高速に削除できるでしょう。 数百万件の小さなファイルの削除がいかに時間がかかり、困難であるかを伝える記事がネット上にいくつか存在します(1、2、3)。 ただし、グローバルに基づいて疑似ファイルシステムを作成した場合は数秒か数分の1秒しかかかりません。 筆者の自宅PCでサブツリーの削除をテストしたときは、HDD(SDDではない)上の2階層のツリーから9,600万ノード~3億4,100万ノードを削除できました。 また、重要な事ですが、ここで話題にしているのはグローバルを含むファイル全体の削除ではなく、グローバルツリーの一部を削除することです。
グローバルのもう1つの長所は、再帰的処理をすることなくサブツリーを削除できることです。信じられないほど高速です。
上記のツリーでは、次のようなKillコマンド1つでサブツリーを削除できます。
Kill ^a("+7926X")
グローバルで実行できるアクションをよりよく理解できるよう、以下の小さな表にまとめています。
COSのグローバルに関連する主なコマンドと機能
Set
ノードまでの枝(未定義の場合)とノード値を設定(初期化)します。
Merge
サブツリーをコピーします。
Kill
サブツリーを削除します。
ZKill
特定ノードの値を削除します。 そのノードから生じたサブツリーは影響を受けません。
$Query
ツリー全体を深さ優先探索します。
$Order
同じ階層の次の添え字を返します。
$Data
ノードが定義されているかどうかを確認します。
$Increment
ACIDの読み取りと書き込みを回避するため、ノード値のアトミックなインクリメント操作を実行します。 最近は、$Sequenceを代わりに使用することを推奨しています。
最後までお読みいただき、ありがとうございました。喜んで皆様からのご質問にお答えします。
免責事項:この記事は筆者(英語原文はSergey Kamenev氏によるものです)の私見を反映したものであり、InterSystemsの公式見解とは関係ありません。
続きは「グローバルはデータを保存するための魔法の剣です パート2 - ツリー」を読み進めてください。 グローバルに表示できるデータのタイプと、グローバルが最適に機能する場所について学習します。
記事
Shintaro Kaminaka · 2020年9月11日
開発者の皆さん、こんにちは。
今回の記事では前回の記事に引き続き、IRIS for Health上で、FHIRリポジトリ+OAuth2認可サーバ/リソースサーバを構成する方法をご案内します。
(注意:2020.4以降のバージョンではこの記事に記載されているスコープ指定では正しくリソースが取得できません。詳細はこちらの記事をご覧ください。)
パート1では、事前準備と、OAuth2認可サーバを構成し、アクセストークンを取得するとこまでをご紹介しました。このパート2では、FHIRリポジトリの構築方法と、OAuth2クライアント/リソースサーバの構成方法をご紹介していきます。
今日構成する、FHIRリポジトリおよび、OAuth2クライアント/リソースサーバの構成は、前回パート1で構成したOAuth2認可サーバのIRISインスタンスと分けることもできますし、同じインスタンスに同居させることもできます。この記事の中では前回と同じインスタンス上に構成していきます。
FHIRリポジトリの構築とOAuth Client Nameの指定
FHIRリポジトリの構築方法は、過去の記事「Azure上でIRIS for Healthをデプロイし、FHIR リポジトリを構築する方法」で紹介しています。
管理ポータルにアクセスし、ネームスペース/データベースを作成、さらにHealthメニューからFHIRリポジトリを構築する手順はこの記事を参考にして進めてください。この記事の中でもネームスペース/データベース=FHIRSERVER , FHIR endpointを/csp/healthshare/fhirserver/fhir/r4として構成しています。
構築後の以下の画面で、endpoint URL /csp/healthshare/fhirserver/fhir/r4 をクリックして構成画面を開きます。
構成画面で OAuth Client Name を欄に、これから作成するOAuth2クライアントの構成名を入力しておきます。先にOAuth2クライアントを構成している場合はその名前に合わせてください。
ここでは、「FHIRResource」という文字列にしておきます。変更するには上記画面で「Edit」ボタンを押して変更し、「Update」ボタンで保存します。
OAuth2クライアントの構成
次は、OAuth2クライアント構成を作成していきましょう。
管理ポータルのシステム管理→セキュリティ→OAuth2.0 と進み、前回パート1とは異なり、「サーバ」ではなく、「クライアント」を選択します。
次の画面では「サーバの説明を作成」をクリックして、OAuth2認可サーバへ接続するための構成を作成します。
サーバデスクリプション ページで発行者エンドポイントには、パート1で構成した認可サーバのエンドポイントを入力します。
以下はパート1で構成したOAuth2認可サーバの構成画面です。
SSL/TLS構成には、パート1の事前準備で作成した、SSL/TLS構成「SSL4CLIENT]を入力します。
項目を入力したら「発見して保存」を実行して、OAuth2認可サーバから情報を取得します!
アクセスに成功すると以下のように取得できた情報が表示されます。パート1事前準備で用意したホスト名を指定したSSL証明書が正しく作成・認識されていない場合、この過程でエラーが発生することがありますので、ご注意ください。
注意:この連載のパート1でDLしたdocker-containerファイルを使っている場合でも、IRISコンテナ→Apacheコンテナへホスト名を指定したアクセスがうまくいかない場合があります。この場合、以下のようにextra_hostsとして、docker-compose.ymlファイルに自分のマシンのホスト名とIPアドレスを入力すると解決できることがあります。
extra_hosts: - <yourhostname>:<your ip address>
「保存」を押して構成を保存すると、以下のページに戻りますので、続いて「クライアント構成」を選択してFHIRリポジトリ用の構成を作成していきます。
OAuth2クライアントにクライアント構成を追加する
ややこしいタイトルですが、次は今作成したOAuth2クライアント設定(どのOAuth2認証サーバに接続するかという情報をもつ)に、クライアント構成(OAuth2クライアントとしてOAuth2認可サーバに接続したい、具体的なFHIRリポジトリやCSPアプリケーションなどの情報)を追加します。
次の画面では「クライアントの構成を作成」をクリックして以下の画面を表示し、必要な項目を設定していきます。
最初に、クライアントの種別=リソース・サーバ を選択すると、下記入力画面と同じになります。
アプリケーション名
FHIRResource : FHIRリポジトリの構成で「OAuth Client Name」に入力した値を入力します。
クライアント名
OAuth2認可サーバに登録されるクライアント名です。アプリケーション名と同じでもかまいませんが、ここでは違う名前にしました。
説明
この構成の説明を入力します。
クライアントの種別
「リソース・サーバ」を選択します。
SSL/TLS構成
パート1事前準備で用意したSSL/TLS構成を指定します。
入力が完了したら、「動的登録と保存」ボタンをクリックして保存とサーバへの登録を行います。(ちょっとわかりにくいですが)ボタンの表示が「動的登録と保存」から「更新メタデータを取得して保存」に変わったら登録が成功しています。
OAuth2認可サーバ側の構成情報を見て、本当に登録されているか確認してみましょう。
管理ポータル→システム管理→セキュリティ管理→OAuth2.0→サーバ の画面で「クライアントデスクリプション」をクリックすると以下のように登録されていることがわかります。
名前がクライアント名で指定した名前になっていることが確認できます。
パート1ではPostmanからアクセステストする際は、このクライアントデスクリプション画面をさらに進んで表示される、クライアント認証情報(クライアントIDとくらい案tの秘密鍵)を手動でコピーしましたが、今回は動的登録の過程でこれらの情報はクライアント側に受け渡されています。
PostmanからOAuth2アクセストークンを使って、FHIRリポジトリへアクセスする
それではいよいよ、Postmanからアクセスしてみましょう!
まずアクセストークンを取得します。基本はパート1の最後で取得した方法と同じですが、アクセストークンの発行先を表すaudienceパラメータを追加する必要があります。
aud=https://<hostname>/csp/healthshare/fhirserver/fhir/r4
Postmanで具体的に追加するには、以下のようにAuthorization Codeのendpoint URLにパラメータとして追加します。(Postmanの画面の都合上パラメータの全体が見えませんが、上記の aud=https://<hostname>/csp/healthshare/fhirserver/fhir/r4 をすべて記載してください)
注意:Postmanに入力するClient IDやClient Secretは先ほどのリソースサーバの動的登録で発行されたものに変更する必要はありません。パート1で追加した postman用に発行されたクライアントIDと秘密鍵を使用します。
アクセストークンが取得できたら、その内容をコピーしておいてください。
PostmanではこのままAuthorizationのTYPEをOAuth2にしておくと、アクセストークンを送信する機能がありますが、IRIS for HealthのFHIRリポジトリでは、OAuth2のアクセストークンだけではなく、Basic認証のユーザ・パスワード情報も送信する必要があります。
そのため、Postmanからアクセスする場合は、(ちょっと手間ですが)AuthorizationのTYPEはBasic Authにして、ユーザ名パスワードを入力し、アクセストークンはFHIRリポジトリへのRESTリクエストのParameterとして送信する必要があります。
具体的には、まず以下の画面のようにユーザ名・パスワードを入力します。このユーザ情報は、アクセストークンのsub内に含まれるユーザ情報と一致しているかの確認が行われるため、必ずアクセストークン取得時に入力したユーザ情報と同じユーザである必要があります。
次に、Paramsタブで、 access_token にパラメータに先ほどのアクセストークン値を入力します。
FHIRリポジトリを構築したばかりであれば、リポジトリには何のデータもはいってはいませんが、Patientデータをリクエストしてみましょう!
Request URL には https://<hostname>/csp/healthshare/fhirserver/fhir/r4/Patient を入力し、HTTPのメソッドはGETを選択します(上の図のようになります)
Sendボタンを押してリクエストを投げてみましょう!以下のようにFHIRのBundleが取得できれば、アクセストークンを使用したFHIRリポジトリへのアクセスは成功です!
FHIRリポジトリへのデータの登録や検索の方法については、IRIS for Healthのドキュメントやコミュニティの記事をご参照ください。
IRIS for Health 2020.1 日本語ドキュメント:リソースリポジトリ
IRIS for Health 2020.3 英語ドキュメント:Resource Repository
(この記事を執筆した段階では、2020.3はPreview Editionです。)
いかがでしたか?FHIRリポジトリへのアクセスが成功したでしょうか?
この連載で紹介した構成内容は最も単純な構成ですが、実際のFHIRプロジェクトではユーザの認めたスコープによってどの範囲のデータまで返すように実装するか?といった検討と実装が必要になってきます。
開発者コミュニティでは引き続きFHIRに関する情報を発信していきたいと思います。 「発見して保存」を押した後、下記エラーが表示されます。
自己署名証明書を使用しているせいかと思いましたが、何か原因がございますでしょうか?
※ブラウザで管理ポータルに繋ぐ際、自己署名証明書の警告は表示されます。
お試しいただきありがとうございます。
IRIS for Health のコンテナから、ホストに対してホスト名でアクセスできていない可能性があります。以下のコマンド試してみてください。
#docker exec -it irishealth bashirisowner@iris:~$ ping <yourhostname>ping: <yourhostname>: Name or service not known
このメッセージが出る場合、記事内にある extra_hosts: の追加を試してみてください。(この注意を書く位置がよくなったです。。。お手数かけて申し訳ありません。)
エラーが出た時点でストップしてしまい、少し下の注釈を見落としておりました。申し訳ございません。
記載頂いたpingは通る状態だったのですが、extra_hostsの設定は行っていなかったため設定したところ無事動作しました。
ただ以下のようにlocalhostではNGで、実際のIPアドレスを指定しないとエラーが解消しませんでしたので念のため共有させて頂ければと思います。
NG
```
iris:
image: store/intersystems/irishealth-community:2020.3.0.200.0
init: true
container_name: irishealth
ports:
- "52773:52773"
- "1972:1972"
environment:
- ISC_DATA_DIRECTORY=/ISC/dur
- TZ=JST-9
volumes:
- .:/ISC
extra_hosts:
- my-ubuntu:172.0.0.1
```
OK
```
iris:
image: store/intersystems/irishealth-community:2020.3.0.200.0
init: true
container_name: irishealth
ports:
- "52773:52773"
- "1972:1972"
environment:
- ISC_DATA_DIRECTORY=/ISC/dur
- TZ=JST-9
volumes:
- .:/ISC
extra_hosts:
- my-ubuntu:192.168.0.123
``` ご確認と情報提供ありがとうございました。
記事の注意書きの位置も修正しておきました。 記事の修正誠にありがとうございます。
その後手順通り試したところ、FHIRのBundle取得まで正常に完了することが出来ました。
記事
Mihoko Iijima · 2023年5月24日
開発者の皆さん、こんにちは!
IRISのInteroperability(相互運用性)機能(Ensembleのプロダクション)を使用してどのようなことができるか、コード例をご覧いただきながら/サンプルを動かしながらご確認いただける記事をまとめてみました。
ぜひご参照ください。
記事一覧:
インストール環境をお持ちでない方でもお試しいただけるチュートリアル
ファイル連携を試してみたい
常駐プロセスを作りたい
レコードマップ機能を使いたい
MQTTを使いたい
FHIRサーバサイドアプリケーションを試したい
Interoperabilityを学習したい
システム連携の自動的な流れの中にユーザからの指示を介入できる「ワークフローコンポーネント」を試したい(2023/6/1 追記)
REST経由で情報を入力する場合の Interoperability(相互運用性機能)のサンプル(2023/5/30 追記)
✅ インストール環境をお持ちでない方でもお試しいただけるチュートリアル
Developer Hubというページをご存知でしょうか。最近追加された開発者向け情報をまとめたポータルで、この中に事前準備不要でお試しいただけるチュートリアルが4種類含まれています。
その中の、Interoperability(相互運用性)チュートリアルでは、Redditに新しく投稿された記事=(https://www.reddit.com/new/)を一定間隔で取得し、全投稿の中から「猫(cat)」🐈について記載されている情報のみを抽出し、対象記事をファイル出力する流れをご体験いただけます。
上記画像をクリックするとチュートリアルトップページに移動します。後は画面右下に表示される ボタンをクリックするだけでチュートリアルがスタートします。
体験内容詳細については「IRIS の Interoperability(相互運用性)を試せるチュートリアル」で画面例付きでご紹介しています。ぜひご覧ください。
✅ ファイル連携を試してみたい
Interoperability機能には、ファイルやHTTP、ODBC/JDBCなど標準的なアクセスの基本処理を記述したアダプタというクラスが用意されています。
「IRIS Interoperability機能を使ったファイル連携」の中では、ファイル入力で行われる特定ディレクトリの定期的な監視、データ取り込みの部分をアダプタを利用して作成する例をご紹介しています。
✅ 常駐プロセスを作りたい
ObjectScriptレベルでも常駐プロセスを作成できますが、「Interoperability機能を使った常駐プロセスの作り方」の中では、「常駐プロセスの監視」や「ログ」の機能も付いたInteroperabilityを利用した常駐プロセスの作り方を解説しています。
手順とコード解説付き+サンプルコードも公開されています。
✅ レコードマップ機能を使いたい
固定長または、区切りマーク付きファイルの入出力を支援する機能です。(CSVを使用する場合は、CSVファイルを読み込ませてレコードマップ機能を作ることもできます)
「レコードマップで何ができるか?」には、サンプルコードに沿った使用例の解説PDFがあります。(説明中「Ensemble」と出てきますが「Interoperability」と同様の機能です)
✅ MQTTを使いたい
Interoperabilityのアダプタの中にはMQTTに対応したアダプタ含まれています。
「【GettingStarted with IRIS】MQTT を使った遠隔モニタリング(IRIS の MQTT アダプタを試してみよう!)」では、インターネット上に公開されているMQTTテスト用ブローカーを使用してサブスクライブする例をご紹介しています。
コンテナを使ったサンプルをご用意しています。
もう1つ、「IRISにてMQTTブローカーから気象データを取得しデータベースに格納する」では、プロダクション作成手順も含めた解説があります。ぜひご参照ください。
✅ FHIRサーバサイドアプリケーションを試したい
IRIS for Healthでは、医療情報交換標準規格 FHIR のリポジトリを作成できるのと、Interoperabilityを利用してFHIRサーバサイドアプリケーションを開発できます。
「FHIR R4 リソースリポジトリを簡単にお試しいただける開発環境テンプレートのご紹介」では、解説ビデオ付きでFHIRリポジトリへのアクセス方法、FHIRサーバーサイドアプリケーション開発例をご紹介しています。
また、コンテナでお試しいただけるサンプルもご用意しています。
✅ Interoperabilityを学習したい
「【はじめてのInterSystems IRIS】Interoperability(相互運用性)を使ってみよう!」は、サンプルコードをご覧いただきながら、Interoperability機能概要を確認できるシリーズ記事です。
これからInteroperabilityをはじめたい方、学習してみたい方に最適です。
✅ システム連携の自動的な流れの中にユーザからの指示を介入できる「ワークフローコンポーネント」を試したい
「システム連携の自動的な流れの中にユーザからの指示を介入できる「ワークフローコンポーネント」のサンプル」では、コンテナで動くサンプル環境を利用して、ユーザからの指示待ち、ユーザからの指示による処理の再開をご体験いただけます。
また、ご自身の環境でワークフローコンポーネントの動作を試されたい方向けに「ワークフローコンポーネントを使ってみよう!~使用手順解説~」もご用意しています。
ぜひお試しください。
記事
Toshihiko Minamoto · 2022年3月2日
これは、IRIS でリレーショナルデータをクエリするアナリストとアプリケーションに、さらに優れた適応性とパフォーマンスによるエクスペリエンスを提供する IRIS SQL のイノベーションをトピックとした短い連載の 3 つ目の記事です。 2021.2 では連載の最後の記事になるかもしれませんが、この分野ではさらにいくつかの機能強化が行われています。 この記事では、このリリースで収集し始めた**ヒストグラム**という追加のテーブル統計について、もう少し詳しく説明します。
### ヒストグラムとは?
ヒストグラムは数値フィールド(またはより広範には、厳密な順序を持つデータ)のデータ分布の近似表現です。 このようなフィールドの最小値、最大値、および平均値がわかれば役立ちますが、データが 3 つのポイント間でどのように分布しているかはほとんどわかりません。 ここで役立つのがヒストグラムです。値の範囲をバケットに分割し、バケットごとに出現するフィールド値の数をカウントします。
これは非常に柔軟な定義であるため、バケットがフィールド値に関して同じ「幅」になるように、またはカバーされるサンプル値の数に関して同じ「大きさ」になるように、バケットのサイズを選択することができます。 後者の場合、各バケットには同じパーセンテージの値が含まれるため、バケットはパーセンタイルを表します。 以下のグラフは、日数で表現された同じバケット幅を使用して、[Aviation Demo データセット](https://github.com/intersystems/Samples-Aviation)の EventData フィールドのヒストグラムをプロットしています。
### ヒストグラムが必要な理由
カリフォルニア州で 2004 年より前のすべてのイベントについて、このデータセットのクエリを実行しているとします。
SELECT * FROM Aviation.Event WHERE EventDate < '2004-05-01' AND LocationCountry = 'California'
「[ランタイムプランの選択](https://jp.community.intersystems.com/node/510746)」という前の記事では、テーブル統計で LocationCounty のようなフィールドの選択性と潜在的な外れ値をキャプチャする方法についてすでに説明しています。 しかし、そのような個別のフィールド値の統計は、EventDate での `<` 条件ではあまり実用的ではありません。 この条件の選択制を計算するには、2004 年 5 月 1 日までのすべての潜在的な EventDate 値の選択制を集計する必要があり、クエリのプランニング時に行えるような手っ取り早い見積もりではなく、それだけで非常に厳しいクエリとなる可能性があります。 ここで使用できるのがヒストグラムです。
EventDate 値の分布のヒストグラムデータを見てみましょう。今回は、データを同じサイズの 16 個のバケットに分割し、各バケットには 6.667% のデータが保持されています。 このようにすると、クエリコストの見積もりに使用できるパーセンタイルと選択制の数値に簡単に変換できます。 このテーブルを読み取るために、4 行目を見てみましょう。値の 20%(各 6.667% の 3 つのバケット)がこのバケットの下限である 2003 年 6 月 22 日より前にあり、さらに 6.667% の値が 2003 年 9 月 19 日まで保持されています。
<colgroup><col style="width:48pt" width="64"><col style="width:61pt" width="81"><col style="width:64pt" width="85"></colgroup>
Bucket
Percentile
Value
0%
21/12/2001
1
7%
02/07/2002
2
13%
19/01/2003
3
20%
22/06/2003
4
27%
19/09/2003
5
33%
30/12/2003
6
40%
01/10/2004
7
47%
01/10/2005
8
53%
20/08/2006
9
60%
14/01/2007
10
67%
02/04/2008
11
73%
14/05/2008
12
80%
29/11/2008
13
87%
01/06/2010
14
93%
30/10/2011
15
100%
26/09/2012
上記のクエリ例で使用されているカットオフ日(2004 年 5 月 1 日)は、5 番目のバケットにあり、その日付より前には 33% から 40% の値があります。 バケットが小さくなるにつれ、その_中_の分布はほぼ均一であると見なすことができ、下限と上限の間を単に補完することができます。つまり、この場合、選択性は約 37% となり、これをクエリコストの見積もりに使用することができます。
ヒストグラムの使用を可視化するには、もう一つ、累積分布グラフとしてプロットする方法があります。 X 軸で 2004 年 5 月 1 日の線(値)がどのように描かれるかを確認すれば、Y 軸で 約 37% と解釈できます。
上記の例では、わかりやすくするために上限のみの範囲条件を使用していますが、このアプローチは、下限または間隔条件(`BETWEEN` 句を使用するなど)を使用しても当然動作します。
2021.2 より、文字列を含むすべての照合フィールドのテーブル統計の一環としてヒストグラムを収集しており、それを使用して RTPC の一部として範囲選択性を推定することができるようになっています。 実世界での多くのクエリには日付(およびその他の)フィールドでの範囲条件が伴うため、この IRIS SQL の機能強化によって、多くのお客様のクエリプランに役立つと信じています。いつものように、皆さんの体験をお聞かせください。