検索

クリアフィルター
記事
Mihoko Iijima · 2023年5月26日

IRISジャーナル(z圧縮済み)をCachéにリストアする方法

これは InterSystems FAQ サイトの記事です。 IRISではジャーナルファイルが自動的に圧縮される仕組みが導入されています。 ジャーナルファイルの圧縮機能について詳しくは、別の記事「ジャーナル圧縮機能について」をご参照ください。 例えば、CachéからIRISへ移行された後に、念のためIRISで更新されたデータを手動でCachéにも反映させたいことばある場合に、IRISのジャーナルファイルをCachéにリストアすることができます。 手順は以下の通りです。 (手順1) IRISのジャーナルファイル(YYYYMMDD.nnnz) ファイルを解凍する(手順2,3) 解凍した ジャーナルファイルを Cachéに転送してリストアする リストアの方法として、以下の2パタンをご紹介 (A) 指定グローバルとデータベースについて、指定ジャーナルから、全エントリをリストア(B) 指定グローバルとデータベースについて、指定ジャーナルから、特定のアドレスまでリストアする (A) 指定グローバルとデータベースについて、指定ジャーナルから、全エントリをリストア (手順1) IRISサーバ上で以下のコマンドにより、ジャーナルを解凍する IRIS 2022.1 以降、現在実行中のジャーナル以外は、拡張子 z で圧縮されています。 以下のコマンドで解凍し、指定のフォルダにコピーします。 これをCachéにリストアしたいジャーナルファイルすべてに対して繰り返し実行してください。 例) set j1="/usr/irissys/mgr/journal/20230413.002z" set j2="/temp/20230413.002" write ##class(%SYS.Journal.File).Decompress(j1,j2) set j1="/usr/irissys/mgr/journal/20230413.003z" set j2="/temp/20230413.003" write ##class(%SYS.Journal.File).Decompress(j1,j2) 以下の実行例では、リストア対象データを作成しています。 USER>set ^abc="Dummyデータ" USER>set ^test1="USERデータベースで登録( I R I S )" USER>set $namespace="test1" TEST1>set ^Dummy=11000 TEST1>set ^DummyXX=2000 USERネームスペースはUSERデータベースにグローバルがセットされ、USERデータベースディレクトリは「/usr/irissys/mgr/user/」です。 TEST1ネームスペースはTEST1データベースにグローバルがセットされ、TEST1データベースディレクトリは「/usr/irissys/mgr/test1/」です。 (手順2) 手順1で解凍した、リストアしたいジャーナルを、すべて Cachéサーバに転送します。 (手順3) Cachéサーバ上で、上記コピーしてきたジャーナルを、連続してリストアします。 IRISからコピーして /temp に置いた 20230413.002 と 20230413.003 ファイルを以下のルールでリストアするように指定します。 [IRIS]/usr/irissys/mgr/user/  → [Cache]/usr/cachesys/mgr/user/ : ^abc と ^test1をリストア対象に指定する [IRIS]/usr/irissys/mgr/test1/  → [Cache]/usr/cachesys/mgr/test1/ : ^Dummy と ^DummyXXをリストア対象に指定する 実行コードは以下の通りです。 注意:Windows以外のOSにリストアする場合、データベースディレクトリの末尾にパスのマーク(/)を必ず入れて下さい。 //ジャーナルリストアに利用するクラスのインスタンスを生成 set jrn=##class(Journal.Restore).%New() //リストア開始ファイルの指定 set jrn.FirstFile="20230413.002" //リストア終了ファイルの指定 set jrn.LastFile ="20230413.003" //ジャーナルファイルの配置場所 do jrn.UseJournalLocation("/home/isjedu/irisjrn/") //カレントインスタンスのジャーナルログファイルを使うかどうか(-1は使わない) set jrn.JournalLog=-1 //ジャーナルリストア終了時、コミットされてないトランザクションをロールバックするかどうか set jrn.RollBack=0 //1つ目のデータベース set src(1)="/usr/irissys/mgr/user/" set to(1)="/usr/cachesys/mgr/user/" //第1引数:IRISのデータベースのデータベースディレクトリ //第2引数:リストア対象グローバル変数名(前方一致可) //IRISのUSERデータベースにある ^abcと、^iijimaをリストア do jrn.SelectUpdates(src(1),"abc") //^abc do jrn.SelectUpdates(src(1),"test1") //^test1 //ジャーナルリストア対象データベースディレクトリのリダイレクト指定 //第1引数:ジャーナルに記録されているIRISのデータベースディレクトリ //第2引数:リストア対象のCacheのデータベースディレクトリ set status=jrn.RedirectDatabase(src(1),to(1)) //もしエラーがあったらエラー情報を出力して終了 if $$$ISERR(status) { write $system.Status.GetErrorText(status) quit } //2つ目のデータベース set src(2)="/usr/irissys/mgr/test1/" set to(2) ="/usr/cachesys/mgr/test1/" do jrn.SelectUpdates(src(2),"Dummy*") ;; ^Dummy ^DummyXX set status=jrn.RedirectDatabase(src(2),to(2)) if $$$ISERR(status) { write $system.Status.GetErrorText(status) quit } //n個目のデータベース /* set src(n)="..(IRISのDBフォルダ名).." set to(n) ="..(CacheのDBフォルダ名).." do jrn.SelectUpdates(src(n),"xxx*") // ^xxx do jrn.SelectUpdates(src(n),"yyy") // ^yyy set status=jrn.RedirectDatabase(src(n),to(n)) if $$$ISERR(status) { write $system.Status.GetErrorText(status) quit } */ //リストア実行 set status=jrn.Run() if $$$ISERR(status) { write $system.Status.GetErrorText(status) quit } (B) 指定グローバルとデータベースについて、指定ジャーナルから、特定のアドレスまでリストア (手順1)(手順2)は、(A)と同様です。 (手順3) Cachéサーバ上で、上記コピーしてきたジャーナルを、連続してリストアします。 指定アドレス以降はリストアしない、というフィルタルーチンを、Caché サーバの %SYS に作成します。ルーチンの引数名はそのままにしておいてください。 作成詳細は、別記事「誤って削除したグローバルを復旧させる方法」の実施例以下をご覧ください。 %SYSネームスペースにルーチン名:ZJRNFILT で作成します。 例) ZJRNFILT(pid,dir,glo,type,restmode,addr,time) set restmode=1 // グローバル変数名に TEST1 が含まれいる // かつ ジャーナルレコードが9060180以降は restmode=0 に設定(=リストアしない) if (glo [ "TEST1") & (addr>=9060180) { set restmode=0 } quit (A)の手順のRun()メソッド実行の直前にフィルタルーチンの名称を設定する1文を加えるだけです。 //実行 set jrn.Filter="^ZJRNFILT" ;; <--- フィルタルーチン指定 set status=jrn.Run() 実行すると、ジャーナルをリストア完了後、以下のように2回聞かれます。 Do you want to rename your journal filter? Do you want to delete your journal filter? どちらも N と入力して、リターンしてください。
記事
Mihoko Iijima · 2024年2月29日

インデックス再構築が終わるまで新しく定義したインデックスを使用させない方法

これは InterSystems FAQ サイトの記事です。 新しいインデックスを定義した後、インデックスの再構築が完了する前にクエリを実行するとデータが存在しているにもかかわらず「検索結果0件」や検索結果数が徐々に増えるような状況が発生します。 インデックスを永続クラス定義(またはテーブル定義)に追加しコンパイルすることで今まで使用していたクエリ実行経路が削除され、再度同じクエリを実行するタイミングで新しいインデックス定義を含めた実行経路が作成されるためです。(この時にインデックス再構築が完了していないとインデックスデータが存在しない、または不完全であるため0件や徐々に検索結果数が増えるような状況を起こします。) これを起こさなために、新しいインデックスの再構築が終了するまでクエリオプティマイザにインデックスを使用させないように指定する方法が用意されています。 ※ 2024/8/2: 2024.1以降から利用できる方法を追加しました。 2024.1以降 CREATE INDEXのDEFERオプションを使用します(オプションを付けないCREATE INDEX文では、作成時にインデックスの再構築も同時に行われます)。 DEFERオプションを使用することで、インデックスは追加されますがインデックスの再構築は行われず、追加したインデックスはクエリオプティマイザが使用しないように「選択不可」に設定されます。 再構築が行えるタイミングで BUILD INDEX文を利用することで、再構築が終了すると同時に追加したインデックスが「選択可能」に自動的に設定されます。 なお、追加したインデックスの選択可/不可は管理ポータルのSQL画面で確認できます(管理ポータル > [SQL] > ネームスペース選択 > テーブル選択 > (画面右)カタログの詳細 > [マップ/インデックス]をチェック)。 ※インデックスの「選択可能」「選択不可」は、以降でご紹介するSetMapSelectability()メソッドを利用しても変更できます。 ※永続クラス定義を利用している場合、デフォルトではDDLの発行が無効化されています。クラス定義文に DdlAllowed属性 を追加することでDDL文の発行ができます(設定後、クラス定義をコンパイルする必要があります)。 以下定義を行った場合は、管理ポータルでは図のように見えます。 CREATE INDEX NameIdx On Sample.Person (Name) DEFER CREATE INDEX PrefIdx On Sample.Person (Pref) インデックス追加から再構築までの手順は以下の通りです。 1) 永続クラス定義の場合、DdlAllowed属性を設定します。 Class Sample.Person Extends %Persistent [ DdlAllowed ] 2) CREATE INDEXのDEFERオプションを付けてインデックス定義を追加します。 CREATE INDEX NameIdx On Sample.Person (Name) DEFER 3) 再構築ができるタイミングで、BUILD INDEXを実行します。 BUILD INDEX FOR TABLE Sample.Person INDEX NameIdx 4) BUILD INDEXが終了したら、追加したインデックスで影響を受けそうなクエリキャッシュを破棄します。 管理ポータルでクエリキャッシュを削除するには、管理ポータル > [システムエクスプローラ] > [SQL] > (対象ネームスペースに切り替えた後) > [アクション] > [クエリキャッシュ削除] プログラムからキャッシュを破棄する場合は、「プログラムでクエリキャッシュを削除する方法」をご参照ください。 2023.1以前 2022.1以降では、$SYSTEM.SQL.Util.SetMapSelectability()を使用します。 ※インデックスの再構築が完了したら、必ず指定を元に戻してください。 (2021.1以前では、$SYSTEM.SQL.SetMapSelectability()を使用します。引数の指定方法は2022.1以降と同様です。) SetMapSelectability()メソッドは%Statusの戻り値が設定されています。ステータスOKの場合は1が戻ります。エラーステータスの場合は以下のメソッドを使用してエラー内容を確認してください。 write $SYSTEM.Status.GetErrorText(ステータスが入った変数) ご参考:%Statusのエラーが返ってきたら 以下、Training.Employeeに新インデックス:NewIndexを定義する例でご紹介します。 1) 定義予定の新インデックス名をクエリオプティマイザが使用しないように設定します。 set status=$SYSTEM.SQL.Util.SetMapSelectability("Training.Employee","NewIndex",0) 第1引数:クラス名 第2引数:インデックス名(これから指定する新インデックス名を指定します。) 第3引数:隠す場合は0、見せる場合は1 2) インデックスを追加します。 永続クラスの場合はインデックスを追加しコンパイルし、インデックス再構築を実行します。 SQL文で実行する場合はCREATE INDEXを実行した後インデックス再構築が自動的に開始されます。 3) クエリオプティマイザにインデックスを見せるように変更します。  ※インデックスの再構築が終了してから行います。 $system.SQL.Util.SetMapSelectability("Training.Employee","NewIndex",1) 4) クエリキャッシュを削除します。 方法詳細は、2024.1以降の手順でご紹介した図解をご参照ください。 関連項目として、インデックスの再構築を複数のプロセスで行う方法もあります。詳細は:「アプリケーション使用中にインデックス再構築を複数プロセスで実行する方法」をご覧ください。 《注意》CREATE INDEX文でインデックスを追加した場合、インデックス追加後すぐに再構築が開始されますが、インデックスをクラス定義文で追加した場合インデックス再構築は実行を命令するまで開始されません。 2024.1以降から利用できる方法を追加しました。
記事
Mihoko Iijima · 2023年3月13日

永続クラス定義のデータが格納されるグローバル変数名について

これは InterSystems FAQ サイトの記事です。 永続クラス定義では、データを格納するグローバル変数名を初回クラスコンパイル時に決定しています。グローバル変数名は、コンパイル後に表示されるストレージ定義(Storage)で確認できます。 例) Class Training.Person Extends %Persistent{Property Name As %String; Property Email As %String; Storage Default{<Data name="PersonDefaultData"><Value name="1"><Value>%%CLASSNAME</Value></Value><Value name="2"><Value>Name</Value></Value><Value name="3"><Value>Email</Value></Value></Data><DataLocation>^Training.PersonD</DataLocation><DefaultData>PersonDefaultData</DefaultData><ExtentSize>0</ExtentSize><IdLocation>^Training.PersonD</IdLocation><IndexLocation>^Training.PersonI</IndexLocation><StreamLocation>^Training.PersonS</StreamLocation><Type>%Storage.Persistent</Type>} } DataLocation:^パッケージ名.クラス名D永続クラスのデータが登録されるグローバル変数です。 IndexLocation:^パッケージ名.クラス名Iインデックスが格納されるグローバル変数です。 StreamLocation:^パッケージ名.クラス名Sストリームプロパティのデータが格納される変数です。 例外として、31文字以上のクラス名を指定した場合、グローバル変数名の文字数制限を超えてしまうため、ネームスペースで一意となる適当なグローバル変数名を使用します。 Class TestPackage.VeryLongLongLongLongName Extends %Persistent{Property Name As %String; Storage Default{<Data name="VeryLongLongLongLongNameDefaultData"><Value name="1"><Value>%%CLASSNAME</Value></Value><Value name="2"><Value>Name</Value></Value></Data><DataLocation>^TestPackage.VeryLongLon92A3D</DataLocation><DefaultData>VeryLongLongLongLongNameDefaultData</DefaultData><IdLocation>^TestPackage.VeryLongLon92A3D</IdLocation><IndexLocation>^TestPackage.VeryLongLon92A3I</IndexLocation><StreamLocation>^TestPackage.VeryLongLon92A3S</StreamLocation><Type>%Storage.Persistent</Type>}} ストレージ定義未作成の場合、DEFAULTGLOBALパラメータを利用してグローバル変数名を指定することができます。(指定したグローバル変数名の末尾にD、I、Sを付与した名称をストレージに定義します。) Class TestPackage.VeryLongLongLongLongName Extends %Persistent{Property Name As %String;Parameter DEFAULTGLOBAL = "^Test.LongName";Storage Default{<Data name="VeryLongLongLongLongNameDefaultData"><Value name="1"><Value>%%CLASSNAME</Value></Value><Value name="2"><Value>Name</Value></Value></Data><DataLocation>^Test.LongNameD</DataLocation><DefaultData>VeryLongLongLongLongNameDefaultData</DefaultData><IdLocation>^Test.LongNameD</IdLocation><IndexLocation>^Test.LongNameI</IndexLocation><StreamLocation>^Test.LongNameS</StreamLocation><Type>%Storage.Persistent</Type>}} 詳細は以下ドキュメントもご参照ください。 標準のグローバル名 ユーザ定義のグローバル名 この他、2017.2以降からストレージに定義されるグローバル変数名をハッシュ化したグローバル変数名に変更できるパラメータ:USEREXTENTSETが、パフォーマンス向上のために追加されました。 ストレージ未作成時(クラス定義の初回コンパイル前)に設定することで、 ^EPgS.D8T6.1 のようなハッシュ化したグローバル変数名が設定されます。 Parameter USEEXTENTSET = 1; USEEXTENTSETパラメータを使用する場合のストレージ定義について詳細は、関連トピックをご参照ください。 関連記事もご参照ください テーブル定義のデータが格納されるグローバル変数名について クラス名が31文字を超える永続クラスをIRIS管理ポータルからクラスエクスポートしました。 一部リネームしたくて、エクスポートしたファイルを直接編集し、クラス名にパッケージ名を追加しました。 編集後、管理ポータルからクラスのインポート(元データはIRIS管理ポータルからエクスポートしたexport.xml)を行うと、グローバル名の31文字制限エラーが出力されますが、インポート対象のネームスペースにはクラスが取り込まれているようでした。(クラスは新規追加を想定) ですが、ストレージ定義の部分で31文字制限にかかっているようで、一度VSCodeなどでインポートしたクラスを開いてストレージ定義をすべて消して保存(コンパイル)したらエラーが出なくなりました。 対応としてはこれで問題ないでしょうか。 @Yusuke.Kojima さん、こんにちは。 一度VSCodeなどでインポートしたクラスを開いてストレージ定義をすべて消して保存(コンパイル)したらエラーが出なくなりました。 ストレージ定義を削除しコンパイルしたため、現在のクラス定義に合わせて新しいストレージ定義ができていると思われます。 変更前の永続クラス定義に合わせて作成されたデータ(グローバル変数)を今後使用しない状況(新規クラスとして利用する状況)であれば問題ありませんが、クラス定義変更前のデータ(グローバル変数)を修正後の新しいクラス定義でも使用したいなどありますか? @Mihoko.Iijimaさん、ご返信ありがとうございます。 クラス定義変更前のデータ(グローバル変数)を修正後の新しいクラス定義でも使用したいなどありますか? 簡単に背景をご説明しますと、永続クラスの定義の基となる標準規格のバージョンがあがり、一部の永続クラスの構造が変化してしまいました。 そのため、新バージョンクラスと旧バージョンクラスを別々に定義し(旧バージョンは名称変更はせず、現状のまま残しておきます)、導入時にはデータ移行プログラム(一度だけ実行するDTLを作成しようと思っています)で旧クラスから新クラスにデータコピーを検討しています。 旧バージョンから新バージョンへの切り替えも、ユーザーの任意のタイミングで実行できるよう、柔軟性を持たせた作りにする予定ですが、その部分はアイデアがあるためユーザーと相談中です。 新旧クラス定義を別々に定義しておいて、新クラスはストレージ定義を作り直しても大丈夫な環境なのですね。背景のご説明ありがとうございました!
記事
Toshihiko Minamoto · 2024年12月4日

d[IA]gnosis_ Embedded Python と LLM モデルを使って診断をベクトル化する

前回の記事では、ICD-10 による診断のコーディングをサポートできるように開発された d[IA]gnosis アプリケーションを紹介しました。 この記事では、InterSystems IRIS for Health が、事前トレーニングされた言語モデル、そのストレージ、およびその後の生成されたすべてのベクトルの類似性の検索を通じて ICD-10 コードのリストからベクトルを生成するために必要なツールをどのように提供するかを見ていきます。 はじめに AI モデルの開発に伴って登場した主な機能の 1 つは、RAG(検索拡張生成)という、コンテキストをモデルに組み込むことで LLM モデルの結果を向上させることができる機能です。 この例では、コンテキストは ICD-10 診断のセットによって提供されており、これらを使用するには、まずこれらをベクトル化する必要があります。 診断リストをベクトル化するにはどうすればよいでしょうか? SentenceTransformers と Embedded Python ベクトルを生成するために、トレーニング済みのモデルからの自由テキストのベクトル化を大幅に容易にする SentenceTransformers という Python ライブラリを使用しました。 そのウェブサイトでは以下のように説明されています。 Sentence Transformers(別名: SBERT)は、最先端のテキスト画像埋め込みモデルへのアクセス、使用、およびトレーニングに使用される一般的な Python モジュールです。 SentenceTransformer モデルを使って埋め込みを計算(クイックスタート)するか、Cross-Encoder モデルを使って類似性スコアを計算(クイックスタート)するために使用できます。 これにより、セマンティック検索、セマンティックテキスト類似性、パラフレーズマイニングなどの広範なアプリケーションが可能になります。 SentenceTransformers コミュニティが開発した全モデルの中で、786 の次元ベクトルを生成する BioLORD-2023-M というトレーニング済みモデルを見つけました。 このモデルは、臨床文章や生物医学的概念の意味のある表現を生成するための新しい事前トレーニング戦略である BioLORD を使用してトレーニングされました。 最先端の方法は、同じ概念を指す名前の表現の類似性を最大化し、対照学習を通じて崩壊を防ぐことによって機能します。 ただし、生物医学的名前は必ずしも自明ではないため、非意味的な表現になることがあります。 BioLORD は、定義を使用した概念表現と、生物医学オントロジーで構成されるマルチリレーショナルナレッジグラフから得られる短い説明を基礎にすることで、この問題を克服しています。 この基礎により、このモデルは、オントロジーの階層構造により密接に一致する、より意味論的な概念表現を生成します。 BioLORD-2023 は、臨床文章(MedSTS)と生物医学概念(EHR-Rel-B)の両方でテキスト類似性の新たな最先端を確立しています。 この定義でわかるように、このモデルは、ICD-10 コードと自由テキストの両方をベクトル化するときに役立つ医療概念で事前トレーニングされています。 このプロジェクトでは、このモデルをダウンロードして、ベクトルの作成を高速化します。 if not os.path.isdir('/shared/model/'): model = sentence_transformers.SentenceTransformer('FremyCompany/BioLORD-2023-M') model.save('/shared/model/') ダウンロードしたら、ベクトル化するテキストをリストに入力して、プロセスを高速化します。以前に ENCODER.Object.Codes クラスに記録した ICD-10 コードをベクトル化する方法を見てみましょう。 st = iris.sql.prepare("SELECT TOP 50 CodeId, Description FROM ENCODER_Object.Codes WHERE VectorDescription is null ORDER BY ID ASC ") resultSet = st.execute() df = resultSet.dataframe() if (df.size > 0): model = sentence_transformers.SentenceTransformer("/shared/model/") embeddings = model.encode(df['description'].tolist(), normalize_embeddings=True) df['vectordescription'] = embeddings.tolist() stmt = iris.sql.prepare("UPDATE ENCODER_Object.Codes SET VectorDescription = TO_VECTOR(?,DECIMAL) WHERE CodeId = ?") for index, row in df.iterrows(): rs = stmt.execute(str(row['vectordescription']), row['codeid']) else: flagLoop = False ご覧のとおり、CSV ファイルから抽出した後に前のステップで登録した、まだベクトル化されていないコードを先に抽出し、次に、ベクトル化する記述のリストを抽出します。Python の sentence_transformers ライブラリを使用してモデルを復元し、関連する埋め込みを生成します。 最後に、UPDATE を実行して、ベクトル化された記述で ICD-10 コードを更新します。 ご覧のように、モデルが返した結果をベクトル化するコマンドは、IRIS における SQL コマンドの TO_VECTOR です。 IRIS で使用する Python コードの準備ができたので、Ens.BusinessProcess を拡張するクラスにラップして、プロダクションに含めましょう。次に、CSV ファイルを取得するビジネスサービスに接続すれば、完成です! プロダクションでこのコードがどのように見えるかを確認しましょう。 ご覧のように、EnsLib.File.InboundAdapter アダプターを備えたビジネス サービスにより、コードファイルを収集し、それをすべてのベクトル化とストレージ操作を実行するビジネスプロセスにリダイレクトできます。すると、以下のようなレコードセットが得られます。 これで、アプリケーションは、送信するテキストに一致する可能性のある項目を検索できるようになりました! 次回の記事では... 次回の記事では、Angular 17 で開発されたアプリケーションのフロントエンドが IRIS for Health のプロダクションとどのように統合されるか、および IRIS が分析するテキストをどのように受け取り、それらをベクトル化し、ICD-10 コードテーブルで類似性を検索するかを説明します。 お見逃しなく!
記事
Toshihiko Minamoto · 2022年2月24日

2021.2 SQL 機能スポットライト - ランタイムプランの選択

InterSystems IRIS Data Platform の 2021.2 リリースには、ミッションクリティカルなアプリケーションを高速で柔軟性に優れ、セキュアに開発するための刺激的な新機能が多数含まれています。 Embedded Python は間違いなく脚光を浴びています(正当な理由で!)が、SQL の分野でも、テーブルデータに関する詳細な統計情報を収集し、それを最適なクエリプランに提供する、より適応性の高いエンジンに向けて大きな一歩を踏み出しました。 この短い連載記事では、2021.2 で新しく追加された 3 つの要素について詳しく説明し、ランタイムプランの選択(RTPC)を手始めに、この目標に向かって進みます。 これらについて適切な順序で話していくのは困難です(この記事を書く上で、私がどれだけ順序を入れ替えたか想像できないほどです!) というのも、これらが相互に非常にうまく機能するためです。 そのため、ご自由に順序を変えてお読みください。 IRIS クエリ処理について IRIS SQL エンジンにステートメントを送信すると、エンジンはリテラル(クエリパラメーター)を置き換えてそのステートメントを正規化された形式に解析し、テーブル構造、インデックス、およびフィールド値に関する統計を見て、正規化されたクエリにとって最も効率的な実行ストラテジーをはじき出します。 これにより、おそらく異なるクエリパラメーター値を使用してクエリをもう一度実行したい場合に、エンジンは同じプランと生成されたコードを再利用することができます。 たとえば、以下のクエリを例にします。 SELECT * FROM Customer WHERE CountryCode = 'US' AND ChannelCode = 'DIRECT' このクエリは以下のような形式に正規化されます。 SELECT * FROM Customer WHERE CountryCode = ? AND ChannelCode = ? そのため、国とチャンネルの様々な組み合わせに対する後続の呼び出しは、即座にキャッシュされた同じクエリクラスを取得するため、計算量の多いプランニング作業を省略することができます。 6 つのチャンネルを通じて世界中にランニングシューズを販売すると仮定しましょう。言い換えると、データは CountryCode とChannelCode の可能な値全体で均等に分散されます。 これらの両方のフィールドに通常のインデックスがある場合、この正規化されたクエリで最も効率的なプランは、対応するインデックスを使用してマスターマップから一致する行にアクセスすることで最も選択的な条件(CountryCode)から始め、その後にマスターマップのすべての行に対し、別の条件(ChannelCode)をチェックします。  ### 外れ値 では、スポーツ用品メーカーではなく、ベルギーチョコレートの型を販売する専門ベンダーだとしましょう(どこから出てきた例でしょうかね )。 この場合、顧客の大半(たとえば 60%)がベルギーにいるとします。つまり、「BE」が CountryCode フィールドの_外れ値_であり、テーブルの多数の行を表します。 すると突然、CountryCode でのインデックスに他の使用方法が現れました。ランタイムで取得する CountryCode のクエリパラメーターが「BE」で_あるとした場合_、そのインデックスを最初に使用すると、マスターマップの大半を読み取ることになるため、ChannelCode でのインデックスから始める方がよくなります。 しかし、CountryCode のクエリパラメーター値が別の値で_あるとした場合_、他のすべての国が残りの 40% のベルギー以外の顧客を分割するため、CountryCode でのインデックスの方がはるかに高い価値が出てきます。 これは、ランタイム時に異なるプランを選択する場合の例です。または、言い換えると、**ランタイムプランの選択**(RTPC)と言えます。 RTPC は、従来のリテラル置換とキャッシュ済みのクエリルックアップロジックに小さなフックを追加して、CounryCode 列の「BE」値などの外れ値を見つけるメカニズムです。 IRIS SQL クエリ処理のさらに詳細な概要に興味のある方は、[こちらの VS2020 動画](https://learning.intersystems.com/course/view.php?id=1598)をご覧ください。 IRIS SQL は過去に、非常に大雑把なオプトインバージョンをサポートしていましたが、2021.2 で、大幅に軽量化され、より広範な述語条件に対応できる、まったく新しい RTPC インフラストラクチャを導入しました。 このランタイムチェックのオーバーヘッドは確かに最小限であるため、ユーザーが何も行わなくてもこのメリットを得られるように、これをデフォルトでオンにしました(いつものように、[アップグレード後にクエリプランを解凍](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCRN_upgrade_intro#GCRN_upgrade_intro_frozen)する必要はありません)。 これまで頻繁に、実世界のデータセットでは外れ値がどれほど一般的であるか(また、厳格に一様な分布がどれほど稀であるか)を見てきましし、パートナーのベンチマークでのテストによって、以下のグラフからわかるように、パフォーマンスと I/O で目を見開くほどの改善が得られることがわかりました。 また、2020.1 のベンチマーク結果も含めているため、リリースのたびにパフォーマンスを改善できるように継続的に取り組んでいること(と結果)を見ていただけるでしょう。 改善のマイレージは、データセットの外れ値の量とインデックスの可用性によって異なりますが、この変更の可能性には非常に興奮しており、皆さんの体験をお聞かせいただければ幸いです。
記事
Junichi Sakata · 2023年11月6日

Embedded Pythonの使い所は?

Python流行ってますよね。(一時は圧倒的な支配力のあったJavaも、O社に買われてライセンスが云々とか言われ始めた頃からブレーキが掛かってしまった気がします。) Pythonの魅力の一つがパッケージで様々な機能が提供されていることがあげられるかなと思っています。 私もこれまでPythonのコードをそこそこ書いてきました。実のところ、ここ1年では間違いなくObject ScriptよりPythonのほうが書いた量が多いです。Excelのドキュメントがそれらよりも遥かに多いのは何とかしたいところですが😅 IRISと連携するため$ZF(-1)を使ってPythonプログラムをコールしているものもあります。 IRIS 2021.2からPythonがIRISにEmbedded Pythonとして組み込まれたということで、どのように使えるかを試してみました。 なお、使用した環境は以下です。 OS: Linux Alma8 5.14.0-162.22.2.el9_1.x86_64 #1 SMP PREEMPT_DYNAMIC Mon Mar 27 07:34:40 EDT 2023 x86_64 x86_64 x86_64 GNU/Linux IRIS: IRIS for UNIX (Red Hat Enterprise Linux 9 for x86-64) 2022.1.2 (Build 574U) Fri Jan 13 2023 15:13:22 EST ■ まずはEmbedded Pythonを動かしてみる パッケージのインストール IRISの Embedded Pythonが使用するパッケージは、IRISインストールディレクトリの下の /mgt/python にインストールする必要があるようで、pipからは--targetで場所を指定してインストールします。 AWS SDK (boto3)をインストールしてみます。 $ pip3 install --target /irissys/mgr/python/ boto3 Collecting boto3 Downloading boto3-1.28.67-py3-none-any.whl (135 kB) |################################| 135 kB 3.4 MB/s Collecting botocore<1.32.0,>=1.31.67 Downloading botocore-1.31.67-py3-none-any.whl (11.3 MB) |################################| 11.3 MB 11.0 MB/s Collecting s3transfer<0.8.0,>=0.7.0 Downloading s3transfer-0.7.0-py3-none-any.whl (79 kB) |################################| 79 kB 8.1 MB/s (中略) Successfully installed boto3-1.28.67 botocore-1.31.67 jmespath-1.0.1 python-dateutil-2.8.2 s3transfer-0.7.0 six-1.16.0 urllib3-1.26.18 Embedded Pythonコードで書いてみる インストールしたパッケージboto3を使って、AWS SNSでメールを送信してみます。 AWSアカウントや、AWS SNSのARNはすでにあるものとして省略します。 なお、どのメールアドレスに送信するかもAWS SNS側で設定しています。 /// Embedded Python で Mail 出す ClassMethod EmbPyMail(Subject As %String, Body As %String) [ Language = python ] { import boto3 # 以下の値はダミーです accesskey = "AKIAABCDEFGHIJKLMNOP" secretkey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" topic_arn = "arn:aws:sns:ap-northeast-1:012345678901:SnsTopicName" sns = boto3.resource("sns", aws_access_key_id=accesskey, aws_secret_access_key=secretkey, region_name="ap-northeast-1") response = sns.Topic(topic_arn).publish( Message=Body, Subject=Subject ) } このコードを動かしてみます。 メールが無事送信されました。 Pythonコードのままirisクラスの中に埋め込むことが出来ました。 ■ Embedded Pythonを使うメリットとは? さて、IRISクラス定義の中でPythonコードを書けることは確認できましたが、これまで使ってきた$ZF(-1,"python xxx.py parameter")でPythonコード呼び出すのとどういう違いがあるのか?と言う疑問が湧いてきました。 同一プロセスで実行 この動画の1:02秒あたりを見ると、Embedded PythonはIRISと同一プロセスで動作するように見えます。 ということで、プロセスIDを表示するコードで確認してみます。 /// PIDを表示するだけ ClassMethod WritePID() [ Language = python ] { import os print(os.getpid()) } 動かしてみると、確かに同じプロセスで動いているようです。 これと同様の処理を$ZF(-1)で実行すると別プロセスになります。 # /tmp/pid.py # PIDを表示するだけ import os print(os.getpid()) ということはEmbedded Pythonは、$ZF(-1)に比べてプロセス立ち上げる処理がなくなる分だけ高速に起動することが出来そうです。試してみましょう。 ClassMethod CallSpeed() { S times=1000 W "$ZF(-1) Call: " S st=$ZH F i=1:1:times D $ZF(-1,"python /tmp/exit.py") W $ZH-st,"sec",! W "Embedded Python Call: " S st=$ZH F i=1:1:times D ..PyReturn() W $ZH-st,"sec",! } ClassMethod PyExit() [ Language = python ] { return } # /tmp/exit.py # 抜けるだけ exit Embedded Python のほうが400倍ぐらい早いですね。 とはいえ、1回の$ZF(-1)では0.02sec位ですから何度も呼び出すものでなければ$ZF(-1)でも良いと思います。逆に何度も繰り返し呼ぶような処理であれば、Embedded Pythonを使うメリットがありそうです。 引数・戻り値の受け渡し インプロセスで呼ばれることのメリットとしては、引数・戻り値を柔軟に受け渡せるということが考えられます。 $ZF(-1)では引数はコマンドの最大長に制限されます。Windows: 8kB, Linux: 大体2MB程度(ARG_MAX参照)であまり困ることはないですが、一時ファイルとかに書き出してファイルパスを渡す方法もよくやりますね。$ZF(-1)での戻り値はPythonコード中でexit(n)で設定するリターンコードnを得る方法がありますが数値しか渡せないため、それ以外の場合は結果ファイルを作ってIRISでそれを読むことでしのいでいます。 それがEmbedded Pythonでは自由に引数、戻り値(タプルでの複数値の返却も)を受け渡すことが出来ます。 ClassMethod ParamRev() { Set rev=..pyParamRev($H) Write rev.GetAt(1),!,rev.GetAt(2) // GetAt(n)でタプル各要素にアクセス。要素数はCount()で } ClassMethod pyParamRev(horo) [ Language = python ] { print(horo) n = 100 s = "aaa" return n, s # 複数の値をタプルで返す } その他の使い所は? PythonコードからIRISデータ・グローバルにアクセスすることはありそうです。が、SQL経由やクラスメソッドで用意された機能を利用するならともかく、グローバルアクセスするならデータ構造の理解とかも必要になりますしObject Script使ったほうが良さそうな気がします。 あとは、IRISクラス定義の中に全ての処理をまとめておきたい場合でしょうか? 私はPythonコードはPythonコードで固めておきたい派なのですが、IRISのクラスと近いところに配置するメリットもありそうに思います。 ■ まとめ Embedded Python使うのはどんな時?は、以下のいくつかに該当する時なのかなと個人的に思います。 有用なPythonパッケージを使いたい 繰り返しPython処理を呼び出す 引数・戻り値が多い IRISクラス定義で全てのコードをまとめて書きたい 他に、こんなシチュエーションで使うと便利というものがありましたら、教えていただけると嬉しいです。 コードはこちらに配置しています。 #第1回 InterSystems Japan 技術文書ライティングコンテスト 最終日に滑り込み。
ディスカッション
Seiji Hirose · 2021年2月22日

IRISで困ること(是非、ご教授下さい)

今思えば、製品名に「M」が付いている時代は説明が楽でした。私が記憶している製品は、DTM(Data Tree Mumps:MS-DOS上で巨大なシステムを構築できていましたね)、DSM(Dec Standard Mumps)、MSM(マイクロネティック Standard Mumps あってますか?)、ISM(Intersystems Standard Mumps)、U-MUMPS(?)その他もあったかも知れませんが、M(Mumps)の実装環境です、という説明で何とかなっていました。すると、「へぇ~、MUMPSってまだあったんだね」という答えを頂くこともありました。これらの製品が統合されて「OpenM」となりましたが、まだ「M」の文字が入っており、MUMPSもSQL対応できるようになりました、などと説明していました。 ところが、製品名がCacheになり、趣が大きく変わり、製品の説明が難しくなりました。「データベースです。」→「RDBなの?」→「RDBとしても動作しますがKey-Value的で多次元データ管理もできます」→「OLAP用なの?」→「OLAP処理もできますが、基本はトランザクション処理用です」→「で、結局、何なの」という禅問答のような状況にはまって行きました。 そして、IRISです。Cache+α+β+θ...で機能満載ですね。端的な表現で「IRISはこういうものです」というフレーズをお持ちでしたら是非ご教授頂けないでしょうか。今後、個人的に、色々なところでIRISについて説明する機会が増えると考えていますが、つかみの部分が難しいと感じていますので...なお、データプラット・フォームです、は除外して頂けると有り難いです。返って長い説明がはじまってしまいそうなので。 宜しくお願い致します。 データプラットフォームと同じかもしれませんが、個人的にはデータベースと言語が一体となったシステムだと思います。(MUMPSやCachéも同じですが。。。) データベースと言語が一体となっているので、言語からデータベースの内容を簡単かつ安全、高速に参照、更新できるところが利点だと思います。また、以前はデータベース=RDBとなっていたので、拒絶反応を示されることが多かったのですが、最近ではドキュメントDBやグラフDBなどリレーショナルでないものもいろいろと出てきているので、少しご理解いただけるようになってきているのでは。と思います。 Minamoto様、コメント頂き有難う御座いました。 個人的には、「何とかツール」的なキラーフレーズがあると良いなぁ、と思っています。多少、厳密性を欠くことがあっても、一言でイメージが伝わるようなキャッチコピーがあると、今よりももっと受け入れられるような気がします。 例えば、無線LAN通信を「wi-fi」と言ってのけるように。ちなみに、wi-fi って、昔のステレオのHi-Fiをまねして、wi-fi になったそうですね。Bluetoothも、かつてのヨーロッパの王様の愛称だったとか... 上記のようなネーミングやキャッチコピーは貴社のポリシーには合致しないかも知れませんが、マーケティング的なキャッチコピーがあると良いなぁ、と思っています。 Hiroseさん先週のバーチャルサミット聴講していただいたと思いますが、ウルシステムズの漆原社長の講演は聞かれましたか?そこでエッジの効いたデータベースと紹介いただいています。見てない場合は是非ご覧になってください。 Sato様。 ウルシステムズの漆原様の講演は拝聴いたしました。 Satoさんのコメントにありますが「エッジの効いたデータベース」のイメージが掴みづらいと個人的に感じています。また、漆原様の講演の中で、「通常の処理であればRDBMSで十分だと思われる」といった旨のお話もあったように記憶しています。(間違っていたらご指摘ください。) 個人的には、IRISで所謂RDBMS的な処理は十分にこなせるのではないかと感じています。漆原様のお話にありましたが、RDBMSよりもIRISの方がデータ更新系の処理が数倍高速である、というのは私も実感しています。(最近、MariaDB(MySQL)でInsertテストを実施して実感しました。) 「エッジの効いたデータベース」が、RDBMSでは構築できない仕組みを構築できるデータベース、だとすると、IRISはRDBMSを補完するデータベースとして認識されてしまう可能性も有るように感じました。 何かもっとしっくり来る表現の仕方があるように思われるのですが、残念ながら、私の頭では思い付かない状況です。 hiroseさん その通りです。大は小を兼ねますからね IRISなら軽いものから重たいものまで何でもこなせます。用途に合わせてデータベースを分けないとなると、同じデータを物理的に別々に管理しなければならなくなりますよね。ITの課題の1つがデータのサイロ化です。 データがいろいろなところに散在していると、それを取りまとめるための手間が増えてしまいます。1つのデータベースが色々な用途に対応できるとその手間が少なくできるのではないかと思います。 そんな方向性をIRISは目指しています。キーワードとしてはマルチモデルとマルチワークロードです。 Sato様。 すでに2016年当時に上記のお考えをお持ちだったのですね。下記の記事を拝読致しました。 https://blogs.itmedia.co.jp/satohiroshi/2016/09/post_3859.html 色々なご意見を伺う中で、私のIRISに対するイメージは下記のようなものになってきました。 Multi Purpose Utility System (or, Environment )→上手く日本語になれば良いのですが。 このようなイメージはある程度当たっているでしょうか? 今後ともどうぞ宜しくお願い致します。 Hiroseさん返答遅れて申し訳ないです。Multi Purpose Utility System これは何だかMUMPSを思い出させるネーミングなので、申し訳ないですが却下させてください。でもイメージは間違っていないと思います。キャッチーなネーミングは、コンシューマを意識する場合は不可欠なのですが、IRISはどこまでいっても消費財ではなく生産財なので、すべての人に理解してもらうのはやはり難しいところです
記事
Shintaro Kaminaka · 2020年8月12日

RESTForms - 永続クラスにREST APIをアドオンする パート2: クエリ

最初の記事では、RESTForms(永続クラス用のREST API)について説明をしました。 基本的な機能についてはすでに説明しましたが、ここではクエリ機能を中心とする高度な機能について説明します。 * 基本クエリ * クエリ引数 * カスタムクエリ ### クエリ クエリを使用すると、任意の条件に基づいてデータの一部を取得できます。 RESTFormsには、2種類のクエリがあります。 * 基本クエリは一度定義すればすべてのRESTFormsクラスに対して機能します。異なっているのはフィールドリストのみです。 * カスタムクエリはそれが指定され、使用できるクラスに対してのみ機能しますが、開発者はクエリの本文に完全にアクセスできます。 ### 基本クエリ 一度定義すると、すべてのクラスか一部のクラスですぐに使用できます。 基本クエリはシステムによって定義されているものもありますが、開発者が追加することもできます。これらのクエリはすべて、SELECTのフィールドリストのみを定義します。 その他すべて(絞り込み、ページネーションなど)はRESTFormsによって行われます。 form/objects/:class/:query を呼び出すと、単純なクエリを実行できます。 2番目の :query パラメーターはクエリ名(クエリの SELECT と FROM の間の内容)を定義します。 デフォルトのクエリタイプは次のとおりです。 |クエリ|説明| |------------|-------------------------| |all|すべての情報| |info|displayName と id| |infoclass|displayName、id、class| |count|行数| 例えば、Form.Test.Personオブジェクトに関する基本的な情報を取得するには、infoclassクエリを実行できます。 form/objects/Form.Test.Person/infoclass {"children": [ {"_id":"1", "displayName":"Alice", "_class":"Form.Test.Person"}, {"_id":"2", "displayName":"Charlie", "_class":"Form.Test.Person"}, {"_id":"3", "displayName":"William", "_class":"Form.Test.Person"} ]} RESTFormsは次の場所で myq という名前のクエリを探します(最初にヒットするまで)。 1. フォームクラス内のqueryMYQクラスメソッド 2. クエリクラス内のMYQパラメーター 3. クエリクラス内のqueryMYQクラスメソッド 4. Form.REST.Objectsクラス内のMYQパラメーター 5. Form.REST.Objectsクラス内のqueryMYQクラスメソッド 独自のクエリクラスを定義できます(上記リストの項目2、3用)。このクエリクラスは、すべてのクラスで使用可能なクエリの定義を保持する特別なクラスです。 そのクラスで myq という名前の独自クエリを定義する手順は以下のとおりです。 1. (1回だけ)YourClassName クラスを定義します。 2. そのクラスで MYQ パラメーターか queryMYQ クラスメソッドを定義します。 パラメーターはメソッドよりも優先されます。 3. メソッドまたはパラメーターは、SQLクエリのSELECTとFROMの間の部分を返す必要があります。 4. (1回だけ)ターミナルで以下を実行します。 Do ##class(For.Settings).setSetting("queryclass", YourClassName) メソッドの署名は以下のとおりです。 ClassMethod queryMYQ(class As %String) As %String クラス固有のクエリを定義することもできます。 myq という名前の独自クラスクエリを定義する手順は以下のとおりです。 1. フォームクラスで queryMYQ クラスメソッドを定義します。 2. メソッドの署名は以下のとおりです。ClassMethod queryMYQ() As %String 3. メソッドは、SQLクエリのSELECTとFROMの間の部分を返す必要があります。 URL 引数 フィルターやその他のパラメーターをURLで指定できます。 すべての引数は省略可能です。 | 引数 | サンプル値 | 説明 | | ------------ | ------------------- | ----------------------------------------- | | size | 2 | ページサイズ | | page | 1 | ページ番号 | | filter | 値+contains+W | WHERE句 | | orderby | 値+desc | ORDER BY句 | | collation | UPPER | COLLATION句 | | nocount | 1 | レコード数を削除(クエリを高速化します)| これらの引数に関するいくつかの情報を次に示します。 ### ORDER BY句 結果の順序を変更します。 値は、カラム名またはカラム名+desc です。 カラム名は、SQLテーブルのカラム名またはカラム番号です。 ### WHERE句 絞り込み条件の書式は、カラム名+条件+値です。 カラム名+条件+値+カラム名2+条件2+値2のように、複数の条件を指定できます。 矢印構文とシリアルオブジェクトもサポートされています: Column_ColumnField+条件+値 Valueに空白が含まれている場合、サーバーに送信する前にタブに置き換えます。 | URL | SQL | | ---------------------- | -------------- | | neq | != | | eq | = | | gte | >= | | gt | > | | lte | <= | | lt | < | | startswith | %STARTSWITH | | contains | [ | | doesnotcontain | '[ | | in | IN | | like | LIKE | リクエストの例: form/objects/Form.Test.Simple/info?size=2&page=1&orderby=text form/objects/Form.Test.Simple/all?orderby=text+desc form/objects/Form.Test.Simple/all?filter=text+eq+Hello form/objects/Form.Test.Person/infoclass?filter=company_name+contains+a form/objects/Form.Test.Simple/all?filter=text+in+A9044~B5920 SQLアクセスには、ユーザーに適切なSQL権限(フォームテーブルに対するSELECT)を付与する必要があることに注意してください。 ### COLLATION句 書式は、collation=UPPER または collation=EXACT です。 指定した照合順序をWHERE句で強制します。 省略した場合、デフォルトの照合順序が使用されます。 ### ページネーション ページネーションは、デフォルトで1ページあたり25レコードになります。 ページサイズと現在のページを変更するには、size 引数と page 引数を(1を基準として)指定します。 ### カスタムクエリ form/objects/:class/custom/:query を呼び出すと、カスタムクエリを実行できます。 カスタムクエリを使用すると、開発者はクエリの内容全体を決めることができます。 size および page 以外のURLパラメーターは指定できません。 メソッドは他のすべてのURLパラメーターを解析する必要があります(またはForm.JSON.SQLからデフォルトのパーサーを呼び出す必要があります)。 myq という名前のカスタムクエリを定義する手順は以下のとおりです。 1. フォームクラスで customqueryMYQ クラスメソッドを定義します。 2. メソッドの署名は以下のとおりです。ClassMethod customqueryMYQ() As %String 3. メソッドは有効なSQLクエリを返す必要があります。 ### デモ [現在デモ環境はお試しいただくことができません。] こちらでRESTFormsをオンラインで試すことができます(ユーザー名:Demo、パスワード:Demo)。 また、RESTFormsUIアプリケーション(RESTFormsデータエディタ)もあります。こちらをご確認ください(ユーザー名:Demo、パスワード:Demo)。 クラスリストのスクリーンショットを以下に掲載しています。 ### まとめ RESTFormsは、幅広くカスタマイズ可能なクエリ機能を提供します。 ### 次の内容 次の記事では、いくつかの高度な機能について説明します。 * メタデータの解釈 * セキュリティと権限 * オブジェクト名 ### リンク * [RESTForms GitHubリポジトリ](https://github.com/intersystems-ru/RESTForms/) RESTForms UI GitHubリポジトリ
記事
Toshihiko Minamoto · 2022年3月1日

2021.2 SQL 機能スポットライト - スマートサンプリング & テーブル統計の自動化

これは、適応性とパフォーマンスに優れた SQL エクスペリエンスを提供する 2021.2 SQL 強化機能に関する連載第 2 回目の記事です。 この記事では、前の記事で説明したランタイムプランの選択機能の主要な入力であるテーブル統計の収集におけるイノベーションに焦点を当てます。 皆さんには次のことを何度もお伝えしてきました: 『Tune tableを実行しましょう!』 [`TUNE TABLE` SQL コマンド](https://docs.intersystems.com/iris20211/csp/docbookj/DocBook.UI.Page.cls?KEY=RSQL_tunetable)または[`$SYSTEM.SQL.Stats.Table` ObjectScript API](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?&LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL.Stats.Table#GatherTableStats) を通じてテーブルをチューニングすることは、IRIS SQL が適切なクエリプランをはじき出すのに役立つテーブルデータの統計情報を収集することです。 これらの統計情報には、テーブル内のおおよその行数など、オプティマイザーが JOIN の順序(通常、最も小さいテーブルから始めるのが最も効率的です)などを決定する上で役立つ重要な情報が含まれています。 クエリのパフォーマンスについて InterSystems サポートに寄せられる多くの問い合わせは、`TUNE TABLE` を実行してもう一度試すだけで解消されます。このコマンドを実行することで、既存のプランが無効になり、次の呼び出しによって新しい統計が得られるためです。 サポートへの問い合わせから、こういったユーザーがテーブルの統計情報を収集していなかった理由が 2 つわかりました。テーブル統計について知らなかった、または本番システムでチューニングを実行した際のオーバーヘッドの余裕がなかったという理由です。 2021.2 では、この 2 つの理由に対処しました。 ### ブロックレベルのサンプリング 2 つ目の理由から始めましょう。統計を収集するコストです。 テーブル統計を収集するには多大な I/O が必要となるため、_テーブル全体をスキャンしているのであれば_、オーバーヘッドも高まります。 API ではすでに、行の一部のみサンプリングすることをサポートしていましたが、この操作にはかなりのコストがかかるというご意見をいただいていました。 2021.2 では、マスターマップグローバルをループすることでランダムな行を選択するのではなく、その下の物理ストレージにすぐにアクセスしてそのグローバルにカーネルが実際のデータベースブロックのランダムなサンプルを取得するように変更しました。 サンプリングされたこれらのブロックから、それらが保存する SQL テーブル行を推論し、通常のフィールド単位の統計情報構築ロジックに対応します。 これを大規模なビールフェスティバルへの参加に例えると、すべての通路を歩き、いくつかの醸造所のブースを選んでそれぞれのボトルをカートに入れるのではなく、単に主催者に依頼してランダムなボトルが入った木箱を渡してもらうので歩かなくても済む、というものです。 (実際のビール試飲会では、歩き回ったほうが適切ですが![wink](https://community.intersystems.com/sites/all/libraries/ckeditor/plugins/smiley/images/wink_smile.png "wink"))。 酔いを醒ますために、以下に、ブロックベースのアプローチ(青い十字)に対する今までの行ベースのアプローチ(赤い十字)をプロットした単純なグラフを示しています。 これは、一部のお客様が `TUNE TABLE` を実行することを警戒している巨大なテーブルについては大きなメリットがあることを表しています。   ブロックサンプリングの制限はあまりありませんが、最も重要なのは、デフォルトのストレージマッピングでないテーブル(例: `%Storage.SQL` を使用してグローバル構造をカスタムマッピングしているテーブル)では使用できないことです。 このような場合には、過去に機能していた方法である、行ベースのサンプリングに戻ります。 ### 自動チューニング オーバーヘッドに関する認識の問題が片付いたところで、お客様が `TUNE TABLE` を実行していなかったもう 1 つの理由について考えましょう。その存在を知らなかったという理由です。 それについて文書化することもできました(また、ドキュメントを改善する余地が常にあることは認識しています)が、この非常に効率的なブロックサンプリングは、私たちが長年求めてきたことを実行する機会であると捉えました。すべてを自動化するということです。 2021.2 からは、統計がまったく提供されていないテーブルに対してクエリを準備する場合、最初に上記のブロックサンプリングメカニズムを使用してそれらの統計を収集し、クエリプランニングに統計を使用し、以降のクエリで使用できるように、テーブルメタデータに保存します。 仰々しく聞こえるかもしれませんが、上記のグラフは、GB サイズのテーブルでは、この統計収集の作業がわずか数秒で開始していることがわかります。 不適切なクエリプランを使用してそのようなテーブルをクエリしている場合(適切な統計がないため)は、事前に簡易サンプリングを実行するよりもはるかにコストがかかる可能性があります。 もちろん、これは、ブロックサンプリングを使用できるテーブルのみに行い、行ベースのサンプリングのみをサポートする特殊なテーブルの場合は、(残念ながら)統計なしで対処するしかありません。 他の新機能と同様に、皆さんの最初の体験に関する感想とフィードバックをお送りください。 この分野では、テーブルの使用状況に基づいて統計を最新の状態に維持するなど、自動化に関するアイデアは他にもありますが、そういった機能をラボ外部の使用体験に基づくものにしたいと考えています。
記事
Toshihiko Minamoto · 2023年5月2日

スタジオとGitHubとの連携環境の構築

開発者のみなさん、こんにちは。 vscode上で動作するObjectScriptエクステンションがリリースされ、vscodeを開発環境として使用できるようになり、GitHubリポジトリと連携できるようになりました。その一方で、使い慣れたIRISやCacheのスタジオからGitHubを扱いたいという要望は根強くあり、GitHubと連携するツールがOpen Exchange上にいくつか公開されています。 そこで、Open exchangeに収録されているツールの中で新しい「git for shared development environment」を使い、環境を作成してみましたので、その手順をお伝えします。 ご利用される際のご参考になれば幸いです。 Git for windows のインストール Git for windows のサイト よりキットをダウンロードし、そのexeファイル (git-2.xx.xx xx-bit.exe) を起動します。ライセンスの確認画面が表示されますので、「Install」ボタンをクリックします。 SSHキーペアの作成 コマンドプロンプトを起動し、ssh-keygen コマンドを実行します。 C:\Users\username> ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (C:\Users\username/.ssh/id_rsa): <-- Enter Enter passphrase (empty for no passphrase): <-- Enter Enter same passphrase again: <-- Enter Your identification has been saved in C:\Users\username/.ssh/id_rsa. Your public key has been saved in C:\Users\username/.ssh/id_rsa.pub. : C:\Users\username> 注意:このツールではSSHキーペアを使ってGitHubにアクセスすることを前提にしています。httpだとうまく動作しないことがありますので、ご注意ください。 GitHub への公開鍵の登録 WebブラウザにてGitHubのSSH公開鍵登録画面(https://github.com/settings/keys)にアクセス(GitHubアカウントへのログインが必要です)し、「New SSH key」をクリックします。Add new画面にて、Titleに公開鍵の名称(何でも構いません)を入力し、Keyに先ほど作成したid_rsa.pubファイルの中身を貼り付け、「Add SSH key」ボタンをクリックします。 InterSystems Package Manager (IPM:旧ZPM)のインストール 以下のURLよりIPMのキット(zpm-0.x.x.xml)をダウンロードします。https://pm.community.intersystems.com/packages/zpm/latest/installer ダウンロードしたzpm-0.x.x.xmlを%SYSネームスペースにロードします。 USER> zn "%SYS" %SYS> do $SYSTEM.OBJ.Load(ファイル名,"c") IPMをインストールいただくことで、簡単にOpen Exchangeに収録されている対応ツールやパッケージをダウンロード、インストールできます。特に依存関係のあるパッケージもインストールされますので簡単です。 Git for Shared Development Environment のインストール Git for Shared Development Environmentは以下のOpenExchangeに掲載されており、スタジオからgitを使用してGitHubとの連携を行うツールです。https://openexchange.intersystems.com/package/Git-for-Shared-Development-Environments ターミナルを起動し、開発環境のネームスペースに移動、IPMを使用してインストールします。 USER> zn "DEV" DEV> zpm "install git-source-control" 設定メソッドを実行し、gitのパスやローカルリポジトリのフォルダ、SSH秘密鍵、Git操作ユーザを設定します。 DEV>d ##class(SourceControl.Git.API).Configure() Configured SourceControl.Git.Extension as source control class for namespace DEV Configured default mappings for classes, routines, and include files. You can customize these in the global: ^SYS("SourceControl","Git","settings","mappings") Path to git executable: "C:\Program Files\Git\bin\git.exe" => <-- Enterのみ Local git repo root folder: c:\temp\DEV\ => <-- ローカルリポジトリのフォルダを入力 Path to private key file (for ssh remotes): c:\Users\user\.ssh\id_rsa <-- 先ほど作成したSSH秘密鍵のファイル名 Event handler class for git pull: SourceControl.Git.PullEventHandler.Default => <-- Enterのみ Character to replace % symbol when importing %-classes into the file systems: <-- Enterのみ Attribution: Git username for user 'username': username => <-- Git操作を行うユーザ名 Attribution: Email address for user 'username': username@pc.company.com => <-- Git操作を行うユーザのメールアドレス Settings saved. DEV> ローカルリポジトリの初期化 Git for windowsバージョン2.35.2以降ではセキュリティが強化されており、ローカルリポジトリフォルダの所有者がローカルシステムアカウントでない場合、エラーが発生するようになりました。そのため、一旦フォルダを削除します。 C:\Users\username> rmdir C:\Temp\DEV スタジオを起動し、開発環境のネームスペースに接続します。「Git」メニューの「Initialize」をクリック、ローカルリポジトリを初期化します。 「Git」メニューが表示されないときは、管理ポータルを起動し、「システム管理」「構成」「追加設定」「ソースコントロール」をクリックしますと、以下のような画面が表示されます。 ここで画面左のネームスペースで開発環境のネームスペースを選択し、ソースコントロールクラス名にて「SourceControl.Git.Extension」クラスをクリック、画面上の「保存」ボタンをクリックします。その後、スタジオを再起動することで「Git」メニューが表示されるようになります。 リモートリポジトリの設定とソースコードの取り込み コマンドプロンプトより、リモートリポジトリ(github.com/username/xxx.git)を登録します C:\Users\username> cd C:\Temp\DEV C:\Temp\DEV> git remote add origin git@github.com:username/xxx.git リモートリポジトリからソースコードをpullし、テンポラリフォルダにソースコードを取り込みます。 C:\Temp\DEV> git pull origin master remote: Enumerating objects: 79, done. remote: Counting objects: 100% (79/79), done. remote: Compressing objects: 100% (51/51), done. remote: Total 79 (delta 14), reused 77 (delta 14), pack-reused 0 Unpacking objects: 100% (79/79), 42.87 KiB | 142.00 KiB/s, done. From github.com:miniclub/xxx * branch master -> FETCH_HEAD * [new branch] master -> origin/master ブランチがmasterでない場合、以下のようにブランチ名を指定し、git checkoutで現在のブランチを変更してください。 C:\Temp\DEV> git pull origin dev : C:\Temp\DEV> git chekout dev マッピング情報の変更 取り込んだリモートリポジトリのフォルダ構成に合わせてマッピング情報を変更します。 スタジオを起動し、開発環境のネームスペースに接続、「Git」「Settings」メニューをクリックします。設定画面が表示されますので、「Mappings」欄に移動します。 表の左からソースコードの拡張子、名前、関連パスとなっており、赤枠で囲まれた関連パスをリポジトリのフォルダ構成に応じて変更します。 例えば、vscodeで作成したリポジトリではクラスやルーチンは全てsrcフォルダ配下に入りますので、それらの値は全て「src/」となります。 ソースコードのインポート 「Git」「Import (force)」メニューで、ローカルリポジトリフォルダのソースコードをIRISに読み込みます。 以上で、GitHubに登録されたリポジトリのソースコードをIRISに取り込むことができました。クラスやルーチンを修正した後、各ファイルを表示した状態で「Git」「Commit changes to file」メニューをクリックすることで変更をコミットし、「Git」「Push to remote branch」でリモートリポジトリに反映させることが。 最後までお読みいただきありがとうございます。 一度お試しいただいて、ご意見やご質問等ございましたら、コメント欄に書き込みいただければと思います。 zpmをロードするメソッドが間違っていましたので、修正しました。$SYSTEM.OBJ.Import(... ⇒ $SYSTEM.OBJ.Load(...
記事
Mihoko Iijima · 2023年6月2日

任意のWebアプリから呼び出せるワークフロー用 REST API の使い方

開発者の皆さん、こんにちは! この記事では、ワークフローコンポーネントを使ってみよう!~使用手順解説~ でご紹介したユーザ操作画面(ユーザポータル)を任意のWebアプリに変更する際に便利な REST API の使用方法をご紹介します。 ワークフロー用 REST APIですが、開発者コミュニティのサンプル公開ページ:Open Exchange に公開されているAPIでどなたでも自由にご利用いただけます。 Open Exchangeの検索ボックスに「Workflow rest」と入力すると出てきます。EnsembleWorkflow が対象のサンプルです。 ちなみに、2023年6月2日時点で724のアプリケーションが公開されているようです👀 以下の内容もぜひご確認ください。 ワークフローコンポーネントでどんなことができるのか?の概要説明については、ウェビナーをご参照ください。 コンテナで動くサンプルもご用意しています。流れを先に確認されたい場合は、システム連携の自動的な流れの中にユーザからの指示を介入できる「ワークフローコンポーネント」のサンプル の手順に沿って動作をご確認ください。 EnsembleWorkflow のページに移動したらボタンをクリックするとサンプルがあるGitリポジトリに移動します。 InterSystems Package Manager(IPM)でもインストールできますが、この記事ではGitリポジトリから入手する方法で記載しています。 リポジトリに移動すると、isc/wf/REST.clsクラスが確認できます。このクラスをお手元のIRISにインストールし、RESTのエンドポイント(=ウェブアプリケーションパスの設定)を作成したら利用できます。 以降の説明では、お手元のIRISにこのクラスをインポートした後の手順をご紹介します。 クラス定義のインポート方法については、1つ前の記事「ワークフローコンポーネントを使ってみよう!~使用手順解説~」の 4) サンプルのインポート をご参照ください。 この後の説明は、「システム連携の自動的な流れの中にユーザからの指示を介入できる「ワークフローコンポーネント」のサンプル」 の手順に沿って作成したワークフロータスクを処理できる環境があることを前提としています。 サンプルをインポートすると「isc.wf.REST」の名称でクラスがインポートされます。このクラス名を使用してRESTのエンドポイントを作成します。 IRISの管理ポータルを開きます(ウェブサーバ名、ポート番号は環境に合わせてご変更ください)👉 http://localhost:52773/csp/sys/UtilHome.csp 管理ポータル > [システム管理] > [セキュリティ] > [アプリケーション] > [ウェブ・アプリケーション] に移動します。 ボタンをクリックし、新しいウェブ・アプリケーションを作成します。 以下の項目を設定します。 「名前」にエンドポイントのパスを指定します。例では、/wfsample としています。(/ を先頭につけ忘れないようにしてください) 「ネームスペース」にクラス定義をインポートしたネームスペースを指定します。例では、USER を選択しています。 「REST」の「ディスパッチ・クラス」にインポートしたクラス名を指定します。例では、isc.wf.REST を指定しています。 セキュリティの設定の「許可された認証方法」を「パスワード」のみに変更します。 ワークフローユーザ名でログインが必要となりますので、パスワードのみにします。 設定が完了したら、画面上にある保存ボタンをクリックします。 保存後表示される「アプリケーション・ロール」タブを選択し、このパスを通過できたときに付与されるロールを設定します。 適切なロールを事前に作成し追加するほうが適切ですが、以下の例では事前定義ロールの%Allを指定する例でご紹介します。 「利用可能」から %Allを選択 ▶アイコンを利用して%Allを「選択済み」に移動 「付与する」ボタンクリック これで準備完了です。早速RESTでアクセスしてみます。 ワークフロータスクが0件の場合は、サンプルデータを更新しタスクを作成してください。 手順については「システム連携の自動的な流れの中にユーザからの指示を介入できる「ワークフローコンポーネント」のサンプル」の「5.メモ」をご参照ください。 (例では、RESTクライアントにPostmanを使用しています。) タスク一覧を取得:GET http://ウェブサーバ/エンドポイント/tasks で取得できます。(ワークフローユーザを利用してBasic認証でアクセスします。パスワードは設定されたパスワードをご利用ください。) 例)http://localhost:52773/wfsample/tasks タスク一覧のJSONオブジェクトの中に、idプロパティがあります。このidの値を利用して、1件のタスク詳細をGETできます。 1件のタスク詳細取得:GET http://ウェブサーバ/エンドポイント/task/id で取得できます。(ワークフローユーザを利用してBasic認証でアクセスします。パスワードは設定されたパスワードをご利用ください。) 例)http://localhost:52773/wfsample/task/8||ManagerA 1件のタスクを処理する:POST http://ウェブサーバ/エンドポイント/task/id をPOST要求で実行します。(ワークフローユーザを利用してBasic認証でアクセスします。パスワードは設定されたパスワードをご利用ください。) 例)http://localhost:52773/wfsample/task/8||ManagerA 特定した1件のタスクを処理します。今回の例では、承認か却下のボタンを押すことができるので、承認のボタンを押すアクションを実施します。 POST時に送付するJSONは以下の通りです。 { "action": "承認", "formFields": { "RejectedReason": "" } } action にボタン名を指定します。 formFields.RejectedReasonには「却下理由」を指定します。「承認」の時は空を設定し渡します。​​​​ この時のトレースは以下の通りです。 「承認」の情報が渡されています。 では次に、却下を試します。 別のタスクIDを利用して、却下理由(RejectedReason)も含めてPOSTします。 トレースを確認します。 ​​​​​「却下」の情報が渡されていることと「却下理由」も渡されていることを確認できます。 REST APIでワークフロー用データにアクセスできれば、IRIS付属のユーザポータルではなくお好みのWebアプリケーションをユーザポータルとして利用することもできます。 システム連携の流れの中にユーザからの指示を含めたい処理がある!!という場合には、ぜひこのワークフローコンポーネントをご利用ください😀
記事
Toshihiko Minamoto · 2022年11月2日

時間の支配者になるには ー誕生

> **良識のある人にはルールなんていらない。** > > _ドクター_ 日付と時間のマスターになるのは簡単なことではありません。いつも問題になる上、どのようなプログラミング言語でも混乱することがあります。そこでこのタスクが可能な限り単純になるように、分かりやすく説明していくつかのヒントをご紹介しましょう。 さぁ、[ターディス](https://ja.wikipedia.org/wiki/%E3%82%BF%E3%83%BC%E3%83%87%E3%82%A3%E3%82%B9)に乗り込みましょう。あなたを**時間の支配者**にして差し上げます。 ![ターディス](/sites/default/files/inline/images/tardis-doctor.gif) ## 基本から始めよう 普段、他の言語を使用しているのであれば、InterSystems Object Script(以降「IOS」と呼びますが、Apple モバイルと勘違いしないように)の日付は少し独特です。 ターミナルで [$HOROLOG](https://docs.intersystems.com/healthconnectlatest/csp/docbookj/DocBook.UI.Page.cls?KEY=RCOS_VHOROLOG) を実行して現在の日付と時刻を取得する場合、2 つの部分に分割されているのがわかります。 WRITE $HOROLOG > 66149,67164 最初の値は日にちです。正確には 1840 年 12 月 31 日以来の日数であり、つまり値 1 は 1841 年 1 月 1 日となります。2 つ目の値は今日の 00:00 時以来の秒数です。 この例では、66149 は 09/02/2022(欧州フォーマットの dd/mm/yyyy なので 2 月 9 日)、67164 は 18:39:24 に対応しています。 このフォーマットを日付と時刻の**内部フォーマット**と呼ぶことにします。 混乱してきましたか? (日付と時刻の)宇宙の大きな秘密を解明し始めることにしましょう。   ## 内部フォーマットをわかりやすいフォーマットに変換するには? それについては、[$ZDATETIME](https://docs.intersystems.com/healthconnectlatest/csp/docbookj/DocBook.UI.Page.cls?KEY=RCOS_fzdatetime) コマンドを使用します。 基本的なコマンドは以下のようになります。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow) > 02/09/2022 18:39:24 デフォルトでは、アメリカ式フォーマット(mm/dd/yyyy)を使用します。 別のフォーマットで日付を表す場合は、欧州フォーマット(dd/mm/yyyy)など2 つ目のパラメーターを使用し、この場合は値 4 を指定します(詳細については、ドキュメントの [$ZDATETIME.dformat](https://docs.intersystems.com/healthconnectlatest/csp/docbookj/DocBook.UI.Page.cls?KEY=RCOS_fzdatetime#RCOS_fzdatetime_dformat) をご覧ください)。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,4) > 09/02/2022 18:39:24 このオプションは、ローカル変数で定義したものを区切り文字と年フォーマットを使用します。 また、別の時間フォーマットを使用したい場合、たとえば 24 時間制ではなく 12 時間制(午前/午後)を使用したい場合は、3 つ目のパラメータを値 3 とし、秒数を表示しない場合は、値 4 を使用します(ドキュメントの [ $ZDATETIME.tformat](https://docs.intersystems.com/healthconnectlatest/csp/docbookj/DocBook.UI.Page.cls?KEY=RCOS_fzdatetime#RCOS_fzdatetime_tformat) を参照)。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,4,3) > 09/02/2022 06:39:24PM WRITE $ZDATETIME(RightNow,4,4) > 09/02/2022 06:39PM わかってきましたか? ではもっと詳しく見てみましょう。 ### ODBC フォーマット このフォーマットはローカル構成に依存しておらず、常に yyyy-mm-dd として表示されます。この値は 3 です。 CSV、HL7 などのファイルでエクスポートされるデータを作成する際は、これを使用することをお勧めします。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,3) > 2022-02-09 18:39:24 ### 曜日、曜日名、年間通産日 | 値 | 説明 | | -- | ------------------------------------------------------------------------- | | 10 | 曜日は 0 から 6 の値で、日曜日は 0、土曜日は 6 です。 | | 11 | 曜日の略名で、ユーザーが定義するローカル構成に基づいて返します。IRIS のデフォルトインストールは enuw(英語、米国、Unicode)です。 | | 12 | ロング形式の曜日名。 11 と同じです。 | | 14 | 年間通産日。つまり 1 月 1 日からの日数です。 | 日付と時刻を個別に扱う場合は、それぞれ $ZDATE コマンドと $ZTIME コマンドを使用します。 フォーマットのパラメーターは、[$ZDATETIME.dformat](https://docs.intersystems.com/healthconnectlatest/csp/docbookj/DocBook.UI.Page.cls?KEY=RCOS_fzdatetime#RCOS_fzdatetime_dformat)と[$ZDATETIME.tformat](https://docs.intersystems.com/healthconnectlatest/csp/docbookj/DocBook.UI.Page.cls?KEY=RCOS_fzdatetime#RCOS_fzdatetime_tformat)で定義されているパラメーターと同じです。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATE(RightNow,10) > 3 WRITE $ZDATE(RightNow,11) > Wed WRITE $ZDATE(RightNow,12) > Wednesday ## 日付を内部フォーマットに変換するには? では、逆のステップを見てみましょう。日付のあるテキストを IOS フォーマットに変換する方法です。 このタスクでは、[$ZDATETIMEH](https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fzdatetimeh) コマンドを使用します。 今度は、日付と時刻に使用されているフォーマット($ZDATETIMEH を使用している場合)、または日付($ZDATEH)または時刻($ZTIMEH)に使用されているフォーマットを個別に示す必要があります。 フォーマットは同じです。つまり、ODBC フォーマット(yyyy-mm-dd)の日付を持つ文字列がある場合は、値 3 を使用します。 SET MyDatetime = "2022-02-09 18:39:24" SET Interna1 = $ZDATETIMEH(MyDatetime, 3, 1) // ODBC フォーマット SET MyDatetime = "09/02/2022 18:39:24" SET Interna2 = $ZDATETIMEH(MyDatetime, 4, 1) // 欧州フォーマット SET MyDatetime = "02/09/2022 06:39:24PM" SET Interna3 = $ZDATETIMEH(MyDatetime, 1, 3) // 米国フォーマット、12 時間制 AM/PM WRITE Interna1,!,Interna2,!,Interna3 > 66149,67164 66149,67164 66149,67164 論理的に、文字列が特定のフォーマットを使用しており、誤ったパラメーターを指定した場合は、2 月 9 日ではなく 9 月 2 日として理解するなど、どのようなことでも起こりえます。 フォーマットを混在させないようにしましょう。後で問題になります。 SET MyDatetime = "09/02/2022" /// 米国フォーマット SET InternalDate = $ZDATEH(MyDatetime, 1) /// 欧州フォーマット SET OtherDate = $ZDATETIME(InternalDate, 4) WRITE InternalDate,!,OtherDate > 66354 02/09/2022 もちろん、欧州式の日付を設定し、米国式に変換しようとすると ... バレンタインデーはどうなってしまうのでしょうか? SET MyDatetime = "14/02/2022" SET InternalDate = $ZDATEH(MyDatetime, 1) // 米国フォーマット。 14 月は存在しません!!! ^ <ILLEGAL VALUE> ハートブレイクなバレンタインデーと同じように... コードブレイクしてしまいました。 では、学習した内容を使って、何かやってみましょう。 READ !,"Please indicate your date of birth (dd/mm/yyyy): ",dateOfBirth SET internalFormat = $ZDATEH(dateOfBirth, 4) SET dayOfWeek= $ZDATE(internalFormat, 10) SET nameOfDay = $ZDATE(internalFormat, 12) WRITE !,"The day of the week of your birth is: ",nameOfDay IF dayOfWeek = 5 WRITE "you always liked to party!!!" // 金曜日生まれ 後で、これを他のやり方で行う方法と、エラーの処理方法を見ることにしましょう。 ###   ### 次の章: タイムトラベルの方法   ## トリビア なぜ 1841年1月1日の値が値1 となるのでしょうか? それが選択された理由は、オブジェクトスクリプトを拡張した [MUMPS プログラミング言語](https://ja.wikipedia.org/wiki/MUMPS)が設計されたときに、南北戦争の退役軍人として当時生存していた最年長 121 歳のアメリカ人が生まれる前のうるう年でない年であったためです。   >オブジェクトスクリプトを拡張した MUMPS プログラミング言語が設計されたときに これは逆ですね。MUMPSプログラミング言語を拡張したオブジェクトスクリプト
記事
Toshihiko Minamoto · 2023年3月23日

IRIS IntegratedML を使った腎臓病予測 Web アプリ

腎臓病は、医学会でよく知られるいくつかのパラメーターから発見することが可能です。 この測定により、医学界とコンピューター化されたシステム(特に AI)を支援すべく、科学者である Akshay Singh は、腎臓病の検出/予測における ML アルゴリズムをトレーニングするための非常に便利なデータセットを公開しました。 このデータセットは、ML の最大級のデータリポジトリとして最もよく知られている Kaggle に公開されています。https://www.kaggle.com/datasets/akshayksingh/kidney-disease-dataset ## データセットについて 腎臓病データセットには、以下のメタデータ情報が含まれています(出典: https://www.kaggle.com/datasets/akshayksingh/kidney-disease-dataset) * 赤血球、足浮腫、血糖値などの 25 種類の特徴量を含む 400 行のデータセット。 * 患者が慢性腎臓病を患っているかどうかを分類することが目的 * 分類は、'classification' と名付けられた属性が 'ckd'(慢性腎臓病)であるか 'notckd' であるかに基づいて行われます。 * データセットの作成者は、テキストと数値のマッピングやその他の変更点を含むデータセットのクリーニングを実施しました。 クリーニングの後、データセット作成者は EDA(探索的データ解析)を行い、データセットをトレーニングとテストに分割し、それらにモデルを適用しました。 分類結果は、最初はあまり満足のいくものではないことがわかっています。 そこで、Nan 値のある行をドロップする代わりに、ラムダ関数を使用して、それらの行を各カラムのモードに置き換えて、 もう一度データセットをトレーニングセットとテストセットに分割し、モデルに適用しました。 今度は、結果が改善され、ランダムフォレストと意思決定ツリーが、1.0 と 0 の誤分類の精度で最も良く実行することがわかりました。 分類のパフォーマンスは、混合行列、分類レポート、および制度の出力によって測定されています。 ### データセットの情報(出典: https://archive.ics.uci.edu/ml/datasets/chronic\_kidney\_disease ) データセットの収集には以下の表現を使用しています。 age - 年齢 bp - 血圧 sg - 比重 al - アルブミン値 su - 糖分 rbc - 赤血球 pc - 膿細胞 pcc - 膿細胞の塊 ba - 細菌 bgr - ランダム血糖値 bu - 血液尿素 sc - 血清クレアチニン sod - ナトリウム pot - カリウム hemo - ヘモグロビン pcv - ヘマトクリット値 wc - 白血球数 rc - 赤血球数 htn - 高血圧 dm - 糖尿病 cad - 冠動脈疾患 appet - 食欲 pe - 足浮腫 ane - 貧血症 class - クラス ### 属性の情報(出典: https://archive.ics.uci.edu/ml/datasets/chronic\_kidney\_disease) 24 + class = 25(数値 11 個、名義尺度 14 個)を使用します。 1. 年齢(数値) 年数 2. 血圧(数値) bp(mm/Hg) 3. 比重(名義尺度) sg - (1.005,1.010,1.015,1.020,1.025) 4. アルブミン値(名義尺度) al - (0,1,2,3,4,5) 5. 糖分(名義尺度) su - (0,1,2,3,4,5) 6. 赤血球(名義尺度) rbc - (normal,abnormal) 7. 膿細胞(名義尺度) pc - (normal,abnormal) 8. 膿細胞集塊(名義尺度) pcc - (present,notpresent) 9. 最近(名義尺度) ba - (present,notpresent) 10. ランダム血糖値(数値) bgr(mgs/dl) 11. 血液尿素(数値) bu(mgs/dl) 12. 血清クレアチニン(数値) sc(mgs/dl) 13. ナトリウム(数値) sod(mEq/L) 14. カリウム(数値) pot(mEq/L) 15. ヘモグロビン(数値) hemo(gms) 16. ヘマトクリット値(数値) 17. 白血球数(数値) wc(cells/cumm) 18. 赤血球数(数値) rc(100万/cmm) 19. 高血圧(名義尺度) htn - (yes,no) 20. 糖尿病(名義尺度) dm - (yes,no) 21. 冠動脈疾患(名義尺度) cad - (yes,no) 22. 食欲(名義尺度) appet - (good,poor) 23. 足浮腫(名義尺度) pe - (yes,no) 24. 貧血症(名義尺度) ane - (yes,no) 25. 区分(名義尺度) class - (ckd,notckd) ## Kaggle から腎臓データを取得する Kaggle の腎臓データは、Health-Dataset アプリケーション(https://openexchange.intersystems.com/package/Health-Dataset)を使って IRIS テーブルに読み込めます。 これを行うには、module.xml プロジェクトから依存関係(Health Dataset 用の ModuleReference)を設定します。 Health Dataset アプリケーションリファレンスを使用した Module.xml <?xmlversion="1.0" encoding="UTF-8"?> <Export generator="Cache" version="25">  <Documentname="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)に移動し、以下の手順に従います。 1. リポジトリを任意のローカルディレクトリに Clone/git pull します。 $ git clone https://github.com/yurimarx/predict-diseases.git 2. このディレクトリで Docker ターミナルを開き、以下を実行します。 $ docker-compose build 3. IRIS コンテナを実行します。 $ docker-compose up -d 4. AI モデルをトレーニングするため管理ポータルのクエリ実行(http://localhost:52773/csp/sys/exp/%25CSP.UI.Portal.SQL.Home.zen?$NAMESPACE=USER)に移動します。 5. トレーニングに使用するビューを作成します。 CREATE VIEW KidneyDiseaseTrain AS SELECT age, al, ane, appet, ba, bgr, bp, bu, cad, classification, dm, hemo, htn, pc, pcc, pcv, pe, pot, rbc, rc, sc, sg, sod, su, wc FROM dc_data_health.KidneyDisease 6. ビューを使用して AI モデルを作成します。 CREATE MODEL KidneyDiseaseModel PREDICTING (classification) FROM KidneyDiseaseTrain 7. モデルをトレーニングします。 TRAIN MODEL KidneyDiseaseModel 8. [http://localhost:52773/disease-predictor/index.html](http://localhost:52773/disease-predictor/index.html) に移動し、Disease Predictor フロントエンドを使用して、以下のように疾患を予測します。 ## 背後の処理 ### 腎臓病を予測するためのバックエンドのクラスメソッド InterSystems IRIS では、前に作成されたモデルを使って、SELECT の実行により予測することができます。 腎臓病を予測するためのバックエンドのクラスメソッド /// 腎臓病の予測 ClassMethod PredictKidneyDisease() As %Status {     Try {       Set data = {}.%FromJSON(%request.Content)       Set %response.Status = 200       Set %response.Headers("Access-Control-Allow-Origin")="*"             Set qry = "SELECT PREDICT(KidneyDiseaseModel) As PredictedKidneyDisease, "                   _"age, al, ane, appet, ba, bgr, bp, bu, cad, dm, "                   _"hemo, htn, pc, pcc, pcv, pe, pot, rbc, rc, sc, sg, sod, su, wc "                   _"FROM (SELECT "_data.age_" AS age, "                   _data.al_" As al, "                   _"'"_data.ane_"'"_" AS ane, "                   _"'"_data.appet_"'"_" AS appet, "                   _"'"_data.ba_"'"_" As ba, "                   _data.bgr_" As bgr, "                   _data.bp_" AS bp, "                   _data.bu_" AS bu, "                   _"'"_data.cad_"'"_" As cad, "                   _"'"_data.dm_"'"_" As dm, "                   _data.hemo_" AS hemo, "                   _"'"_data.htn_"'"_" AS htn, "                   _"'"_data.pc_"'"_" As pc, "                  _"'"_data.pcc_"'"_" As pcc, "                   _data.pcv_" AS pcv, "                   _"'"_data.pe_"'"_" AS pe, "                   _data.pot_" As pot, "                   _"'"_data.rbc_"'"_" As rbc, "                   _data.rc_" AS rc, "                   _data.sc_" AS sc, "                   _data.sg_" As sg, "                   _data.sod_" As sod, "                   _data.su_" AS su, "                   _data.wc_" AS wc)"       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.classification = rset.PredictedKidneyDisease       Set Response.age = rset.age       Set Response.al = rset.al       Set Response.ane = rset.ane       Set Response.appet = rset.appet       Set Response.ba = rset.ba       Set Response.bgr = rset.bgr       Set Response.bp = rset.bp       Set Response.bu = rset.bu       Set Response.cad = rset.cad       Set Response.dm = rset.dm       Set Response.hemo = rset.hemo       Set Response.htn = rset.htn       Set Response.pc = rset.pc       Set Response.pcc = rset.pcc       Set Response.pcv = rset.pcv       Set Response.pe = rset.pe       Set Response.pot = rset.pot       Set Response.rbc = rset.rbc       Set Response.rc = rset.rc       Set Response.sc = rset.sc       Set Response.sg = rset.sg       Set Response.sod = rset.sod       Set Response.su = rset.su       Set Response.wc = rset.wc         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 フォルダをご覧ください。
記事
Toshihiko Minamoto · 2020年10月8日

Ansible を使った Caché アプリケーションのプロビジョニング(パート 1)

Ansible は Caché とアプリケーションコンポーネントをいかに迅速にデータプラットフォームのベンチマークにデプロイするかという課題を解決するのに役立ちました。 同じツールと方法をテストラボ、トレーニングシステム、開発環境、またはその他の環境の立ち上げも使うことができます。 顧客サイトにアプリケーションをデプロイする場合、デプロイの大部分を自動化し、アプリケーションのベストプラクティス標準に合わせてシステム、Caché、アプリケーションを確実に構成することができます。 ## 概要 テクノロジーアーキテクトである私たちのグループの仕事の一つに、さまざまなベンダーのハードウェアやオペレーティングシステムでの InterSystems データプラットフォームのベンチマークがあります。 多くの場合、インフラストラクチャはリリース前のものであり、返却や他者への引き渡しが必要になるまでの時間が決まっているため、ベンチマークを迅速かつ正確に設定し、実際のベンチマーク作業にできるだけ多くの時間をかけることが不可欠です。 私たちは長年にわたってシェルスクリプトレットとチートシートやチェックリストからのカットアンドペーストを使用し、多くのベンチマークインストール作業を自動化していましたが、多くのサーバーがあり、異なるオペレーティングシステムを使用する場合は特に非常に激しくエラーが発生する傾向がありました。SLES 11、Red Hat 6、Red Hat 7、AIX などでのサービスのインストールや使用には、微妙で厄介な違いがある場合があるからです。 私はシステムの自動構成と管理に使用できるいくつかのソフトウェアオプションを検討した後、データプラットフォームアプリケーションとベンチマークコンポーネントをプロビジョニングする作業のために Ansible を選択しました。 なお、Ansible がデプロイと構成に**最適な**ソリューションであると決めつけているわけではありませんのでご了承ください。 私は Ansible を選択する前に、Puppet や Chef などの他のツールの機能や操作を確認しました。 あなたの組織がすでに他のツールを使用しており、あなたがそれを使用できるのなら、私が Ansible で使用する方法やコマンドなどは他のソフトウェアに読み替えることができるはずです。この投稿が、使用しているツールに関係なく役立つことを願っています。 これは、InterSystems データプラットフォームのアプリケーションをデプロイする際に Ansible を使用する方法を説明する連載の最初の投稿です。 この投稿では、Caché をインストールして基盤を構築する方法について説明します。次の投稿では、ソリューションを拡張し、%installer クラスの使用を含むアプリケーションのインストールについて掲載します。 この記事では以下について説明します。 * Ansible の概要とインストール。 * 簡単な管理とスケーリングのための Ansible のレイアウト。 * 1つ以上のサーバーでの Caché のインストール。 ## Ansible とは? Ansible では複雑なタスクを自動化しながら1つ以上のサーバーを構成でき、新しいサーバーを非常に簡単に追加できます。 タスクは冪等性が保たれるように設計されています(同じサーバーで同じスクリプトを何度でも実行でき、結果のサーバー構成は同じになります)。 Ansible をプロビジョニングタスク用に選択した主な理由は、システム要件が最小(Linux サーバーに Python 2.7 がインストールされていること)であり、自己完結型のソリューションであることです。Ansible のコードは管理サーバーにのみインストールされ、プッシュアーキテクチャを使用して OpenSSH 経由でターゲットサーバー上のコマンドとスクリプトを実行します。 プロビジョニングされるサーバーにはエージェントは必要ありません。 対照的に、Chef と Puppet はプルクライアントアーキテクチャであり、クライアントサーバー(Web、データベースなど)にソフトウェアが読み込まれ、クライアントは継続的に更新をマスターにポーリングします。 Ansible のプッシュアーキテクチャは、スケジュールに応じてサーバーを段階的に実装するのにも適しています。 Ansible はオープンソースであり、コミュニティによってメンテナンスされています。 Ansible, Inc は 2015 年 から Red Hat が所有しています。 Ansible, Inc は付加価値の高いライフサイクル製品(Ansible Tower)と共に有償のサポートとトレーニングを提供していますが、この投稿で使用されているものはすべてオープンソースのコマンドラインバージョンです。 活発なコミュニティ(Ansible Galaxy)があり、Web サーバー、ftp、Kerbros のインストールなどの多数のタスク用にダウンロードできる既成のソリューションが豊富に存在し、拡充され続けています。 完全なベンチマークのデプロイプロジェクトの例に、RHEL、SLES、または Solaris に(他のプラットフォームと共に)Apache 2.x をインストールして構成するためにダウンロードしてカスタマイズした Apache モジュールを含めています。 Ansible のダウンロードとインストールの手順は、Ansible の Web サイトと github にあります。 質問がある場合や貢献したい場合は、活発なコミュニティがあります。 https://www.ansible.com/get-started http://docs.ansible.com ## Ansible のインストール この投稿の例は、Red Hat 7.0 および 7.2 が稼働中の VM でテストされています。また、Centos 7 を搭載した私のノートパソコンで VirtualBox と Vagrant を使用し、Ansible コントローラーサーバーの初期テストも行いました。 Caché をコントローラーにインストールする必要はないため、Caché でサポートされているよりも多くのプラットフォームからオペレーティングシステムを選択できます。 話を簡単にするため、私は Red Hat で利用可能な Ansible の最新 rpm バージョン(Ansible 1.9.4)を使用しました。それ以降のバージョンは github から入手できます。 この例では、cache-2015.2.2.805.0-lnxrhx64 をインストールしていますが、HealthShare または Ensemble ディストリビューションにも同じ一般的な手順を適用できます。 後述するように、特定のファイル名、ディレクトリパスなどの変数を使用してインストールオプションをパラメーター化します。 この最初の投稿ではタスクを基本的な Caché のインストールに限定しています。そのため、ほとんどのタスクはプラットフォームに依存しません。 Ansible プレイブックが開始する最初のタスクの 1 つは、ターゲットマシンのマニフェスト(オペレーティングシステム、インターフェースカード、メモリ情報、CPU 数、ディスクレイアウトなど)を取得することです。このターゲットオペレーティングシステムの情報は、ターゲットで実行される実際のコマンドから Ansible スクリプトのコマンドを抽象化するためにコマンドが実行される際に使用されます(Red Hat の service httpd on や SLES の /etc/init.d/apache2 など)。 ここでは皆さんがプラットフォームの説明に従い、説明を読んで管理マシンに Ansible をインストールしたと想定します。 Ansible では Linux システムをコントローラーとして使用する必要がありますが、ターゲットシステムは Linux または Windows にすることができます。 Windows ターゲットの詳細については、Ansible のドキュメントを参照してください。 ### コントローラーシステムのインストール例:RHEL/CentOS 7 64ビット上の Ansible Red Hat または CentOSでは、Ansible を含む epel-release(Enterprise Linux 用の追加パッケージ)RPM を先にインストールする必要があります。 epel プロジェクトは多くの有用なオープンソースパッケージ(ネットワーク、システム管理、監視など)をまとめて提供しており、主要な Linux ディストリビューション向けに設計されています。 [root@localhost tmp]# wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm : : [root@localhost tmp]# rpm -ivh epel-release-7-5.noarch.rpm : : [root@localhost tmp]# yum --enablerepo=epel info ansible Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager Installed Packages Name : ansible Arch : noarch Version : 1.9.4 Release : 1.el7 Size : 7.0 M Repo : installed From repo : epel Summary : SSH-based configuration management, deployment, and task execution system URL : http://ansible.com License : GPLv3+ Description : : Ansible is a radically simple model-driven configuration management, : multi-node deployment, and remote task execution system. Ansible works : over SSH and does not require any software or daemons to be installed : on remote nodes. Extension modules can be written in any language and   : are transferred to managed machines automatically. [root@localhost tmp]# [root@localhost tmp]# sudo yum install ansible : : [root@localhost tmp]# ansible --version ansible 1.9.4     configured module search path = None **成功しました。 始めましょう!** ## Ansible について インベントリ、プレイブック、モジュール、ロールなどのさまざまな Ansible コンポーネントの使用方法を Ansible のドキュメントでよく確認する必要があります。 管理を単純化し、大規模で複雑なスクリプトファイルを回避するため、事前定義されたディレクトリ構造と検索パスが使用されています。 この投稿では Ansible の推奨事項を使用し、より大きなインストールの例を作ることを検討する際のモデルとして使用できるファイル構造を使用する予定です。 使用されている Ansible モジュールはコメントが付いた分かりやすいもので、github から入手できます。 ファイルをダウンロードして内容を読み、ワークフローの感触をつかんでください。 この例のベースディレクトリには、次のファイルがあります。 * ansible.cfg:Ansible のデフォルト値を変更します。 * inventory:作業環境を定義し、記述します (サーバー名/IPなど)。 * >任意の名前<.yml ファイル:これらのファイルは、特定のサーバーのロールに対して実行されるタスクセットを記述します。 ### 用語集 今後の説明をより分かりやすくするため、いくつかの Ansible 用語について簡単に説明します。 モジュールは、システムで実行する自動化アクションを作成するビルディングブロックです。 各モジュールは特定のタスク用に構築されており、パラメーターを使用してそのタスクを変更できます。 例えば、ファイルのコピー、ユーザーの作成、コマンドの実行、サービスの開始などがあります。 現在、デフォルトの Ansible インストールには 400 種類以上のモジュールが含まれており、さらにはコミュニティからも多くのモジュールが提供されていますが、独自に作成することもできます。 モジュールは自動化ワークフローを実行する手段として、プレイとプレイブックを作成するために組み合わされます。 プレイは複数のタスクを持つことができ、プレイブックは複数のプレイを持つことができます。 ロールを使うと、複数のプレイブックを組み合わせることができます。 ロールは、ターゲットサーバーの使用状況に応じてサーバーのコンポーネント構成をグループ化するものと考えることができます。 この投稿のロールの例では、サーバーを構築するための構成レイヤーを構築します。 私のベンチマーク環境では、次のロールを使用してサーバーを構築しています。 * hs\_server\_common: OS の構成、Apache のインストール、Caché のインストール。 *  webserver: Webファイル(csp、html、js など)のコピー、アプリケーション用のApacheの構成。 * generator: ファイルのコピー、Webストレスジェネレーターのデータベース / ネームスペース / グローバルマッピングなどの作成と構成。 * dbserver:ファイルのコピー、DBサーバーシステムの設定 / アプリケーションデータベース / ネームスペース / グローバルマッピングなどの構成。 これらのロールを組み合わせて、さまざまなサーバータイプを構築できます。 * hs\_server\_common + webserver + generator = Web ストレスジェネレーターサーバー。 * hs\_server\_common + webserver = アプリケーション Web サーバー。 * hs\_server\_common + dbsevrer = データベースサーバー。 ロールを構成するものや各ロールに含まれる構成は、デプロイされるアプリケーションによって大きく異なります。 この投稿の例では、オペレーティングシステムの事前構成を前提とする最小限のタスクセットを使用しますが、Ansible や Galaxy で利用可能なモジュールを使用すると、はるかに複雑なフル機能のシステム構成が可能になります。 ## Caché のインストールに関するメモ {.MsoNormal} 私はいくつかの興味く便利な機能を紹介するため例を記載しましたが、その中でも以下は特に注目すべきものです。 注: これらの例は、InterSystems データプラットフォーム(Caché、HealthShare、Ensemble)をインストールするためのガイドとして使用できます。 HealthShare をインストールする例を書きましたが、HealthShare と Caché には同じ機能があります。 **./testserver/roles/hs\_server\_common/tasks/main.yml** これは、一般的な OS の構成、Apache のインストール、Caché のインストールの各タスクに使えるメインラインスクリプトです。 この投稿ではCaché をインストールおよび構成するため、Red Hat のインクルードファイルのみに短縮しています。 Ansible が起動した後、オペレーティングシステムの情報がスクリプトで決定を行うために使用できる _ansible\_os\_family_ などの _ansible_* _変数に保持されていることがわかります。 **./testserver/roles/hs\_server\_common/tasks/configure-healthshare2015.yml** これは、Caché をインストールするためのメインスクリプトです。 スクリプトを見ると、次のようなターゲット上のタスクの論理ワークフローがわかります。 * オペレーティングシステムのユーザーとグループを作成する。 * コントローラーのマニフェストフォルダーからインストールファイルをコピーする。 * インストールファイルを解凍する。 * サイレントインストールを使用して Caché をインストールする(以下の注意書きを参照)。 * Caché キーファイルをコピーする。 * デフォルト Caché インスタンスを設定する。 * Apache を再起動する。 * Caché を再起動する。 Caché のサイレントインストールには、次のようないくつかのオプションがあります。 * parameters.is ファイルを使用する。 テンプレートの .isc ファイルは以前のインストールによって作成されたもので、そのまま使用することも、変更して使用することもできます。 * 環境で設定されたキーと値のペアで cinstall_silent を使用する。 * %installer クラスを使用する。 この例では、install\_silent を使用することを選択しましたが、Ansible でテンプレートファイルを使用する方法を示すため、パラメーターファイルを使用する代替方法もコメントに含めています(./roles/hs\_server\_common/templates/parameters\_hs20152_rh64.isc を参照してください)。 後の投稿では、Caché をインストールする際とデータベースおよびネームスペースを設定する際に %installer クラスを使用する方法を説明します。 インストールオプションの詳細は、Caché のオンラインドキュメントで確認できます。また、コミュニティには %installer クラスの使用に関する素晴らしい投稿もあります。 パラメーターファイルは、旧バージョンの Caché で Caché 組み込みの Apache バージョン以外の Web サーバで CSP Gateway を使用するように Cachéをインストールして構成する場合に役立ちます。 この機能は、Caché 2016.1の %installer で使用できます。 **./testserver/roles/hs\_server\_common/tasks/setup_RedHat.yml** この例は、システム固有の変数(ansible_*)の使用方法とオペレーティングシステム変数の設定を説明するためのものです。 **./testserver/roles/hs\_server\_common/vars/*** 変数ファイルのキーと値のペアには変数が含まれています。ご覧のとおり、さまざまな環境や状況で同じスクリプトを再利用することができます。 ## Caché インストールの実行 この例では、システムが使用可能であり、次のように設定されていることを想定しています。 1. コントローラーに Ansible がインストールされており、次のディレクトリに GitHub からファイルと構造が読み込まれていること。 * **./testserver/*** :  インベントリ、.yml ファイルなどを含むディレクトリツリー。 次のディレクトリを含みます。 * **./testserver/Distribution_Files/Cache** :  (Caché ディストリビューションと cache.key を含むマニフェスト)。 2. ターゲットマシンに Red Hat と Apache がインストールされていること。 テスト環境に合わせてインストールをカスタマイズするには、次のファイルを編集する必要があります。 1. **inventory_test** テストサーバー名または IP アドレスを編集する必要があります。 2. **./testserver/roles/hs\_server\_common/vars/healthshare2015.yml** テスト環境に合わせてパスを編集する必要があります。 ターゲットサーバーの次のパスを確認します。 * **common\_install\_base_path**: マニフェストファイルがコピーされ、解凍され、Caché インストールが実行されるパスです。 * **ISC\_PACKAGE\_INSTALLDIR**: Caché のインストールディレクトリです。 ディレクトリパスが存在しない場合は、ターゲットサーバー上に作成されます。 注意: 自動デプロイの機能の一つに、複数サーバーの並行構築があります。 インベントリファイルに複数のサーバーがある場合、各ステップが各ターゲットサーバーで同時に完了した後、次のステップがグループ内の各サーバーで開始します。 いずれかのサーバーでステップが失敗した場合、スクリプトは停止します。 また、問題解決に役立つエラーメッセージが表示されます。 エラーを解消したら、再び最初から実行するだけです。これは、冪等性が保たれるように設計されているというスクリプトの重要な特徴です。 冪等性とは、あるモジュールが例えばファイルコピーを目的としてステップ内で実行される際、ファイルがすでに存在した場合はそのステップが再実行されず、スクリプトが次のステップに進むことを意味します。 コピーなどのモジュールにはコピーを強制するように設定できるパラメーターがありますが、これはデフォルトではありません。 スクリプトを詳しく調べると、"creates" パラメータがいくつかのケースで使用されていることがわかります。次に例を示します。 - name: unattended install of hs using cinstall_silent shell: > ISC_PACKAGE_INSTANCENAME="{{ ISC_PACKAGE_INSTANCENAME }}" ISC_PACKAGE_INSTALLDIR="{{ ISC_PACKAGE_INSTALLDIR }}" ISC_PACKAGE_UNICODE="{{ ISC_PACKAGE_UNICODE }}" ISC_PACKAGE_INITIAL_SECURITY="{{ ISC_PACKAGE_INITIAL_SECURITY }}" ISC_PACKAGE_MGRUSER="{{ ISC_PACKAGE_MGRUSER }}" ISC_PACKAGE_MGRGROUP="{{ ISC_PACKAGE_MGRGROUP }}" ISC_PACKAGE_USER_PASSWORD="{{ ISC_PACKAGE_USER_PASSWORD }}" ISC_PACKAGE_CACHEUSER="{{ ISC_PACKAGE_CACHEUSER }}" ISC_PACKAGE_CACHEGROUP="{{ ISC_PACKAGE_CACHEGROUP }}" ./cinstall_silent chdir="{{ common_install_base_path }}/{{ hs_install_unpack_path }}" args: creates: "{{ ISC_PACKAGE_INSTALLDIR }}/cinstall.log" 上の節は creates パラメータを使用し、このアクションが cinstall.log ファイルを作成することを Ansible モジュール(この場合は shell モジュール)に知らせています。 モジュールがこのファイルを検出した場合(Caché はすでにインストールされている状態)、このステップは実行されません。 すべての設定が完了したら、インストールを実行できます。 $ ansible-playbook dbserver.yml PLAY [dbservers] ************************************************************** GATHERING FACTS *************************************************************** ok: [db1] TASK: [hs_server_common | include_vars healthshare2015.yml] ******************* ok: [db1] TASK: [hs_server_common | include_vars os-RedHat.yml] ************************* ok: [db1] etc etc etc TASK: [hs_server_common | Create default cache group] ************************* changed: [db1] TASK: [hs_server_common | Create default cache manager group] ***************** changed: [db1] TASK: [hs_server_common | Create default cache user] ************************** changed: [db1] TASK: [hs_server_common | Create default cache system users] ****************** changed: [db1] TASK: [hs_server_common | Create full hs install temp directory] ************** changed: [db1] TASK: [hs_server_common | Check tar file (gunzipped already) does not exist] *** ok: [db1] TASK: [hs_server_common | Copy healthshare install file] ********************** changed: [db1] TASK: [hs_server_common | un zip hs folder] *********************************** changed: [db1] TASK: [hs_server_common | un tar hs install] ********************************** changed: [db1] TASK: [hs_server_common | Create hs install directory] ************************ changed: [db1] TASK: [hs_server_common | touch ztrak.conf.] ********************************** changed: [db1] TASK: [hs_server_common | Process parameters file] **************************** changed: [db1] TASK: [hs_server_common | unattended install of hs using cinstall_silent] ***** changed: [db1] TASK: [hs_server_common | copy hs key] **************************************** changed: [db1] TASK: [hs_server_common | Set default hs instance] **************************** changed: [db1] TASK: [hs_server_common | restart apache to initialize CSP.ini file] ********** changed: [db1] NOTIFIED: [hs_server_common | restart healthshare] **************************** changed: [db1] PLAY RECAP ******************************************************************** db1 : ok=32 changed=21 unreachable=0 failed=0 ターゲットサーバーを見ると、db サーバーの Caché が稼働しています。 $ ccontrol list Configuration 'H2015' (default) directory: /test/hs2015 versionid: 2015.2.1.705.0 conf file: cache.cpf (SuperServer port = 1972, WebServer = 57772) status: running, since Wed Feb 17 15:59:11 2016 state: ok ## 最後に 次の投稿では、構成ファイルの編集や %installer クラスを使用したアプリケーションの構成など、他のタスクでスクリプトを構築します。 この投稿に興味を持ち、独自のデプロイを作成し始めた方は、ご質問やご提案がございましたらお気軽にお問い合わせください。 私は仮想化とパフォーマンスについて Global Summit で定期的に講演を行っています。今年の Global Summit に参加される予定の方は自己紹介をお願いします。Ansible やその他のシステムアーキテクチャに関する皆様のご経験をお聞かせください。
記事
Mihoko Iijima · 2022年10月23日

JP Core などのカスタムプロファイルを IRIS の FHIR リポジトリで検証する方法

FHIR開発者の皆さん、こんにちは! IRIS の FHIR リポジトリは、HL7 FHIR 標準プロファイルに対する検証をサポートしていますが、カスタムプロファイルに対する検証は、まだサポートできていません(将来のリリースバージョンで対応予定です)。 カスタムプロファイルの Search Parameter の追加はサポートしています!詳しくは、「動画:FHIR プロファイル」をご参照ください。 この記事では、IRIS の FHIR リポジトリに対して、カスタムプロファイルの検証を行う方法についてご紹介します。 方法としては、HL7 FHIR が提供している FHIR Validator で提供しているJARファイル(validator_cli.jar)を利用します。 利用のためには、FHIR リソースの検証のタイミングで、JARファイルの検証を実行するようにFHIRサーバサイドの動作をカスタマイズする必要があります。 ということで、大まかな準備は以下の通りです。 1) Java用外部サーバ(External Language Servers)の開始 IRISから FHIR Validator で提供しているJARファイル(validator_cli.jar)を利用するため、Java用外部サーバを開始します。 2) 検証ロジックを加工する FHIRリポジトリのデフォルトの動作を変更するため、FHIRサーバーサイドのカスタマイズクラス群を用意します(サンプルをご提供しています)。​​ イメージは以下の通りです。 今回は、FHIRリソースの検証を外部のJARファイルで行うため、FHIRサーバーサイドの加工(絵の青い破線の部分)を行いますが、この他にも、 「このリソースに〇〇というデータがあるときは登録させたくない」 「このリソースに△△のデータが含まれているときは応答として返したくない」 や、 患者さんのお名前を匿名化したい! のような実装を行う場合も、同様に、カスタマイズクラス群を作成します。 この記事では、外部の検証機能を使用するようにカスタマイズする流れをご紹介しますが、その他のカスタマイズ内容詳細については、以下記事をご参照ください。 FHIRリポジトリをカスタマイズしよう!パート1​ 「このリソースに〇〇というデータがあるときは登録させたくない」や「このリソースに△△のデータが含まれているときは応答として返したくない」のカスタマイズ例を紹介しています。 FHIRリポジトリをカスタマイズしよう!パート2(カスタムOperation編) 患者さんのお名前を匿名化するカスタムオペレーションの作成例を紹介しています。 デモをご覧いただく場合は、こちらのビデオをご覧ください。 それでは、具体的な手順をご紹介します。サンプルはこちら👉 https://github.com/Intersystems-jp/FHIRValidation-Test 事前準備:最初に、FHIR ValidatorのJARを利用して検証のテストを行います。 [1] JARのダウンロード validator_cli.jar を https://github.com/hapifhir/org.hl7.fhir.core/releases/latest/download/validator_cli.jar からダウンロードします。 サンプル一式をgit cloneまたはダウンロードいただいた場合は、lib にJARを配置してください。 もちろん、任意ディレクトリに配置することもできますが、JARの呼び出しをまとめたJavaサンプルコードのコンパイルなどを簡単に行っていただくため、compile.bat/compile.sh をご用意しています。 この bat/sh の中でJARの場所を指定してるので、サンプルをそのままご利用いただく場合は、lib 以下に配置してください。 [2] JDKのバージョンの確認 JDKのバージョンについては、https://confluence.hl7.org/pages/viewpage.action?pageId=35718580#UsingtheFHIRValidator-JDKVersionをご確認ください。 例では、OpenJDK11をインストールして利用しています。IRISがサポートするJDKについては、こちらをご参照ください。(8と11をサポートしています) [3]-1 JARの呼び出しをまとめたJavaファイルの準備(Windows) 1. 事前準備 環境変数 JAVA_HOMEにJavaインストールディレクトリを設定します。 2. JavaValidatorFacade.java をコンパイルする .\compile.bat 3. Jarファイルを作る .\updateJar.bat [3]-2 JARの呼び出しをまとめたJavaファイルの準備(Linux) 1. JavaValidatorFacade.java をコンパイルする ./compile.sh 2. Jarファイルを作る ./updateJar.sh [4]検証のテスト 検証初回実行時、標準プロファイルのパッケージがない場合、https://confluence.hl7.org/display/FHIR/FHIR+Package+Cache からスキーマをダウンロードするため初回のみ時間がかかります。 Windows の場合、以下ディレクトリに保存されます(通常のユーザの場合)。 C:\Users<username>.fhir Linuxの場合、以下ディレクトリに保存されます。 ~/.fhir サンプルファイル(test_Patient.json)を利用してテストを行います。 実行例).\run.bat または ./run.sh <プロファイルのJSONがあるディレクトリ> <テストするリソースのJSON> 《メモ》run.bat/run.sh の第1引数はカスタムプロファイル用ファイルを配置する予定のディレクトリを指定します(空でも実行できます)。例では、fhirprofileを指定しています。 以下、Windowsでの実行例です(WindowsとLinuxとで出力内容に違いはありません。)。 1つのワーニングが出ていますが、Success と返ります。 (ワーニングの理由:meta.profileに指定しているURLが実在しないため) PS C:\WorkSpace\FHIRValidation-Test> .\run.bat C:\WorkSpace\FHIRValidation-Test\fhirprofile\ C:\WorkSpace\FHIRValidation-Test\sample\test_Patient.json C:\WorkSpace\FHIRValidation-Test>"C:\Program Files\jdk-11\bin\java" -cp lib\validator_cli.jar;lib\JavaValidatorFacade.jar ISJSample.JavaValidatorFacade C:\WorkSpace\FHIRValidation-Test\fhirprofile\ C:\WorkSpace\FHIRValidation-Test\sample\test_Patient.json Load hl7.terminology.r4#4.0.0 - 4164 resources (00:04.784) Load C:\WorkSpace\FHIRValidation-Test\fhirprofile - 0 resources (00:00.005) Validate C:\WorkSpace\FHIRValidation-Test\sample\test_Patient.json Validate Patient against http://hl7.org/fhir/StructureDefinition/Patient..........20..........40..........60..........80.........| 00:00.485 Success: 0 errors, 1 warnings, 1 notes { "resourceType" : "OperationOutcome", "text" : { "status" : "extensions", "div" : "<div xmlns=\"http://www.w3.org/1999/xhtml\"><table class=\"grid\"><tr><td><b>Severity</b></td><td><b>Location</b></td><td><b>Code</b></td><td><b>Details</b></td><td><b>Diagnostics</b></td><td><b>Source</b></td></tr><tr><td>INFORMATION</td><td/><td>Structural Issue</td><td>Canonical URL 'http://intersystems.com/fhir/ISJSample/StructureDefinition/JP_Patient' does not resolve</td><td/><td>No display for Extension</td></tr><tr><td>WARNING</td><td/><td>Structural Issue</td><td>Profile reference 'http://intersystems.com/fhir/ISJSample/StructureDefinition/JP_Patient' has not been checked because it is unknown, and the validator is set to not fetch unknown profiles</td><td/><td>No display for Extension</td></tr></table></div>" }, "extension" : [{ "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-file", "valueString" : "C:\\WorkSpace\\FHIRValidation-Test\\sample\\test_Patient.json" }], "issue" : [{ "extension" : [{ "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line", "valueInteger" : 6 }, { "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col", "valueInteger" : 8 }, { "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-source", "valueCode" : "InstanceValidator" }, { "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id", "valueCode" : "TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE" }], "severity" : "information", "code" : "structure", "details" : { "text" : "Canonical URL 'http://intersystems.com/fhir/ISJSample/StructureDefinition/JP_Patient' does not resolve" }, "expression" : ["Patient.meta.profile[0]"] }, { "extension" : [{ "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line", "valueInteger" : 1 }, { "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col", "valueInteger" : 2 }, { "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-source", "valueCode" : "InstanceValidator" }, { "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id", "valueCode" : "VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY" }], "severity" : "warning", "code" : "structure", "details" : { "text" : "Profile reference 'http://intersystems.com/fhir/ISJSample/StructureDefinition/JP_Patient' has not been checked because it is unknown, and the validator is set to not fetch unknown profiles" }, "expression" : ["Patient.meta.profile[0]"] }] } 続いて、test_Patient.json 15行目の gender に不正な値(例では"Man")を設定し、検証エラーが発生するか確認します。 今度は FAILUREと表示されます。(先ほどと同じワーニングに対するメッセージは省いています。) PS C:\WorkSpace\FHIRValidation-Test> .\run.bat C:\WorkSpace\FHIRValidation-Test\fhirprofile\ C:\WorkSpace\FHIRValidation-Test\sample\test_Patient.json C:\WorkSpace\FHIRValidation-Test>"C:\Program Files\jdk-11\bin\java" -cp lib\validator_cli.jar;lib\JavaValidatorFacade.jar ISJSample.JavaValidatorFacade C:\WorkSpace\FHIRValidation-Test\fhirprofile\ C:\WorkSpace\FHIRValidation-Test\sample\test_Patient.json Load C:\WorkSpace\FHIRValidation-Test\fhirprofile2 - 0 resources (00:00.004) Validate C:\WorkSpace\FHIRValidation-Test\sample\test_Patient.json Validate Patient against http://hl7.org/fhir/StructureDefinition/Patient..........20..........40..........60..........80.........| 00:00.428 *FAILURE*: 1 errors, 1 warnings, 1 notes { "resourceType" : "OperationOutcome", "text" : { "status" : "extensions", "div" : "<div xmlns=\"http://www.w3.org/1999/xhtml\"><table class=\"grid\"><tr><td><b>Severity</b></td><td><b>Location</b></td><td><b>Code</b></td><td><b>Details</b></td><td><b>Diagnostics</b></td><td><b>Source</b></td></tr><tr><td>ERROR</td><td/><td>Invalid Code</td><td>The value provided ('man') is not in the value set 'AdministrativeGender' (http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1), and a code is required from this value set) (error message = Unknown Code http://hl7.org/fhir/administrative-gender#man in http://hl7.org/fhir/administrative-gender)</td><td/><td>No display for Extension</td></tr><tr><td>INFORMATION</td><td/><td>Structural Issue</td><td>Canonical URL 'http://intersystems.com/fhir/ISJSample/StructureDefinition/JP_Patient' does not resolve</td><td/><td>No display for Extension</td></tr><tr><td>WARNING</td><td/><td>Structural Issue</td><td>Profile reference 'http://intersystems.com/fhir/ISJSample/StructureDefinition/JP_Patient' has not been checked because it is unknown, and the validator is set to not fetch unknown profiles</td><td/><td>No display for Extension</td></tr></table></div>" }, "extension" : [{ "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-file", "valueString" : "C:\\WorkSpace\\FHIRValidation-Test\\sample\\test_Patient.json" }], "issue" : [{ "extension" : [{ "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line", "valueInteger" : 15 }, { "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col", "valueInteger" : 21 }, { "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-source", "valueCode" : "TerminologyEngine" }, { "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id", "valueCode" : "Terminology_TX_NoValid_16" }], "severity" : "error", "code" : "code-invalid", "details" : { "text" : "The value provided ('man') is not in the value set 'AdministrativeGender' (http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1), and a code is required from this value set) (error message = Unknown Code http://hl7.org/fhir/administrative-gender#man in http://hl7.org/fhir/administrative-gender)" }, "expression" : ["Patient.gender"] }, ここまでで、 FHIR Validator から提供されているJARを利用した検証のテストは完了です。 ここからは、IRIS の FHIRサーバーサイドのカスタマイズを行っていきます。 <カスタマイズ概要> 1) Java用外部サーバ(External Language Servers)の開始 2) 検証ロジックを加工する まずは、1) Java用外部サーバ(External Language Servers)の開始 を行うための手順をご紹介します。 [5] ISJSample.JavaValidatorFacade クラスを実行するためJava用外部サーバを起動 実行環境に合わせて設定を追加します。 管理ポータル > システム管理 > 構成 > 接続性 > External Lanaugae Server > %Java Server 設定項目 値 クラスパス validator_cli.jarとJavaValidatorFacade.jarのパス Java Home Directory Javaのインストールディレクトリ (図例はWindowsでの例です) 設定が終わったら start のリンクをクリックします。以下のようなログが表示されます。 Start External Language Server %Java Server: お待ちください...結果が以下に表示されます: 2022-10-07 19:48:59 Starting Java Gateway Server '%Java Server' 2022-10-07 19:48:59 Executing O.S. command: C:\Program Files\jdk-11\bin\java.exe -Xrs -Djava.system.class.loader=com.intersystems.gateway.ClassLoader -classpath C:\WorkSpace\FHIRValidation-Test\lib\validator_cli.jar;C:\WorkSpace\FHIRValidation-Test\lib\JavaValidatorFacade.jar;C:\InterSystems\IRISHealth\dev\java\lib\1.8\intersystems-jdbc-3.3.1.jar com.intersystems.gateway.JavaGateway 53272 "" "JP7430MIIJIMA:IRISHEALTH:%Java Server" 127.0.0.1 "" 2022-10-07 19:49:10 Execution returned: '' 2022-10-07 19:49:10 Gateway Server start-up confirmation skipped 2022-10-07 19:49:10 Starting background process to monitor the Gateway Server 2022-10-07 19:49:10 Job command successful, started monitor process '26376' ここからは、2) 検証ロジックを加工する を行うための手順をご紹介します。 [6]FHIRリポジトリのカスタマイズ用クラスの準備 以下説明は、IRIS for Health 2022.1を利用した例で記載しています。 詳細はドキュメント:FHIRサーバのカスタマイズ もご参照ください。 !!FHIRリポジトリを作成する前に、ネームスペースにカスタマイズ用クラス群を準備する必要があります!! サンプルでは、以下の説明に登場するクラス群をISJSample以下に用意しています。インポート方法は 「5.インポート」をご参照ください。 1. クラス一覧 それぞれのクラスの関係は以下のイメージです。 2.作成したクラスにパラメータを設定 最初から作成される場合は、以下のクラスパラメータを設定してください。サンプルをそのままご利用いただく場合は修正不要です。 1で作成したクラスにパラメータを設定します。 ISJSample.FHIRValidationInteractions クラス ResourceValidatorClassに検証実行用カスタムクラス名(ISJSample.FHIRResourceValidator)を指定 BatchHandlerClassにBundleのtype=batchまたはtransactionを処理するカスタムクラス名(ISJSample.FHIRBundleProcessor)を指定 ISJSample.FHIRValidationInteractionsStrategy クラス StrategyKeyにInteractionsStrategyの一意の識別子を指定 InteractionsClassにInteractionsのカスタムクラス名(ISJSample.FHIRValidationInteractions)を指定 ISJSample.FHIRBundleProcessor クラス BundleValidatorClassにBundle検証用カスタムクラス名(ISJSample.FHIRBundleValidator)を指定 ISJSample.FHIRValidationRepoManager クラス StrategyClassに InteractionStrategyのカスタムクラス名(ISJSample.FHIRValidationInteractionsStrategy)を指定 StrategyKeyにInteratcionsStrategyで指定したStrategyKeyの値を指定 《補足》 この他、ISJSample.FHIRValidationInteractionsクラスとISJSample.FHIRResourceValidatorクラスでメソッドをオーバーライドしています。 ISJSample.FHIRValidationInteractionsクラスでは、Bundleに含まれるリソースに対して検証が2回呼び出されないように StartTransactionBundle()メソッドとEndTransactionBundle()メソッドのオーバーライドしています。 %inTransactionFlag変数の設定を追加 Method StartTransactionBundle(pBundleTransactionId As %Integer) { do ##super(pBundleTransactionId) set %inTransactionFlag=$$$YES } Method EndTransactionBundle() { kill %inTransactionFlag do ##super() } ISJSample.FHIRResourceValidatorクラスでは、ValidateResource()をオーバーライドしています。 %inTransactionFlgが$$$YES ではないとき、検証を実行するように変更 Method ValidateResource(pResourceObject As %DynamicObject) { //do ##super(pResourceObject) if $get(%inTransactionFlag,$$$NO)'=$$$YES { do ##class(ISJSample.FHIRValidation).validate(pResourceObject) } } ISJSample.FHIRBundleValidator クラスでは、スーパークラスのValidateBundle()をオーバーライドし、ISJSample.FHIRValidationクラスのvalidate()を呼び出しています。 ClassMethod ValidateBundle(pResourceObject As %Library.DynamicObject, pFHIRVersion As %String) { //do ##super(pResourceObject,pFHIRVersion) do ##class(ISJSample.FHIRValidation).validate(pResourceObject) } 3. カスタム検証用クラス(ISJSample.FHIRResourceValidator)の修正 最初から作成される場合は、以下のクラスパラメータを設定してください。サンプルをそのままご利用いただく場合は修正不要です。 スーパークラスのValidateResource()をオーバーライドし、ISJSample.FHIRValidationクラスのvalidate()を呼びしています。 4.カスタム検証用クラスが使用するパラメータ値の設定 管理ポータル > Health > 作成したネームスペース > Configuration Registry に移動し、以下のKeyとValueを設定します。 項目名(Key) 設定例(Value) 意味 /FHIR/Validation/SkipIfNoProfile 1 設定値 1:検証をスキップする(リソースのmeta.profileの値が空や設定されていない場合に、検証をスキップする設定です。) 設定値 0:検証をスキップしない /FHIR/Validation/JavaGatewayServer 127.0.0.1 Java用外部サーバのIPアドレスまたはサーバ名 /FHIR/Validation/JavaGatewayPort 53272 Java用外部サーバが使用するポート番号 /FHIR/Validation/ProfileLocation C:\WorkSpace\FHIRValidation-Test\fhirprofile カスタムプロファイルのディレクトリを指定(フルパス) /FHIR/Validation/TerminologyServer (設定なし) TerminologyサーバのURLを指定 5.サンプルクラスのインポート VSCodeをお使いの方は、対象ネームスペースに接続後、ISJSampleディレクトリ を右クリックし[Import and Compile]をクリックします(または、クラス定義を Ctrl + S で保存することでインポートできます)。 スタジオをお使いの方は、対象ネームスペースに接続し、ISJSampleディレクトリ 以下 *.clsを複数選択し、スタジオのエリアにドラッグ&ドロップすることでインポートできます。 管理ポータルからインポートする場合は、管理ポータル > システムエクスプローラー > クラス > 対象ネームスペース選択 > [インポート]ボタンクリック で表示されるインポート画面でISJSampleディレクトリ を選択し、インポートを実行します。 詳細は図例をご参照ください。 [7]FHIRリポジトリの作成 InteratcionsStrategyクラスのカスタムクラスがコンパイル済であることを確認し、以下GUIで作成します。 管理ポータル > Health > 作成したネームスペース > FHIR Configuration > Server Configuration > Add Endpoint !!Interactions sterategy classに作成したクラス名: ISJSample.FHIRValidationInteractionsStrategyを指定してからリポジトリを作成して下さい!! リポジトリ作成後、デバッグモードから認証なし設定します(Debuggingの「Allow Unauthenticated Access」にチェック) [8]テスト実行 Postmanで以下実行します。 通常のPOSTでテストしてもいいのですが、何度も同じリソースを使って検証のテストを行うような場合には、リソースの検証だけを行う $validate オペレーションの利用が便利です。 例では、URL末尾に /$validate をつけています。この指定があるだけとリソースの検証のみを行うことができます(/$validate をなくして実行すると通常のPOST要求の流れで検証が行われます)。 設定項目 値 URL <エンドポイント>/Patient/$validate Method POST Header Content-Type に application/json+fhir;charset=utf-8 Body PatientリソースのJSON 結果は以下の通りです。 { "resourceType": "OperationOutcome", "issue": [ { "severity": "information", "code": "informational", "diagnostics": "All OK", "details": { "text": "All OK" } } ] } Bundleの場合は以下の通りです。 設定項目 値 URL <エンドポイント>/Bundle/$validate Method POST Header Content-Type に application/json+fhir;charset=utf-8 Body Bundle用JSON ここまでで、手続きは完了です。 次は、いよいよ、カスタムプロファイルの検証を実行してみます! [9]JPCoreのプロファイルを適用する方法 1.JPCoreのアーティファクトを用意する 公開されているJPCoreのプロファイル FHIR JP Core 実装ガイドパッケージをダウンロードされるか、公式Git を clone して、SUSHIを利用してImplementation Guide(IG)などのJSONのファイル群(アーティファクト)を作成する方法があります。 参照元:HL7FHIR JP Core実装ガイド<Draft Ver.1>の公開について(ご案内) 以下例は、2022年10月24日にダウンロードしたFHIR JP Core 実装ガイド V1.1.0 のパッケージを利用して記述しています。 ※2022年11月5日にダウンロードした v1.1.1のパッケージでも以下の例が動作することを確認しています。 2. 1.で用意したアーティファクトをプロファイル配置用ディレクトリにコピーして検証を実行する テストする場合は、run.bat または run.sh の第1引数にアーティファクトがあるディレクトリを指定します。 例では、 FHIR JP Core 実装ガイド にある V1.1のtgz版をダウンロードして展開したディレクトリにあるJSONファイルを fhirprofile以下にコピーした状態で実行しています。 以下、Windowsでの実行例です(WindowsとLinuxとで出力内容に違いはありません。)。 PS C:\WorkSpace\FHIRValidation-Test> .\run.bat C:\WorkSpace\FHIRValidation-Test\fhirprofile C:\WorkSpace\FHIRValidation-Test\sample\test_Patient-JPCore.json C:\WorkSpace\FHIRValidation-Test>"C:\Program Files\jdk-11\bin\java" -cp lib\validator_cli.jar;lib\JavaValidatorFacade.jar ISJSample.JavaValidatorFacade C:\WorkSpace\FHIRValidation-Test\fhirprofile C:\WorkSpace\FHIRValidation-Test\sample\test_Patient-JPCore.json Load hl7.terminology.r4#4.0.0 - 4164 resources (00:04.947) Load C:\WorkSpace\FHIRValidation-Test\fhirprofile - 241 resources (00:00.465) Unable to generate snapshot for http://jpfhir.jp/fhir/core/StructureDefinition/JP_MedicationRequest: Attempt to use a snapshot on profile 'http://jpfhir.jp/fhir/core/StructureDefinition/JP_MedicationRequestBase' as Base for generating a snapshot for the profile http://jpfhir.jp/fhir/core/StructureDefinition/JP_MedicationRequest before it is generated Unable to generate snapshot for http://jpfhir.jp/fhir/core/StructureDefinition/JP_MedicationRequest_Injection: Attempt to use a snapshot on profile 'http://jpfhir.jp/fhir/core/StructureDefinition/JP_MedicationRequestBase' as Base for generating a snapshot for the profile http://jpfhir.jp/fhir/core/StructureDefinition/JP_MedicationRequest_Injection before it is generated Unable to generate snapshot for http://jpfhir.jp/fhir/core/StructureDefinition/JP_MedicationRequest: Profile JP_MedicationRequest (http://jpfhir.jp/fhir/core/StructureDefinition/JP_MedicationRequest), element null. Error generating snapshot: Error sorting Differential: The element MedicationRequest.dosageInstruction is out of order (and maybe others after it) Unable to generate snapshot for http://jpfhir.jp/fhir/core/StructureDefinition/JP_MedicationRequest_Injection: Profile JP_MedicationRequest_Injection (http://jpfhir.jp/fhir/core/StructureDefinition/JP_MedicationRequest_Injection), element null. Error generating snapshot: Error sorting Differential: The element MedicationRequest.dosageInstruction is out of order (and maybe others after it) Validate C:\WorkSpace\FHIRValidation-Test\sample\test_Patient-JPCore.json Validate Patient against http://hl7.org/fhir/StructureDefinition/Patient..........20..........40..........60..........80.........| Validate Patient against http://jpfhir.jp/fhir/core/StructureDefinition/JP_Patient..........20..........40..........60..........80..........100| 00:00.641 Success: 0 errors, 0 warnings, 1 notes { "resourceType" : "OperationOutcome", "text" : { "status" : "extensions", "div" : "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>All OK</p><table class=\"grid\"><tr><td><b>Severity</b></td><td><b>Location</b></td><td><b>Code</b></td><td><b>Details</b></td><td><b>Diagnostics</b></td></tr><tr><td>INFORMATION</td><td/><td>Informational Note</td><td>All OK</td><td/></tr></table></div>" }, "extension" : [{ "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-file", "valueString" : "C:\\WorkSpace\\FHIRValidation-Test\\sample\\test_Patient-JPCore.json" }], "issue" : [{ "severity" : "information", "code" : "informational", "details" : { "text" : "All OK" } }] } 実行に指定しているtest_Patient-JPCore.jsonには、JPCoreが定義しているextension.Race(患者の人種)の情報が含まれます。 例) "birthDate": "1970-01-01", "extension":[ { "url": "http://jpfhir.jp/fhir/core/Extension/StructureDefinition/JP_Patient_Race", "valueCodeableConcept": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v3-Race", "code": "2039-6", "display": "Japanese" } ] } } ], 3. IRISから実行する。 実行前にカスタムプロファイルのアーティファクトがあるディレクトリが正確に設定されているか確認します。 IRISの管理ポータル > Health > ネームスペース選択 > Configuration Registry > /FHIR/Validation/ProfileLocation Valueを確認 《メモ》Configuration Registry の Key には、ISJSample.FHIRValidationクラスに定義されているパラメータ名を設定しています。 Value の取得は以下ユーティリティクラスのメソッドを実行することで取得できます。 ##class(HS.Registry.Config).GetKeyValue(..#ProfileLocationKEY) 設定値が管理ポータルで変更できるので、プロファイルを参照する場所を変える場合は、/FHIR/Validation/ProfileLocation を変更するだけでOKです。 以下、Postmanからのテスト例です。 設定項目 値 URL <エンドポイント>/Patient/$validate Method POST Header Content-Type に application/json+fhir;charset=utf-8 Body JPCore対応用PatientリソースのJSON 検証がOKであることを確認したら、JPCoreのプロファイルを正しく認識しているかどうかテストするため、患者の人種(Race)の情報に誤ったデータタイプの情報を設定し検証します。 RaceのデータタイプはExtension(CodeableConcept)が指定されているため、valueCodeableConceptにCodeableConceptタイプの情報を持つ必要がありますが、以下の例では、valueStringにstringの情報を設定しています。 "extension":[ { "url": "http://jpfhir.jp/fhir/core/Extension/StructureDefinition/JP_Patient_Race", "valueString":"日本人" } ], 以下、Postmanからのテスト例です。 設定項目 値 URL <エンドポイント>/Patient/$validate Method POST Header Content-Type に application/json+fhir;charset=utf-8 Body JPCore対応用PatientリソースのJSON:検証失敗用 例と同様に、JPCore V1.1のtgz版プロファイルを利用している場合のPatientリソース内タイプの確認については、https://jpfhir.jp/fhir/core/V1B/StructureDefinition-jp-patient.html をご参照ください。 RESTクライアントからの実行結果は以下の通りです。(CodeableConceptのタイプではなくstringの情報が来ていることでエラーが発生しています) { "resourceType": "OperationOutcome", "issue": [ { "severity": "error", "code": "structure", "diagnostics": "5001", "details": { "text": "This element does not match any known slice defined in the profile http://jpfhir.jp/fhir/core/Extension/StructureDefinition/JP_Patient_Race and slicing is CLOSED: Patient.extension[0].value.ofType(string): Does not match slice 'valueCodeableConcept' (discriminator: ($this is CodeableConcept))" }, "expression": [ "Patient.extension[0].value.ofType(string)" ] }, { "severity": "error", "code": "structure", "diagnostics": "5001", "details": { "text": "The Extension 'http://jpfhir.jp/fhir/core/Extension/StructureDefinition/JP_Patient_Race' definition allows for the types [CodeableConcept] but found type string" }, "expression": [ "Patient.extension[0]" ] } ] } 以上です! 最後までお読みいただきありがとうございました 今回の記事は、JP Coreのカスタムプロファイルの適用でご紹介しましたが、カスタムプロファイルの作成からコンパイルまでの手続きなどについては、以下の記事でご紹介しています。 FHIRプロファイル作成ツールであるSUSHIの握り方使い方を紹介している記事 👉 SUSHIを使ってFHIRプロファイルを作成しようパート1 Extensionの追加と、Extensionで追加した項目に対するSearchParameterの定義を行い、IRISのFHIRリポジトリに適用し、新しいSearchParameterが利用できるまでの流れをご紹介している記事 👉 SUSHIを使ってFHIRプロファイルを作成しようパート2 ぜひご覧ください!