クリアフィルター
記事
Toshihiko Minamoto · 2024年3月25日
バージョン 2023.3(InterSystems IRIS for Health)の新機能は、FHIR プロファイル基準の検証を実行する機能です。
(*)
この記事では、この機能の基本的な概要を説明します。
FHIR が重要な場合は、この新機能を絶対にお試しになることをお勧めします。このままお読みください。
背景情報
FHIR 規格は、$validate という演算を定義します。 この演算は、リソースを検証する API を提供することを意図しています。
FHIR Validation に関する概要を理解するには、こちらの FHIR ドキュメントをご覧ください。
また、Global Summit 2023 での私の「Performing Advanced FHIR Validation」セッションもご覧ください。最初の方で、様々な種類の検証に関する情報を提供しています。
この検証の一部は、特定のプロファイルに対する検証です。 プロファイリングについてはこちらをご覧ください。
簡単な例として、Patient Resource の基本的な FHIR 定義では、識別子の基数を '0..*' として定義します。つまり、Patient には識別子が何もなく(ゼロ)、それでも有効であるということです。 しかし、US Core Patient Profile は、'1..*' の帰趨を定義しています。つまり、Patient に識別子がない場合、有効ではないということになります。
別の例では、上記の US Core Patient の例に従うと、race や birthsex などの拡張が使用されることがあります。
FHIR Profiling についての詳細は、Global Summit 2022 で @Patrick.Jamieson3621 が講演した 「Using FHIR Shorthand」セッションをご覧ください。Pat が FSH(FHIR の略)の使用について説明していますが、Profiling の一般的なトピックの説明から始まっています。
以前のバージョンでは、FHIR Server はこの種(プロファイルベース)の検証をサポートしていませんでしたが、最新バージョン(2023.3)からはサポートされています。
使用方法
ドキュメントには、プロファイルベースの検証に $validate を呼び出す方法についてのセクションが含まれています。
$validate 演算を呼び出す基本的な方法には 2 つあります。
クエリ URL でのプロファイリング
1 つ目は、Request Body の Resource と URL パラメーターとしてのプロファイルを含めた POST 送信です。
たとえば、Postman では:
または curl を使用(プロファイル URL パラメーターの値のスラッシュのエンコーディングに注意してください。Postman によって処理されます):
curl --location 'http://fhirserver/endpoint/Patient/$validate?profile=http%3A%2F%2Fhl7.org%2Ffhir%2Fus%2Fcore%2FStructureDefinition%2Fus-core-patient' --header 'Content-Type: application/fhir+json' --header 'Accept: application/fhir+json' --header 'Authorization: Basic U3VwZXJVc2VyOnN5cw==' --data "@data.json"
上記で参照される data.json には、例としてこの有効な US Core Patient が含まれています。
{
"resourceType" : "Patient",
"id" : "example",
"meta" : {
"profile" : ["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient|7.0.0-ballot"]
},
"text" : {
"status" : "generated",
"div" : "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p style=\"border: 1px #661aff solid; background-color: #e6e6ff; padding: 10px;\"></div>"
},
"extension" : [{
"extension" : [{
"url" : "ombCategory",
"valueCoding" : {
"system" : "urn:oid:2.16.840.1.113883.6.238",
"code" : "2106-3",
"display" : "White"
}
},
{
"url" : "ombCategory",
"valueCoding" : {
"system" : "urn:oid:2.16.840.1.113883.6.238",
"code" : "1002-5",
"display" : "American Indian or Alaska Native"
}
},
{
"url" : "ombCategory",
"valueCoding" : {
"system" : "urn:oid:2.16.840.1.113883.6.238",
"code" : "2028-9",
"display" : "Asian"
}
},
{
"url" : "detailed",
"valueCoding" : {
"system" : "urn:oid:2.16.840.1.113883.6.238",
"code" : "1586-7",
"display" : "Shoshone"
}
},
{
"url" : "detailed",
"valueCoding" : {
"system" : "urn:oid:2.16.840.1.113883.6.238",
"code" : "2036-2",
"display" : "Filipino"
}
},
{
"url" : "text",
"valueString" : "Mixed"
}],
"url" : "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"
},
{
"extension" : [{
"url" : "ombCategory",
"valueCoding" : {
"system" : "urn:oid:2.16.840.1.113883.6.238",
"code" : "2135-2",
"display" : "Hispanic or Latino"
}
},
{
"url" : "detailed",
"valueCoding" : {
"system" : "urn:oid:2.16.840.1.113883.6.238",
"code" : "2184-0",
"display" : "Dominican"
}
},
{
"url" : "detailed",
"valueCoding" : {
"system" : "urn:oid:2.16.840.1.113883.6.238",
"code" : "2148-5",
"display" : "Mexican"
}
},
{
"url" : "text",
"valueString" : "Hispanic or Latino"
}],
"url" : "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"
},
{
"url" : "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex",
"valueCode" : "F"
}
],
"identifier" : [{
"use" : "usual",
"type" : {
"coding" : [{
"system" : "http://terminology.hl7.org/CodeSystem/v2-0203",
"code" : "MR",
"display" : "Medical Record Number"
}],
"text" : "Medical Record Number"
},
"system" : "http://hospital.smarthealthit.org",
"value" : "1032702"
}],
"active" : true,
"name" : [{
"use" : "old",
"family" : "Shaw",
"given" : ["Amy",
"V."],
"period" : {
"start" : "2016-12-06",
"end" : "2020-07-22"
}
},
{
"family" : "Baxter",
"given" : ["Amy",
"V."],
"suffix" : ["PharmD"],
"period" : {
"start" : "2020-07-22"
}
}],
"telecom" : [{
"system" : "phone",
"value" : "555-555-5555",
"use" : "home"
},
{
"system" : "email",
"value" : "amy.shaw@example.com"
}],
"gender" : "female",
"birthDate" : "1987-02-20",
"address" : [{
"use" : "old",
"line" : ["49 MEADOW ST"],
"city" : "MOUNDS",
"state" : "OK",
"postalCode" : "74047",
"country" : "US",
"period" : {
"start" : "2016-12-06",
"end" : "2020-07-22"
}
},
{
"line" : ["183 MOUNTAIN VIEW ST"],
"city" : "MOUNDS",
"state" : "OK",
"postalCode" : "74048",
"country" : "US",
"period" : {
"start" : "2020-07-22"
}
}]
}
この演算のリソースは OperationOutcome Resource です。
Resource が有効(上記のとおり)である場合、この種のレスポンスが得られます。
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "informational",
"details": {
"text": "All OK"
}
}
]
}
ただし、例えば上記の Resource から識別子を省略すると、この OperationOutcome が得られます。
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "invariant",
"details": {
"text": "generated-us-core-patient-1: Constraint violation: identifier.count() >= 1 and identifier.all(system.exists() and value.exists())"
},
"diagnostics": "Caused by: [[expression: identifier.count() >= 1, result: false, location: Patient]]",
"expression": [
"Patient"
]
}
]
}
クエリ本文でのプロファイリング
データを $validate に送信するもう 1 つの方法は、Parameters 配列内のリソースをプロファイルやその他のオプションとともに指定して POST 送信することです。
Postman では、これは以下のようになります。
curl を使用した場合:
curl --location 'http://fhirserver/endpoint/Patient/$validate' --header 'Content-Type: application/fhir+json' --header 'Accept: application/fhir+json' --header 'Authorization: Basic U3VwZXJVc2VyOnN5cw==' --data "@data.json"
URL にはプロファイルは含まれませんが、ボディのペイロード(または上記の例の data.json)は以下のようになります。
{
"resourceType": "Parameters",
"parameter": [
{
"name": "mode",
"valueString": "profile"
},
{
"name": "profile",
"valueUri": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
},
{
"name": "resource",
"resource": {
"resourceType": "Patient"
}
}
]
}
実際の Patient Resource は前の例と同じであるため除外しました。
ただし、ここで異なるのは、mode パラメーター要素と profile パラメーター要素であり、リソースは「resource」という独自のパラメーター要素内に含まれています。
ID が URL のどこに含まれるかなど(たとえば、更新または削除を検証するため)、mode のその他のオプションについては、上記で参照したドキュメントをご覧ください。
便宜上、上記のサンプルリクエストなどの Postman コレクションを含む単純な Open Exchange パッケージを作成しました。
コードを使用
標準 REST API 経由で $validate 演算を呼び出す代わりに、ユースケースが適切であれば、内部的にクラスメソッドを呼び出すこともできます。
既存の HS.FHIRServer.Util.ResourceValidator:ValidateResource() メソッド(こちらのドキュメントでも説明)と同様に、ValidateAgainstProfile() という新しいメソッドも追加されており、それを利用できます。
範囲
現在(v2023.3)では、この種の検証(プロファイル基準の検証)は $validate 演算の一部としてのみ行われ、Resource の作成や更新時には行われないことに注意しておくことが重要です。 そこでは、より「基本的な」検証が行われます。 必要であれば、より「高度な」プロファイル基準の検証を使って、POST または PUT される前に Resouce を実行することができます。
別のオプションも、今後のバージョンで提供される可能性があります。
セットアップに関する注意事項
一般に、FHIR Server がほとんどの Profile Validator セットアップを処理します。
確認が必要なのは、サポートされている Java 11 JDK がインストールされていることです(現在、Oracle のものか OpenJDK のもの)。
詳細は、Profile Validation Server の構成に関するドキュメントをご覧ください。
基本的に、Validator JAR を実行するために、外部言語(Java)サーバーが実行中であることが確認されています(JAR ファイルはインストールフォルダの dev/fhir/java にあります。ちなみに、logs フォルダを覗くと、以下のような警告が表示されます:
CodeSystem-account-status.json : Expected: JsonArray but found: OBJECT for element: identifier
気にする必要はありません。 Validator はデフォルトで多数のプロファイルを読み込むものであり、その一部にはフォーマットエラーがあります)。
つまり、外部言語サーバーのリストを見ると、以下のようなものを確認できます。
Validator がプロファイルに対して検証する必要がある場合、初めてプロファイルをロードする必要があることに注意してください。そのため、パフォーマンスを向上させるために、HS.FHIRServer.Installer:InitalizeProfileValidator() メソッドを呼び出すことができます。
do ##class(HS.FHIRServer.Installer).InitializeProfileValidator()
これについても上記で参照したドキュメントには、Validator の構成に関して説明されています。
実際、この呼び出しをインスタンスの %ZSTART 起動ルーチンに含めることもできます。
また、これについても関連するクラスリファレンスで説明されています。
このメソッドは、検証操作中にプロファイルを読み込むことによるパフォーマンスへの影響がないように、インスタンスまたは外部言語サーバーの再起動後に呼び出すことが推奨されています。
今後の予定
今後のバージョンでは、Validator 内と Validator 周りにより多くの機能を提供する予定です。
ただし、例えば現時点でも、外部用語サーバーを使った検証(LOINC コードなど)を実行する場合、別のアプローチを使用することができます。1 つは、前述の Global Summit セッションで説明と実践が行われている方法で、同僚の @Dmitry.Zasypkin のサンプル(Open Exchange で提供)に基づくものです。
謝辞
この新機能を調べながらこの記事を準備するにあたって貴重な情報を提供してい頂いた @Kimberly.Santos にお礼申し上げます。
(*)Microsoft Bing の DALL-E 3 を使用して上記の画像を作成してくれた画像クリエーターに感謝しています。
記事
Toshihiko Minamoto · 2023年2月8日
InterSystems IRIS 2020.1 には、重要なアプリケーションの構築を支援する新機能と機能改善が多数盛り込まれています。 2019.1 から 2020.1 までに行われた多数の大幅なパフォーマンス改善のほかに、最近の SQL の歴史において最も大きな変更点の 1 つであるユニバーサルクエリキャッシュ(UQC)が導入されています。 この記事では、SQL ベースのアプリケーションに対するそのインパクトについて、技術的な観点で詳しく説明しています。
### UQC とは?
スクラブルで頭字語として使用されればゲームチェンジャーとなる可能性もあるでしょうが、ユニバーサルクエリキャッシュは、あらゆる種類の SQL ステートメントを単一のキャッシュで管理することで、ボード全体の SQL 操作を単純化することを目的としています。 これまでは、[動的 SQL](https://docs.intersystems.com/irislatest/csp/docbookj/Doc.View.cls?KEY=GSQL_dynsql)(%SQL.Statementインフラストラクチャを使用)や JDBC 、 ODBC にて発行されるステートメントは、Prepare 時に [キャッシュドクエリ](https://docs.intersystems.com/irislatest/csp/docbookj/Doc.View.cls?KEY=GSQLOPT_cachedqueries)として最適化されたコードにコンパイルされていました。 一方、[埋め込み SQL](https://docs.intersystems.com/irislatest/csp/docbookj/Doc.View.cls?KEY=GSQL_esql) を使用する場合(&SQL() 構文を使用)、クエリを実行するコードはインラインで生成され、アプリケーションの一部となっていました。 このため、埋め込み SQL は、IRIS がサポートする他のすべての形態の SQL クエリとは非常に異なって処理されていました。
キャッシュドクエリクラスは、ステートメントを発行したコードから独立しており、新しいインデックスの追加やアップグレードの後に、更新されたテーブルの統計に基づいてより効率的なアクセスパスを得るなどのために、必要に応じてパージしたり生成し直したりすることが可能です([アップグレード時に凍結されたクエリプラン](https://docs.intersystems.com/irislatest/csp/docbookj/Doc.View.cls?KEY=GSQLOPT_frozenplans#GSQLOPT_frozenplans_upgrade)の注意事項もご覧ください)。 一方、埋め込み SQL は静的であり、新しいテーブル統計とインデックスを自動的に利用することができませんでした。
2020.1 では、ユニバーサルクエリキャッシュの導入により、埋め込み SQL にもキャッシュ済みクエリクラスを生成できるようになり、このコードをアプリケーションロジックを保持するクラスに混在させることがなくなりました。 最も重要なメリットは、埋め込み SQL では、より新しいテーブル統計または新しいインデックスを利用するために、ソースクラスを再コンパイルする必要がなくなったことです。 UQC で参照されているテーブルが更新されると、新しいキャッシュ済みクエリクラスが自動的に生成されるため、生成可能な最も効率の良いコードが確実に実行されます。 埋め込み SQL をクラスに生成すると、他のすべてのクエリが既に使用しているより高速なカーソル変数ストレージを利用できます。 つまり、膨大な作業を行うクエリが以前よりも速く実行されるということです。 また、UQC によって、クラスのコンパイル順の管理において管理者を悩ませることの多かったクラス間の SQL 依存関係がすべて取り除かれます。
これによって作業が大きく変わります。多大な労力をつぎ込んで、「想像力に溢れる」セットアップに対応するために大規模なテストを行ってはいるものの(デプロイ済みのコード、アプリケーションコード内でのネームスペースの切り替えなど)、その想像を超えるセットアップがまだまだ存在するかもしれません。 そのため、埋め込み SQL を大量に(そして創造性豊かに)使用しているアプリケーションを 2020.1 にアップグレードする際には、特に注意してください。また、通常と異なることに気づいた場合は、[WRC](http://wrc.intersystems.com/) までご連絡ください。
### 埋め込み SQL と動的 SQL は 1 つに統一されたのですか?
動的 SQL と埋め込み SQL の使用方法については、現在でもセマンティックがある程度異なっています。 埋め込み SQL は名前付きカーソルを操作しますが、これは動的 SQL のクエリに対する実際のオブジェクトハンドルを操作するよりも少し扱いにくいものです。 ただし、これは個人の好みに大きく左右される問題であるため、明らかに、楽しむためだけに、あるフレーバーから別のフレーバーにアプリケーションを書き直す理由はありません。
パフォーマンスについては、埋め込み SQL には動的 SQL よりも高速だという評判がありましたが、ここ数年におけるコード生成とオブジェクト処理の改善により、その差はほとんどなくなっています。 とは言え、埋め込み SQL のカーソルの値は変数に直接取得することが可能であるため、動的 SQL の相当するオブジェクトプロパティを通じて値にアクセスするよりも、わずかに高速ではあります。 したがって、確かにより高速ではありますが、その差はほんのわずかであるため、多くの場合、(主観的に!)開発の利便性を上回ることはありません。
### 但し書き
UQC の導入により、2020.1 の埋め込み SQL に、同じルーチンまたはクラスのコンテキストにとどまるのではなく、キャッシュ済みクエリオブジェクト内の別のクラスメソッドをバックグラウンドで呼び出してクエリを実行するという、以前にはなかった非常に小さくも固定されたオーバーヘッドが存在するようになりました。 キャッシュ済みのクエリコードの生成とコンパイルにおける作業は、以前は、&SQL() コンストラクトを含むクラスまたはルーチンのコンパイルの一環でしたが、現在は、動的 SQL と同様に、またより現実的なテーブル統計を利用して、クエリが初めて呼び出されるときに行われるようになっています。 開発者は、新しい /compileembedded=1 コンパイルフラグを使って埋め込み SQL を強制的にコンパイルできます。
これとは別に、生成されたキャッシュ済みのクエリコードで高速な i%var アクセスを使用してカーソルの状態情報を保持するのには様々なメリットがあり、ほとんどの重要なクエリで固定オーバーヘッドが相殺され、実際に埋め込み SQL が以前のバージョンよりも高速になります。 パフォーマンスが低下する可能性があるのは、2019.4 に比べれば、ほんの少数のステートメント(非常に単純な INSERT など)です。 独自に行った実験では、そのような単純なクエリでは約 6%、複雑なクエリでは 3% のパフォーマンスの_増加_が見られました。
一方で、2019.4 より前のバージョンからアップグレードするユーザーは、パフォーマンスの_改善_以外は、何も感じられない可能性があります。 2019.1 から 2019.4 までは、SQL とカーネルレイヤーにおいて、UQC で導入される小さな固定オーバーヘッドをはるかに上回る他のパフォーマンス改善が多数行われているためです。 これらの多数の変更点の概要については、[こちらの GS セッション録画](https://community.intersystems.com/post/new-video-sql-performance-need-speed)をご覧ください。
### まとめ
簡単に言えば、ユニバーサルクエリキャッシュによって、最新の統計と利用可能なインデックスを考慮するようにクエリプランが素早く確実に更新されるため、DBA の業務がより楽になります。 特に、SQL アプリケーションを別のお客様環境にデプロイする場合には、一番ピッタリな SQLを開発者が自由に選択できる重要な機能強化と言えます。
この記事は、@Mark.Hansonと@ Tom.Woodfin からいただいた貴重な貢献に基づいています
記事
Toshihiko Minamoto · 2020年4月21日
Mirroring 101
Cachéミラーリングは、CachéおよびEnsembleベースのアプリケーションに適した信頼性が高く、安価で実装しやすい高可用性および災害復旧ソリューションです。 ミラーリングは幅広い計画停止シナリオや計画外停止シナリオで自動フェイルオーバーを提供するもので、通常はアプリケーションの回復時間を数秒に抑制します。 論理的にデータが複製されるため、単一障害点およびデータ破損の原因となるストレージが排除されます。 ほとんど、またはダウンタイムなしでアップグレードを実行できます。
ただし、Cachéミラーの展開にはかなり大がかりな計画が必要であり、さまざまな手順が要求されます。 また、他の重要なインフラストラクチャコンポーネントと同様に、運用中のミラーには継続的な監視とメンテナンスが必要とされます。
この記事はよくある質問のリスト、あるいはミラーリングの理解と評価、ミラーの計画、ミラーの設定、ミラーの管理という簡単な一連のガイドとして利用することができます。 それぞれの回答には、各トピックの詳細なディスカッションへのリンクと各タスクの段階的手順へのリンクが含まれています。
ミラーを導入する計画を始める準備ができたら、まずはCaché高可用性ガイドの「ミラーリング」の章のミラーリングのアーキテクチャと計画セクションから必ず読み始めるようにしてください。
よくある質問
ミラーリングの理解と評価
ミラーリングのメリットとは?
仮想化環境にミラーを導入できますか?
クラウドにミラーを導入できますか?
ミラーの基本的な設計はどうなっていますか?
データベースのコピーは、実際の本番データベースとどのように同期されますか?
自動フェールオーバーはどのようにトリガーされますか? フェールオーバーで対応できない状況はありますか?
ミラーは災害復旧(DR)機能を提供しますか?
ミラーの計画
ミラーのアーキテクチャはどのように計画すべきですか? メンバー構成や物理的配置はどのようになりますか?
ネットワークや遅延に関してどのようなことを考慮すべきですか? ミラーにはどのようなネットワーク構成が必要ですか?
フェイルオーバー時にアプリケーションの接続を新しいプライマリにリダイレクトするためのオプションにはどのようなものがありますか?
ミラー内のCachéインスタンスの互換性要件にはどのようなものがありますか?
既存のデータベースをミラーに移行するには?
仮想化環境にミラーを導入する場合に考慮すべきことはありますか?
ミラーの設定
どのような構成ガイドラインを考慮する必要がありますか?
ミラーを保護するには?
ミラー仮想IPアドレス(ミラーVIP)を構成するには?
アービターはどこにどのように配置すべきですか?
ISCAgentをインストールして起動するには?
ミラーを作成して構成するには?
ミラーデータベースを作成するには? 既存のデータベースをミラーに追加するには?
フェールオーバーした後、ECPにアプリケーションサーバーの接続をリダイレクトさせるには?
クラウドなどでミラーVIPが使用できない場合にアプリケーションの接続をリダイレクトさせるには?
Cachéシャドウをミラーに変換するには?
他にどのような構成情報を調べる必要がありますか?
ミラーの管理
ミラーの動作状態を監視するには?
ミラーを変更するには?変更できる 設定は?
ミラーにメンバーを追加できますか? メン バーを削除できますか? ミラーを完全に削除するには?
メンバーをミラーから一時的に削除する必要がある場合は?
ミラーはまとめてアップグレードする必要がありますか? そのためには、ミラーを本番環境から外す必要がありますか?
他にどのようなミラーまたはミラー関連の管理手順と情報を知っておくべきですか?
ミラーの停止手順
ミラーリングの理解と評価
ミラーリングのメリットとは?
CachéおよびEnsembleベースのアプリケーションでは、主にフェールオーバークラスター 、仮想化HA 、およびCachéミラーリングの3つの手法で高可用性を確保しています。 最初の2つの最大の欠点は、共有ストレージに依存していることです。そのため、ストレージの障害が発生すると甚大な被害が発生します。これは必要に応じてストレージレベルで冗長性を確保することにより改善できますが、ある種のデータ破損も引き続き発生する可能性があります。 さらに、ソフトウェアのアップグレードにはかなりのダウンタイムが必要であり、多くの障害ではアプリケーションの復旧に数分程度の時間を要する可能性があります。
ミラーリングでは2つの物理的に独立したシステムを使用して別々のストレージに論理データを複製することで、共有ストレージの問題を回避しています。また、アップグレードに必要とされるダウンタイムはゼロまたは最小限に抑えられているため、アプリケーションの復旧時間は通常数秒となります。 また、この手法では本番データセンターから適切な距離に災害復旧サイトを配置し、ミラーリングによる信頼性の高い堅牢な災害復旧機能を提供することができます。
ミラーリングの主な制限事項として、データベース自体のみを複製することが挙げられます。そのため、アプリケーションが必要とする外部ファイルには追加のソリューションが必要であり、現状はセキュリティと構成管理が分散化されています。
以下の情報源では、これらのHA手法の詳細な分析と比較、およびミラーリングのメリットに関する詳細な情報を提供しています。
システムフェールオーバー戦略(Caché高可用性ガイド)
高可用性手法(ホワイトペーパー)
事業継続性を実現する高可用性(ビデオ)
Cachéミラーリング:高可用性の冒険(ビデオ)
ミラーリング:スループットの設計(オンライン学習)
InterSystems Caché:データベースミラーリング:概要(ホワイトペーパー)
HealthShare:ミラーリングによる高可用性の実現(オンライン学習)
仮想化環境にミラーを導入できますか?
ミラーリングはよく仮想化環境に導入されています。 ミラーによって自動フェールオーバーを介した計画停止や計画外停止への即時対応が実現するいっぽうで、仮想化HAソフトウェアが計画外のマシンやOSの停止が発生した後にミラーメンバーをホストしている仮想マシンを自動的に再起動します。 そのため、障害が発生したメンバーはミラーにすばやく再参加し、バックアップとして機能することができます(または必要に応じてプライマリの役割を引き継ぐことができます)。
この手法の利用に関する情報は、InterSystemsのホワイトペーパー「高可用性手法」を参照してください。
クラウドにミラーを導入できますか?
ミラーリングは効果的にクラウドに導入できます 。 クラウドネットワークの制限により、フェイルオーバーした後に仮想IPアドレス(ミラーVIP)を使用してアプリケーションの接続をリダイレクトすることは通常は不可能ですが、これはロードバランサーなどのネットワークトラフィックマネージャーを使用することで実質的に解決できます 。
ミラーの基本的な設計はどうなっていますか?
通常、Cachéミラーにはフェールオーバーメンバーと呼ばれる物理的に独立したホスト上の2つのCachéインスタンスが含まれています。ミラーによってプライマリの役割が自動的に片方のインスタンスに割り当てられ、もう片方はバックアップになります。 アプリケーションがプライマリのデータベースを更新するいっぽうで、ミラーはバックアップのデータベースをプライマリのデータベースと同期させます。
プライマリに障害が発生するか使用できなくなると、バックアップが自動的にプライマリの役割を引き継ぎ、アプリケーションの接続がそちらにリダイレクトされます。 また、プライマリインスタンスが稼働できる状態に復帰したら自動的にバックアップになります。
メンテナンスやアップグレードのための計画停止中に可用性を維持するため、オペレーターは手動でフェールオーバーさせることができます。
ミラーには、必要に応じて災害復旧とビジネスインテリジェンス、およびデータウェアハウジングを目的とした非同期メンバーと呼ばれる追加のメンバーが含まれます。
ミラーは災害復旧が主な目的である場合など、1つのフェールオーバーメンバーと複数の非同期メンバーで動作することもできます。
データベースのコピーは、実際の本番データベースとどのように同期されますか?
ミラーのバックアップと非同期メンバーは、最後にバックアップされてからCachéインスタンスのデータベースに加えられた時系列の変更記録を含むジャーナルファイルを使用してプライマリと同期されます。 ミラー内ではプライマリからジャーナルファイルが送信され、他のメンバー上で記録が取り込まれます。つまり、ジャーナルファイルに記録された変更がデータベースのローカルコピーに適用され、プライマリの最新の状態に維持されます。
ジャーナルレコードはプライマリからバックアップに同期的に転送され、プライマリは要所要所でバックアップの承認を待ちます。 このような仕組みによってフェールオーバーメンバーが厳密に同期され、バックアップが有効になり、プライマリの役割を引き継げるようにもなります。 非同期メンバーはプライマリから非同期的にジャーナルデータを受信するため、結果としていくつかのジャーナルレコードを遅れて受信することがあります。
自動フェールオーバーはどのようにトリガーされますか? フェールオーバーで対応できない状況はありますか?
バックアップが自動的に役割を引き継げるのは、手動操作を行わなくてはプライマリを動作させることができなくなったことを認識した場合だけです。 フェールオーバーメンバー間の直接通信が途切れると、バックアップは両方のフェールオーバーメンバーと独立した接続を維持している3番目のシステムであるアービターの力を借りてこの状況を確認します。
また、バックアップがプライマリの最新ジャーナルデータを持っていることや取得できていることを確認できない場合、自動フェールオーバーは発生しません。 ISCAgentsと呼ばれる各フェールオーバーホスト上のCachéインスタンスとは独立して実行されるエージェントのプロセスが、この動作や自動フェールオーバーのロジックやメカニズムの他の場面に関与しています。
アービターが適切に機能していると仮定すれば、ほぼあらゆるプライマリの計画外停止に対応できます。有効なバックアップが障害が発生した、あるいは利用不可能なプライマリから役割を引き継ぐのを妨げるのは、両方のフェールオーバーメンバーをお互いに、さらにはアービターから分離するようなネットワーク障害だけです。
ミラーは災害復旧(DR)機能を提供しますか?
非同期ミラーメンバーの種類の1つに、災害復旧(DR)非同期メンバーがあります。 DR非同期メンバーはプライマリ上のデータベースをすべてミラーリングしたデータベースのコピーを持ち、いつでもフェールオーバーメンバーに昇格できます。 停止によってミラーの中に機能するフェールオーバーメンバーが存在しなくなる場合、昇格されたDR非同期メンバーに手動でフェールオーバーさせることができます。その場合のデータ損失範囲は、停止が発生した時点でDR非同期メンバーがプライマリからどの程度遅れているか、ならびに旧プライマリのホストシステムが機能しており、追加のジャーナルデータを取得できるかどうかによって決まります。 昇格されたDR非同期メンバーは、その他多くの計画停止や計画外停止の状況でも役立ちます。
ミラーの計画
ミラーのアーキテクチャはどのように計画すべきですか? メンバー構成や物理的配置はどのようになりますか?
ミラーのサイズ・メンバー構成・物理的配置は、ミラーを導入する理由やインフラストラ上および運用上の多くの要因によって決まり、非常に多くの構成が可能です。
2つのフェイルオーバーメンバーを持つミラーは、自動フェールオーバーによって高可用性を実現します。 オプションの非同期メンバーのうち、1つ以上のDR非同期メンバーがデータセキュリティ機能と災害復旧機能を提供できますが、レポート非同期メンバーはデータマイニングやビジネスインテリジェンスなどの目的に使用されます。 単一のレポート非同期メンバーは最大10個の独立したミラーに属することができ、企業全体のデータウェアハウスとして機能し、別々の場所から関連するデータベース一式をまとめることができます。
自動フェールオーバーが必要でない限り、ミラーは単一のフェールオーバーメンバーと、災害復旧およびレポート作成を目的とした多数の非同期メンバーで構成することもできます。
ミラーには最大で16個のメンバーを含めることができます。 フェールオーバーメンバーは遅延の少ない接続が必要とされるために通常は同じ場所に配置されますが、非同期メンバーはローカルまたは個別のデータセンターに配置できます。
複数のミラーメンバーを単一のホストに組み込むことができますが、計画が別途必要になります。
ネットワークや遅延に関してどのようなことを考慮すべきですか? ミラーにはどのようなネットワーク構成が必要ですか?
重要なネットワーク構成の考慮事項には、アプリケーションのパフォーマンスに関する重要な考慮事項である信頼性、帯域幅、ネットワークの遅延時間などがあります。 通常はプライマリから他のメンバーに転送されるジャーナルデータを圧縮することが好まれますが、常にそうであるとは限りません。
ミラーを支えるのに必要なネットワーク構成を計画する前に、各ミラーメンバーがさまざまな目的に使用される複数の異なるネットワークアドレスを持っていることを理解する必要があります。 ミラー構成およびネットワーク構成のサンプルが、必要なネットワーク構成を定義するのに役立ちます。このサンプルでは、単一のデータセンター、コンピュータールーム、またはキャンパス内のミラー、およびデュアルデータセンターを使ってDR機能を地理的に分散したミラーを紹介しています。
フェイルオーバー時にアプリケーションの接続を新しいプライマリにリダイレクトするためのオプションにはどのようなものがありますか?
ミラーリングとCachéには、ミラー用の仮想IPアドレス(VIP)の使用、ECPデータサーバーのミラー接続としての認識、ミラー対応のCSPゲートウェイといった複数の自動リダイレクトオプションが組み込まれています。
ミラー用のVIPは一般的に非常に効果的なソリューションですが、ネットワーク構成関連を中心にいくつかの事前計画が必要になります。
ロードバランサーなどのネットワークトラフィックマネージャーの使用、自動または手動でのDNSの更新、アプリケーションレベルのプログラミング、ユーザーレベルの手順を含め、さまざまな外部テクノロジーも利用できます。
ミラー内のCachéインスタンスの互換性要件にはどのようなものがありますか?
ミラーに追加するシステムを決める前に、Cachéインスタンスとプラットフォームのエンディアンに関する要件を必ず確認してください。 フェールオーバーメンバーはいつでもプライマリおよびバックアップとしての役割を交換できるため、できるだけ同じようなシステムにしなければなりません。CPUとメモリは同じまたは近い構成に、ストレージサブシステムは同等にしなければなりません。
既存のデータベースをミラーに移行するには?
任意のCachéデータベースをミラーに簡単に追加できます。必要なのはデータベースをバックアップして復元する機能か、CACHE.DATをコピーする機能のいずれかだけです。 手順については、次のセクションで説明します。
仮想化環境にミラーを導入する場合に考慮すべきことはありますか?
仮想化環境でミラーリングを使用する場合、仮想ミラーメンバーホストと物理ホスト・ストレージ間の正しい関係を計画することが重要です。ミラーと仮想化プラットフォームの両方の側面から見た重要な運用上の考慮事項もあります。
ミラーの設定
どのような構成ガイドラインを考慮する必要がありますか?
ミラー仮想IPアドレス(VIP)を構成する場合、InterSystemsは同じスーパーサーバーポートとウェブサーバーポートを使用するようにフェールオーバーメンバーを構成することを推奨しています。
プライマリフェールオーバーメンバー上のCachéインスタンス構成(ユーザー、役割、名前空間、マッピングなど)やミラー化されていないデータ(SQLゲートウェイやウェブサーバー構成に関連するファイルなど)は、他のミラーメンバー上のミラーによって複製されません。 したがって、バックアップやDR非同期メンバー(昇格されている場合もあります)がフェールオーバー発生時にプライマリから役割を引き継ぐために必要なすべての設定やファイルをそれらのメンバーに手動で複製し、必要に応じて更新する必要があります。
ミラーメンバーとして構成されているシステムではインターネット制御メッセージプロトコル(ICMP)を無効にしないでください。ミラーリングはICMPを利用してメンバーが到達可能かどうかを検出しています。
ジャーナリングはミラー同期の基礎であるため、一般的にはフェールオーバーメンバーのジャーナリングのパフォーマンスを監視および最適化し、ジャーナリングのベストプラクティスに従うことが不可欠です。 InterSystemsでは特にすべてのミラーメンバーで共有メモリヒープサイズを増やすことを推奨しています。
ミラーを保護するには?
ミラーリング通信の保護には、X.509証明書を使用してミラー内の全トラフィックを暗号化するSSL/TLSが主に使用されます。 SSL/TLSによる保護を強く推奨します。 ミラーでSSL/TLSを有効にするには、最初に各ミラーメンバーでミラーのSSL/TLS構成を作成する必要があります。ミラーを作成する前にこの構成を行うのが最も便利かもしれません。 SSL/TLSが有効になっている場合、ミラーに追加される各メンバーはプライマリ側で承認を受ける必要があります。あるメンバーのX.509証明書が更新された場合も同様です。
SSL/TLSを使用するミラーに対する保護をさらに強化するため、ジャーナルの暗号化を有効化することができます。 これにより、ジャーナルレコードはプライマリ上で作製される際に有効な暗号鍵のいずれかを使用して暗号化され、他のメンバー上でジャーナルが取り込まれる前に復号化されます。 バックアップとすべての非同期メンバーでは同じ鍵を有効化する必要があり、バックアップとDR非同期メンバーもその鍵を使用してデータを暗号化する必要があります。
ミラーが使用するネットワークを構成する方法も、ミラーの安全性に重大な影響を及ぼします。
ミラー仮想IPアドレス(ミラーVIP)を構成するには?
ミラーVIPは、ミラーを作成する際やメンバーを追加する際、またはミラーを変更する際に詳細を入力することで構成されます。ただし、必要な情報を特定したり、場合によってはミラーメンバーのホストやCachéインスタンスを構成したりといった準備が必要になります。
アービターはどこにどのように配置すべきですか?
アービターは、アービターとフェールオーバーメンバーの計画外停止が同時に発生するリスクを最小限に抑えるように配置すべきです(両方のフェールオーバーが発生した場合、アービターは無意味になります)。そのため、アービターの配置場所は主にフェイルオーバーメンバーの場所によって決まります。 複数のミラーがある場合、それぞれに対して適切に配置されているという条件で単一のシステムをアービターとして構成できます。 1つのミラーに対して1つ以上のフェイルオーバーメンバーかDR非同期メンバーをホストしているシステムは、そのミラーのアービターとして構成しないでください。
Cachéバージョン2015.1以降のインスタンスを1つ以上ホストしているシステムなど、バージョン2015.1以降のISCAgentを実行しているシステムはアービターとして構成できます。 ISCAgentをインストールすることで、2015.1未満のCachéインスタンスをホストしているシステムを含む他のサポート対象システム(OpenVMSシステムを除く)をアービターとして構成できます。
ISCAgentをインストールして起動するには?
ISCAgentはCachéと一緒に自動的にインストールされるため、すべてのミラーメンバーにインストールされています。 ただし、エージェントは各ミラーメンバーのシステム起動時に起動するよう構成する必要があります。
ミラーを作成して構成するには?
ミラーを構成するには、次のような複数の手順を実施する必要があります。
ミラーを作成して最初のフェールオーバーメンバーを構成する
2番目のフェールオーバーメンバーを構成する(必要な場合)
2番目のフェールオーバーメンバーを承認する(SSL/TLSを使用する場合に推奨)
非同期ミラーメンバーを構成する(必要に応じてDRまたはレポートのいずれかを構成)
新しい非同期メンバーを承認する(SSL/TLSを使用する場合に推奨)
これらの手順のいずれかを完了すると、Mirror Monitorでミラーの状態を調査し、意図したとおりの結果を得られたかを確認することができます。
ミラーデータベースを作成するには? 既存のデータベースをミラーに追加するには?
データベースをミラーに追加する前に、ミラーリングできるものとできないもの、ミラーリングとシャドーイングの同時使用、ミラーリングデータベースのプロパティの伝播、およびミラーリング対象となっているインスタンスごとのデータベースの最大数に関する一定のミラーデータベースの考慮事項を確認することをお勧めします。
ミラーデータベースの作成と既存データベースの追加手順は異なります。ミラーデータベースへの変更はミラーされたジャーナルファイルに記録され、非ミラージャーナルファイルとは異なるからです。 データベースがミラーデータベースとして作成されている場合は最初からミラージャーナルファイルが使用されます。そのため、プライマリを始めとする各ミラーメンバーに同じミラー名のミラーデータベースを作成することで、簡単に新しいデータベースをミラーに追加することができます。
プライマリ上にミラーデータベースとして既存の非ミラーデータベースを追加する場合、非ミラージャーナルファイルからミラージャーナルファイルを使用するように切り替わります。 そのため、単純に他のメンバー上にデータベースを作成することはできません。なぜなら、ミラーは非ミラージャーナルファイルを他のメンバーに転送できないからです。 代わりに、データベースがプライマリのミラーに追加された後にデータベースをバックアップして他のメンバーに復元するか、そのCACHE.DATファイルを他のメンバーにコピーする必要があります。
フェールオーバーした後、ECPにアプリケーションサーバーの接続をリダイレクトさせるには?
ミラーVIPを構成したかどうかにかかわらず、接続する各ECPアプリケーションサーバー上のミラー接続としてミラーECPデータサーバーを構成することにより、ECP接続を新しいプライマリにリダイレクトさせることができます。 (アプリケーションサーバーは指定されたホストから定期的に情報を収集し、フェールオーバーを自動的に検出して新しいプライマリに切り替えるため、VIPを使用しません。)
クラウドなどでミラーVIPが使用できない場合にアプリケーションの接続をリダイレクトさせるには?
ミラーVIPはミラーメンバーが同じネットワークサブネット上にある場合にのみ使用できますので、一般的にはミラーメンバーが別々のデータセンターにある場合は使用できません。 同様の理由で、VIPは一般的にクラウドに導入できるオプションではありません。
VIPと同レベルの透過性を実現するために使用され、クライアントアプリケーションやデバイスに単一のアドレスを提示するロードバランサーなどのネットワークトラフィックマネージャーの使用(物理または仮想)を含め、さまざまな外部テクノロジーも利用できます。 その他に想定される手順には、自動または手動でのDNSの更新、アプリケーションレベルのプログラミング、およびユーザーレベルの手順などがあります。
Cachéシャドウをミラーに変換するには?
ミラーリングは、シャドウのソースと宛先、およびそれらの間にマッピングされたシャドウデータベースをプライマリ、バックアップまたは非同期メンバーを含むミラーとミラーベーデータベースに変換するシャドウ・ミラーユーティリティを提供します。
他にどのような構成情報を調べる必要がありますか?
通常はデフォルトで十分ですが、必要に応じてISCAgentポート番号をカスタマイズできます。
プライマリフェールオーバーメンバーでは、必要に応じて既存の^ZSTUまたは^ZSTARTルーチンからユーザー定義の^ZMIRRORルーチンにコードを移動することができます。これにより、特定のミラーリングイベント用に独自構成固有のロジックと機構をミラーが初期化されるまで実行しないように実装することができます。
Ensembleでミラーリングを使用する場合、ミラーデータを含むEnsemble名前空間の特別な要件とミラー環境でのEnsemble Autostartの機能に注意する必要があります。
ミラーの管理
ミラーの動作状態を監視するには?
任意のミラーメンバーのCaché管理ポータルで読み込めるMirror Monitorでは、以下の詳細な情報を確認できます。
SSL/TLSを使用中のメンバーのx.509 DNを含むミラーとその各メンバーの動作状態。
フェールオーバーメンバーの場合は、両方のフェールオーバーメンバーのネットワークアドレスとアービターの接続状態、およびアービターのアドレス。非同期メンバーの場合は、レポート非同期メンバーが属するミラー。
バックアップおよび非同期メンバーの場合は、プライマリからのジャーナルデータ転送とジャーナルデータの取り込みの状態、およびプライマリから届くジャーナルデータの転送速度。
Mirror Monitorを読み込むメンバー上のミラーデータベースの状態。
Mirror Monitorでは、メンバーのジャーナルファイルの表示や検索、DR非同期メンバーのフェールオーバーメンバーへの昇格・バックアップのDR非同期メンバーへの降格、ミラーデータベースの有効化・キャッチアップ・削除などのさまざまな操作を実行できます。
ミラーメンバーのミラー通信プロセスを監視するため、該当ミラーメンバーの%SYS名前空間でCachéシステム状態ルーチン(^%SS)を使用できます。
ミラーを変更するには?変更できる設定は?
ミラーの構成(SSL/TLS、ミラーVIPその他)を変更したり、ネットワーク構成変更時にメンバーのネットワークアドレスを更新したりするには、プライマリでミラーを編集してください。 また、プライマリのミラーを編集して他のメンバー上でのX.509証明書の更新を承認する必要があります。
非同期の種類を変更し、レポート非同期メンバーを別のミラーに追加し、その他非同期メンバー固有の変更を行うには、非同期メンバーのミラーを編集してください。
Mirror Monitorを使用して任意のメンバー(およびそのメンバーのみ)のミラーからミラーデータベースを削除できますが、その影響は関連するメンバーの種類によって異なります。
ミラーにメンバーを追加できますか? メンバーを削除できますか? ミラーを完全に削除するには?
ミラーにはいつでも合計16メンバーの上限まで非同期メンバーを追加できます。 フェールオーバーメンバーが1つで非同期メンバーが15未満の場合はいつでもバックアップを追加できます。 DR非同期メンバーを昇格してフェールオーバーメンバーにすることでバックアップを置き換え、現在のバックアップをDR非同期メンバーに自動的に降格させることもできます。
任意のメンバーでミラーを編集し、ミラーからそのメンバーを削除できます。 ミラーを完全に削除するには、特定の順序でメンバーを削除し、追加の手順を実行する必要があります。
メンバーをミラーから一時的に削除する必要がある場合は?
Mirror Monitorを使用すればメンテナンスや(非同期メンバーの場合に)ネットワーク負荷軽減などを目的としてミラーからメンバーを切り離すことで、無期限にバックアップまたは非同期メンバーのミラーを停止することができます。
非同期メンバーでは、プライマリから非同期メンバーへのジャーナルデータ転送を止めることなくミラー内の全データベースに対してジャーナルの取り込みを停止することもできます。
ミラーはまとめてアップグレードする必要がありますか? そのためには、ミラーを本番環境から外す必要がありますか?
ミラーを構成するすべてのフェールオーバーメンバーとDR非同期メンバーは同じバージョンのCachéでなければならず、違っていても構わないのはミラーをアップグレードする間だけです。 アップグレードされたメンバーがプライマリになると、他のフェールオーバーメンバーやDR非同期メンバーは同様にアップグレードされるまで使用できなくなります。 一般的に、レポート非同期メンバーを同じバージョンに同時にアップグレードするのがベストプラクティスとされています。
アップグレード手順は、メンテナンスリリースのアップグレード、ミラーデータベースへの変更を一切伴わないメジャーアップグレード、ミラーデータベースへの変更を伴うメジャーアップグレードのどれを行うかによって決まります。 定められた手順は、アプリケーションのダウンタイムを最小限に抑えるよう工夫されています。通常、最初の2つのケースではダウンタイムを完全員回避できます。また、最後のケースでは一般的に計画フェールオーバーを実行して必要なミラーデータベースの変更にかかる時間内に限定されます。
計画ダウンタイム中にメジャーアップグレードを行い、アプリケーションのダウンタイムを最小限に抑える必要がない場合に採用できるより簡単な手順もあります。
他にどのようなミラーまたはミラー関連の管理手順と情報を知っておくべきですか?
各メンバーに有効なミラーのSSL/TLS構成があるという前提で、まだそれを利用していないミラーのSSL/TLS保護を有効化できます。
ミラーにSSL/TLS保護を使用しており、プライマリ上のジャーナルデータを暗号化する有効な暗号鍵がバックアップとすべての非同期メンバーで有効化されているという前提で、ジャーナルの暗号化を利用していないミラーでジャーナルの暗号化を有効化できます。
ハードウェアとネットワークの構成に応じてミラーのサービス品質タイムアウト(QoSタイムアウト)設定を調整することができます。この設定はフェールオーバー機構で重要な役割を果たします。 一般的に、専用ローカルネットワークを持つ(仮想化されていない)物理ホストに展開されたミラーでより迅速な停止への対応が求められる場合にこの設定を減らすことができます。
ミラーデータベースの更新内容の大部分が高圧縮データ(圧縮済みの画像など)や暗号化されたデータである場合、ジャーナルデータの圧縮は効果的ではないと予想され、CPU時間を浪費する可能性があります。 このような場合は、ミラーを構成または変更してジャーナルデータを非圧縮に設定することができます。 (Cachéデータベースの暗号化やジャーナルの暗号化を利用しても、圧縮を選択したことにはなりません。)
プライマリと他のミラーメンバー間のネットワーク遅延が問題になる場合は、プライマリおよびバックアップ/非同期メンバーがそれぞれ適切なサイズの送信および受信バッファを確立できるようにオペレーティングシステムのTCPパラメーターを微調整することで遅延を緩和できる場合があります。
^MIRRORルーチンはあらゆるミラーリングタスク用に、管理ポータルの代わりとなるコマンドラインを提供します。 SYS.Mirror APIは管理ポータルと^MIRRORルーチンを通じて利用可能なミラー操作をプログラムで呼び出すためのメソッドを提供します。
ミラーの停止手順
さまざまな計画的および計画外のミラー停止シナリオに対処するための推奨手順の概要については、ミラーの停止手順を参照してください。
記事
Tomohiro Iwamoto · 2020年8月17日
# 本稿について
本稿では、InterSystems IRISを使用してSQLベースのベンチマークを行う際に、実施していただきたい項目をご紹介します。
Linuxを念頭においていますが、Windowsでも考慮すべき点は同じです。
## メモリ自動設定をやめる
パフォーマンスに直結する、データベースバッファサイズの[自動設定](https://docs.intersystems.com/irislatestj/csp/docbook/Doc.View.cls?KEY=GSA_config#GSA_config_system_startup)はデフォルトで有効になっています。自動設定は、実メモリの搭載量にかかわらず、データベースバッファを最大で1GBしか確保しません。
> 更新: 2020年11月20日 バージョン2020.3から、確保を試みるデータベースバッファが実メモリの25%に変更されました。
搭載実メモリ64GB未満の場合は実メモリの50%程度、搭載実メモリ64GB以上の場合は実メモリの70%を目途に、明示的に設定を行ってください。
設定するにはiris停止状態で、iris.cpfファイル(IRISインストール先\mgr\iris.cpf)を変更します。下記はブロックサイズ8KB用(既定値です)のデータベースバッファサイズの自動構成を4096(MB)に変更する例です。
修正前
```
[config]
globals=0,0,0,0,0,0
```
修正後
```
[config]
globals=0,0,4096,0,0,0
```
詳細は[こちら](https://docs.intersystems.com/irislatestj/csp/docbook/Doc.View.cls?KEY=GSCALE_vertical#GSCALE_vertical_memory_initial)です。
また、Linuxの場合、[ヒュージ・ページ有効化](https://docs.intersystems.com/irislatestj/csp/docbook/Doc.View.cls?KEY=GSCALE_vertical#GSCALE_vertical_memory_large)の設定を行ってください。設定値の目安ですが、IRIS起動時のメッセージから、確保される共有メモリサイズ(下記だと749MB)を読み取り、その値めがけて設定するのが簡単です。
> コンテナバージョンは非rootで動作するため、ヒュージ・ページは利用できません
```
$ iris start iris
Starting IRIS
Using 'iris.cpf' configuration file
Starting Control Process
Allocated 749MB shared memory: 512MB global buffers, 64MB routine buffer
$ cat /proc/meminfo
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
```
ページサイズが2048kBですので、750MB(丸めました)を確保するには750/2=375ページ必要になります。構成を変えると必要な共有メモリサイズも変わりますので、再調整してください。
```
$ echo 375 > /proc/sys/vm/nr_hugepages
$ iris start iris
Starting IRIS
Using 'iris.cpf' configuration file
Starting Control Process
Allocated 750MB shared memory using Huge Pages: 512MB global buffers, 64MB routine buffer
```
メモリ関連の設定値は、パフォーマンスに大きく影響しますので、比較対象のDBMSが存在するのであればその設定値に見合う値を設定するようにしてください。
## データベース関連ファイルの配置
特にクラウド上でのベンチマークの場合、ストレージのレイアウトがパフォーマンスに大きな影響を与えます。クラウドではストレージごとのIOPSが厳格に制御されているためです。
[こちら](https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_prepare_install#GCI_filesystem)に細かな推奨事項が掲載されていますが、まずは下記のように、アクセス特性の異なる要素を、それぞれ別のストレージに配置することをお勧めします。
```
Filesystem Size Used Avail Use% Mounted on
/dev/sdc 850G 130G 720G 16% /irissys/data (IRIS本体のインストール先、ユーザデータべース)
/dev/sdd 800G 949M 799G 1% /irissys/wij (ライトイメージジャーナルファイル)
/dev/sde 200G 242M 200G 1% /irissys/journal1 (ジャーナルファイル)
/dev/sdf 100G 135M 100G 1% /irissys/journal2 (予備のジャーナルファイル保存エリア)
```
サイズは用途次第です。WIJに割り当てているサイズが大きめなのは、IOPS性能がファイルシステムの容量に比例するクラウドを使用するケースを意識したためで、IOPSとサイズが連動しないオンプレミス環境では、これほどのサイズは必要ありません。
## テーブルへのインデックス追加
IRISのテーブルへのインデックス追加は一部のDWH製品のように自動ではありません。多くのRDBMS製品と同様に、CREATE TABLE命令の実行時に、プライマリキー制約やユニーク制約が指定されている場合、インデックが追加されますが、それ以外のインデックスはCREATE INDEX命令で明示的に追加する必要があります。
どのようなインデックスが有用なのかは、実行するクエリに依存します。
一般的に、ジョインの結合条件(ON句)となるフィールド、クエリやUPDATE文の選択条件(WHERE句)となるフィールド、グルーピングされる(GROUP BY)フィールドが対象となります。
[インデックスの対象](https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_optquery#GSQLOPT_optquery_indexfields)を参照ください。
## データベースファイルのサイズ
IRISのデータベースファイルの初期値は1MBです。既定では、ディスク容量が許容する限り自動拡張を行いますので、エラーにはなりませんが、パフォーマンスは劣化します。ベンチマークプログラムでそのことに気づくことはありません。お勧めは、一度ベンチマークを実行して、必要な容量まで自動拡張を行い、データを削除(DROP TABLEやTRUNCATE TABLEを実行)した後に、再度ベンチマークを実行することです。
## 統計情報の取得
IRISはクエリの実行プランを最適化するために、テーブルデータに関する統計情報を使用します。
統計情報の取得処理(TuneTable/TuneSchema)は、初期データロード後に明示的に実行する必要があります。
> IRISバージョン2022.1以降、統計情報の取得処理が一度も実行されていない場合、初回のクエリ実行時に自動実行されるようになりました。そのため、初回のクエリに若干のオーバヘッドが発生します。
TuneTable(テーブル対象)/TuneSchema(スキーマ対象)を実行すると、既存のクエリプランがパージされるので、次回の初回クエリ実行時はクエリ解析・プラン生成にかかる分だけ時間が多めにかかります。
また、TuneTable/TuneSchema実行後にインデックスを追加した場合には、そのテーブルに対してTuneTableを再実行してください。
これらを考慮すると、ベンチマークで安定した計測結果を得るには、テストデータのロード後に、明示的に下記を実施することが重要になります。
- TuneTable/TuneSchemaを実行
- 最低でも一度は全クエリを実行する
> もちろん、これらの性能を計測したい場合は、その限りではありません。
TuneTable/TuneSchemaは、管理ポータル及びCLIで、手動で[実行可能](http://docs.intersystems.com/irislatestj/csp/docbook/Doc.View.cls?KEY=GSQLOPT_opttable#GSQLOPT_opttable_tunetable_run)です。ベンチマーク測定時は、データの再投入やインデックスの追加や削除といった、統計情報の更新を繰り返し実行する必要性が高くなることを考慮すると、手動での更新は避けて、下記のObjectScriptのCLIやSQL文で実施するのが効果的です。
下記コマンドはスキーマMySchemaで始まる全てのテーブルの統計情報を取得します。
```
MYDB>d $SYSTEM.SQL.Stats.Table.GatherSchemaStats("MySchema")
```
下記コマンドは指定したテーブルのテーブルの統計情報を取得します。
```
MYDB>d $SYSTEM.SQL.Stats.Table.GatherTableStats("MySchema.MyTable")
```
あるいはSQL文として、データロード実行後にベンチマークプログラム内で実行する事も可能です。
```SQL
TUNE TABLE MySchema.MyTable
```
## タイムスタンプ型に%TimeStampではなく%PosixTimeを使用する
更新: 2020年11月20日 使用方法を、SQL文の修正が不要な方法に変更しました
> IRISバージョン2022.1以降、SQLのTimeStamp型の既定値が%PosixTimeに変更されました。この変更によりテーブルを新規作成した際のTimeStamp型は%PosixTimeになります。
```
create table TEST (ts TIMESTAMP)
```
これを、下記のように変更してください。
```
create table TEST (ts POSIXTIME)
```
デフォルトではSQLのTIMESTAMP型は、IRIS内部では%TimeStamp型で保持されます。
%TimeStamp型は、データを文字列として保存するのに対して、%PosixTimeは64bitの整数で保持します。その分、ディスクやメモリ上のデータサイズや比較演算処理などで有利になります。両者はxDBC上は、共にTIMESTAMP型となり、互換性がありますので、DML文の修正は不要です(敢えて指摘するとすれば、ミリ秒以下の精度が異なります)。以下は、実行例です。%PosixTimeに対するクエリのほうが、程度の差こそあれ、高速になっています。
```
create table TEST (ts TIMESTAMP, pt POSIXTIME)
create index idxts on TABLE TEST (ts)
create index idxpt on TABLE TEST (pt)
insert into TEST ... 100万レコードほどINSERT
select count(*),DATEPART(year,pt) yyyy from TEST group by DATEPART(year,pt)
平均 669ミリ秒
select count(*),DATEPART(year,ts) yyyy from TEST group by DATEPART(year,ts)
平均 998ミリ秒
select count(*) from TEST where DATEPART(year,pt)='1990'
平均 533ミリ秒
select count(*) from TEST where DATEPART(year,ts)='1990'
平均 823ミリ秒
select count(*) from TEST where pt>='1980-01-01 00:00:00' and pt='1980-01-01 00:00:00' and ts IRISバージョン2022.1以降、既定値がPosixTimeに変更されましたので、構成ファイルの変更は不要です。
```
[SQL]
TimePrecision=0 (修正前)
TimePrecision=6 (修正後)
[SqlSysDatatypes]
TIMESTAMP=%Library.TimeStamp (修正前)
TIMESTAMP=%Library.PosixTime (修正後)
```
## バイナリデータの保存には可能であればVARBINARYを使用する。
バイナリデータを保存する場合、サイズの上限が指定できる場合はVARBINARYを使用してください。LONGVARBINARYはサイズ上限が無い代わりに、内部でストリーム形式で保存する機構を伴うためパフォーマンスが低下します。
```
CREATE TABLE TestTable (ts TIMESTAMP, binaryA VARBINARY(512), binaryB VARBINARY(256))
```
VARBINARY使用時には、行全体のサイズに注意が必要です。SQLの行は、IRIS内部では、既定では下記のフォーマットで、1つのノードのデータ部(IRISの内部形式の用語でKV形式のバリューに相当します)に格納されます。このノードの[サイズ上限]((https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=GCOS_types#GCOS_types_strings_long))は3,641,144バイトです。
長さ|データタイプ|データ|長さ|データタイプ|データ|...
この長さを超えると、\という内部エラーが発生し、INSERTが失敗します。
## Query ソース保存有効化
実行プランがコード化されたものがソースコードとして保存されます。デフォルトでは無効です。本稿では扱っていませんが、後の解析で有用になることもありますので、念のため有効化しておきます。
```
[SQL]
SaveMAC=1
記事
Tomoko Furuzono · 2020年9月17日
Caché 2017以降のSQLエンジンには新しい統計一式が含まれています。 これらの統計は、クエリの実行回数とその実行所要時間を記録します。
これは、多くのSQLステートメントを含むアプリケーションのパフォーマンスを監視する人や最適化を試みる人にとっては宝物のような機能ですが、一部の人々が望むほどデータにアクセスするのは簡単ではありません。
この記事と関連するサンプルコードでは、このような情報の使用方法と、日次統計の概要を定期的に抽出してアプリケーションのSQLパフォーマンス履歴記録を保持する方法について説明します。
※詳細については、下記ドキュメントページもご参考になさってください。
https://docs.intersystems.com/iris20201/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_sqlstmts
記録内容
SQLステートメントが実行されるたびに、所要時間が記録されます。 この処理は非常に軽量であり、オフにすることはできません。 コストを最小限に抑えるため、統計はメモリに保持されてから定期的にディスクに書き込まれます。 このデータには当日にクエリが実行された回数と、その平均所要時間と合計所要時間が含まれます。
データはすぐにはディスクに書き込まれません。ただし、統計はデータが書き込まれた後に通常は1時間ごとに実行されるようにスケジュールされている「SQLクエリ統計の更新」タスクによって更新されます。 このタスクは手動で起動できますが、クエリをテスト中にリアルタイムで統計を表示したい場合は、プロセス全体の実行時間が若干長くなります。
警告:InterSystems IRIS 2019以前では、%Studio.Project:Deploy メカニズムを使用して配置されたクラスやルーチンに埋め込まれたSQLのこれらの統計は収集されません。 サンプルコードを破壊するものは何もありませんが、コストがかかるものが何も表示されないため、すべてが正常であると思い込んでしまう可能性があります(私はそう思い込んでしまいました)。
確認できる情報
クエリのリストを管理ポータルで確認できます。 [SQL] ページに移動し、[SQLステートメント] タブをクリックしてください。 これは実行中および調査中の新しいクエリには適していますが、膨大な数のクエリが実行中の場合は管理しきれなくなる可能性があります。
別の方法として、SQLを使用してクエリを検索することができます。 情報は、INFORMATION_SCHEMAスキーマのテーブルに保存されます。 このスキーマには多数のテーブルが含まれていますが、この記事の最後にいくつかのサンプルSQLクエリを掲載しています。
統計が削除されるタイミング
クエリのデータは、クエリが再コンパイルされるたびに削除されます。 そのため、動的クエリの場合はキャッシュ済みのクエリがパージされる可能性があります。 埋め込みSQLの場合、それはSQLが埋め込まれているクラスやルーチンが再コンパイルされるタイミングになります 。
※IRIS2020.1 以降では、埋め込みSQLも動的クエリと同様に、クエリが再コンパイルされるたびにデータが削除されます。
稼働中のサイトでは1日以上統計が保持されると思われますが、統計を保持しているテーブルは、レポートや長期分析を実行するための長期参照ソースには使用できません。
情報を要約するには
パフォーマンスレポートを生成する際は、作業しやすい永続的なテーブルに毎晩データを抽出することをお勧めします。 クラスが日中にコンパイルされる場合は一部の情報が失われる可能性がありますが、それによってスロークエリの分析に実質的な違いが出ることはほとんどありません。
以下のコードは、各クエリの日次概要に統計を抽出する方法の例です。 このコードには3つの短いクラスが含まれています。
毎晩実行する必要があるタスク。
DRL.MonitorSQLは、INFORMATION_SCHEMAテーブルからデータを抽出して保存するメインクラスです。
3番目のクラスであるDRL.MonitorSQLTextは(長い可能性がある)クエリテキストを1回だけ保存し、毎日の統計にクエリのハッシュのみを保存する最適化です。
サンプルに関する注意
このタスクは前日分のデータを抽出するため、午前0時を過ぎた直後にスケジュールする必要があります。
履歴データが存在する場合は、それをエクスポートできます。 過去120日間を抽出するには以下を実行します。
Do ##class(DRL.MonitorSQL).Capture($h-120,$h-1)
最も古いバージョンの統計ではDateがSQLに公開されていなかったため、サンプルコードでは^rIndexグローバルを直接読み取っています。
私が含めたバリエーションはインスタンス内のすべての名前空間を通ってループしますが、それが常に適切であるとは限りません。
抽出されたデータに対してクエリを実行するには
データを抽出したあと、以下のクエリを実行することで最も重いクエリを見つけることができます。
SELECT top 20
S.RunDate,S.RoutineName,S.TotalHits,S.SumpTIme,S.Hash,t.QueryText
from DRL.MonitorSQL S
left join DRL.MonitorSQLText T on S.Hash=T.Hash
where RunDate='08/25/2019'
order by SumpTime desc
また、高コストなクエリのハッシュを選択すると、そのクエリの履歴を表示できます。
SELECT S.RunDate,S.RoutineName,S.TotalHits,S.SumpTIme,S.Hash,t.QueryText
from DRL.MonitorSQL S
left join DRL.MonitorSQLText T on S.Hash=T.Hash
where S.Hash='CgOlfRw7pGL4tYbiijYznQ84kmQ='
order by RunDate
私は今年の初めに本番サイトからデータを収集し、最も高コストなクエリを調べました。 1つのクエリの平均実行時間は6秒未満でしたが、1日あたり14,000回呼び出されており、毎日合計で24時間近くかかっていました。 事実上、この1つのクエリで1つのコアが完全に占有されていました。 さらに悪いことに、1時間かかる2番目のクエリは1番目のクエリのバリエーションでした。
RunDate
RoutineName
Total Hits
Total Time
Hash
QueryText(略記)
03/16/2019
14,576
85,094
5xDSguu4PvK04se2pPiOexeh6aE=
DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) …
03/16/2019
15,552
3,326
rCQX+CKPwFR9zOplmtMhxVnQxyw=
DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , …
03/16/2019
16,892
597
yW3catzQzC0KE9euvIJ + o4mDwKc =
DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , :%col(5) , :%col(6) , :%col(7) ,
03/16/2019
16,664
436
giShyiqNR3K6pZEt7RWAcen55rs=
DECLARE C CURSOR FOR SELECT * , TKGROUP INTO :%col(1) , :%col(2) , :%col(3) , ..
03/16/2019
74,550
342
4ZClMPqMfyje4m9Wed0NJzxz9qw=
DECLARE C CURSOR FOR SELECT …
表1:顧客サイトでの実際の結果
INFORMATION_SCHEMAスキーマのテーブル
このスキーマのテーブルは統計だけでなく、使用されているクエリ、カラム、インデックスなどを 追跡しています。 通常はそのSQLステートメントが最初のテーブルになり、"Statements.Hash = OtherTable.Statement" のような形で結合されます。
これらのテーブルに直接アクセスし、1日で最も高コストなクエリを見つけるための対応するクエリは以下のようになります。
SELECT DS.Day,Loc.Location,DS.StatCount,DS.StatTotal,S.Statement,S.Hash
FROM INFORMATION_SCHEMA.STATEMENT_DAILY_STATS DS
left join INFORMATION_SCHEMA.STATEMENTS S
on S.Hash=DS.Statement
left join INFORMATION_SCHEMA.STATEMENT_LOCATIONS Loc
on S.Hash=Loc.Statement
where Day='08/26/2019'
order by DS.stattotal desc
より体系的なプロセスのセットアップを検討しているかどうかに関係なく、SQLを使用する大規模アプリケーションを持つすべての方に今すぐこのクエリを実行することをお勧めします。
特定のクエリが高コストであると表示される場合は、次を実行して履歴を取得できます。
SELECT DS.Day,Loc.Location,DS.StatCount,DS.StatTotal,S.Statement,S.Hash
FROM INFORMATION_SCHEMA.STATEMENT_DAILY_STATS DS
left join INFORMATION_SCHEMA.STATEMENTS S
on S.Hash=DS.Statement
left join INFORMATION_SCHEMA.STATEMENT_LOCATIONS Loc
on S.Hash=Loc.Statement
where S.Hash='jDqCKaksff/4up7Ob0UXlkT2xKY='
order by DS.Day
日次統計を週出するためのコードサンプル
Class DRL.MonitorSQLTask Extends %SYS.Task.Definition
{
Parameter TaskName = "SQL Statistics Summary";
Method OnTask() As %Status
{
set tSC=$$$OK
TRY {
do ##class(DRL.MonitorSQL).Run()
}
CATCH exp {
set tSC=$SYSTEM.Status.Error("Error in SQL Monitor Summary Task")
}
quit tSC
}
}
Class DRL.MonitorSQLText Extends %Persistent
{
/// Hash of query text
Property Hash As %String;
/// query text for hash
Property QueryText As %String(MAXLEN = 9999);
Index IndHash On Hash [ IdKey, Unique ];
}
/// Summary of very low cost SQL query statistics collected in Cache 2017.1 and later. <br/>
/// Refer to documentation on "SQL Statement Details" for information on the source data. <br/>
/// Data is stored by date and time to support queries over time. <br/>
/// Typically run to summarise the SQL query data from the previous day.
Class DRL.MonitorSQL Extends %Persistent
{
/// RunDate and RunTime uniquely identify a run
Property RunDate As %Date;
/// Time the capture was started
/// RunDate and RunTime uniquely identify a run
Property RunTime As %Time; /// Count of total hits for the time period for
Property TotalHits As %Integer; /// Sum of pTime
Property SumPTime As %Numeric(SCALE = 4); /// Routine where SQL is found
Property RoutineName As %String(MAXLEN = 1024); /// Hash of query text
Property Hash As %String; Property Variance As %Numeric(SCALE = 4); /// Namespace where queries are run
Property Namespace As %String; /// Default run will process the previous days data for a single day.
/// Other date range combinations can be achieved using the Capture method.
ClassMethod Run()
{
//Each run is identified by the start date / time to keep related items together
set h=$h-1
do ..Capture(+h,+h)
} /// Captures historic statistics for a range of dates
ClassMethod Capture(dfrom, dto)
{
set oldstatsvalue=$system.SQL.SetSQLStatsJob(-1)
set currNS=$znspace
set tSC=##class(%SYS.Namespace).ListAll(.nsArray)
set ns=""
set time=$piece($h,",",2)
kill ^||TMP.MonitorSQL
do {
set ns=$o(nsArray(ns))
quit:ns=""
use 0 write !,"processing namespace ",ns
zn ns
for dateh=dfrom:1:dto {
set hash=""
set purgedun=0
do {
set hash=$order(^rINDEXSQL("sqlidx",1,hash))
continue:hash=""
set stats=$get(^rINDEXSQL("sqlidx",1,hash,"stat",dateh))
continue:stats=""
set ^||TMP.MonitorSQL(dateh,ns,hash)=stats
&SQL(SELECT Location into :tLocation FROM INFORMATION_SCHEMA.STATEMENT_LOCATIONS WHERE Statement=:hash)
if SQLCODE'=0 set Location=""
set ^||TMP.MonitorSQL(dateh,ns,hash,"Location")=tLocation
&SQL(SELECT Statement INTO :Statement FROM INFORMATION_SCHEMA.STATEMENTS WHERE Hash=:hash)
if SQLCODE'=0 set Statement=""
set ^||TMP.MonitorSQL(dateh,ns,hash,"QueryText")=Statement
} while hash'=""
}
} while ns'=""
zn currNS
set dateh=""
do {
set dateh=$o(^||TMP.MonitorSQL(dateh))
quit:dateh=""
set ns=""
do {
set ns=$o(^||TMP.MonitorSQL(dateh,ns))
quit:ns=""
set hash=""
do {
set hash=$o(^||TMP.MonitorSQL(dateh,ns,hash))
quit:hash=""
set stats=$g(^||TMP.MonitorSQL(dateh,ns,hash))
continue:stats=""
// The first time through the loop delete all statistics for the day so it is re-runnable
// But if we run for a day after the raw data has been purged, it will wreck eveything
// so do it here, where we already know there are results to insert in their place.
if purgedun=0 {
&SQL(DELETE FROM websys.MonitorSQL WHERE RunDate=:dateh )
set purgedun=1
}
set tObj=##class(DRL.MonitorSQL).%New() set tObj.Namespace=ns
set tObj.RunDate=dateh
set tObj.RunTime=time
set tObj.Hash=hash
set tObj.TotalHits=$listget(stats,1)
set tObj.SumPTime=$listget(stats,2)
set tObj.Variance=$listget(stats,3)
set tObj.Variance=$listget(stats,3)
set queryText=^||TMP.MonitorSQL(dateh,ns,hash,"QueryText")
set tObj.RoutineName=^||TMP.MonitorSQL(dateh,ns,hash,"Location")
&SQL(Select ID into :TextID from DRL.MonitorSQLText where Hash=:hash)
if SQLCODE'=0 {
set textref=##class(DRL.MonitorSQLText).%New()
set textref.Hash=tObj.Hash
set textref.QueryText=queryText
set sc=textref.%Save()
}
set tSc=tObj.%Save()
//avoid dupicating the query text in each record because it can be very long. Use a lookup
//table keyed on the hash. If it doesn't exist add it.
if $$$ISERR(tSc) do $system.OBJ.DisplayError(tSc)
if $$$ISERR(tSc) do $system.OBJ.DisplayError(tSc)
}while hash'=""
} while ns'=""
} while dateh'=""
do $system.SQL.SetSQLStatsJob(0)
} Query Export(RunDateH1 As %Date, RunDateH2 As %Date) As %SQLQuery
{
SELECT S.Hash,RoutineName,RunDate,RunTime,SumPTime,TotalHits,Variance,RoutineName,T.QueryText
FROM DRL.MonitorSQL S LEFT JOIN DRL.MonitorSQLText T on S.Hash=T.Hash
WHERE RunDate>=:RunDateH1 AND RunDate<=:RunDateH2
}
}
記事
Toshihiko Minamoto · 2024年5月23日
HealthShare、HealthConnect、および InterSystems IRIS ユーザーが使用できる FHIR アダプターツールに関する連載記事を再開しましょう。
前回の記事では、ワークショップをセットアップした小さなアプリケーションを紹介し、FHIR アダプターをインストールした後に IRIS インスタンスにデプロイされたアーキテクチャを説明しました。 この記事では、最も一般的な CRUD(作成、読み取り、更新、削除)操作の 1 つである読み取り操作を実行する方法の例を確認します。ここではリソースの取得によって行います。
リソースとは?
FHIR のリソースはある種の臨床情報です。この情報は、患者(Patient)、臨床検査へのリクエスト(ServiceRequest)、または診断(Condition)などです。 各リソースはそれを構成するデータのタイプのほか、データの制約や他のリソースタイプとの関係を定義します。 リソースごとに、それが格納する情報の拡張が可能であるため、FHIR ポリシーの範囲外のニーズに 80% 対応できます(ユーザーの 80% が使用する要件に対応)。
この記事の例では、最も一般的なリソースである Patient を使用します。 その定義を確認しましょう。
{
"resourceType" : "Patient",
// from Resource: id, meta, implicitRules, and language
// from DomainResource: text, contained, extension, and modifierExtension
"identifier" : [{ Identifier }], // An identifier for this patient
"active" : <boolean>, // Whether this patient's record is in active use
"name" : [{ HumanName }], // A name associated with the patient
"telecom" : [{ ContactPoint }], // A contact detail for the individual
"gender" : "<code>", // male | female | other | unknown
"birthDate" : "<date>", // The date of birth for the individual
// deceased[x]: Indicates if the individual is deceased or not. One of these 2:
"deceasedBoolean" : <boolean>,
"deceasedDateTime" : "<dateTime>",
"address" : [{ Address }], // An address for the individual
"maritalStatus" : { CodeableConcept }, // Marital (civil) status of a patient
// multipleBirth[x]: Whether patient is part of a multiple birth. One of these 2:
"multipleBirthBoolean" : <boolean>,
"multipleBirthInteger" : <integer>,
"photo" : [{ Attachment }], // Image of the patient
"contact" : [{ // A contact party (e.g. guardian, partner, friend) for the patient
"relationship" : [{ CodeableConcept }], // The kind of relationship
"name" : { HumanName }, // I A name associated with the contact person
"telecom" : [{ ContactPoint }], // I A contact detail for the person
"address" : { Address }, // I Address for the contact person
"gender" : "<code>", // male | female | other | unknown
"organization" : { Reference(Organization) }, // I Organization that is associated with the contact
"period" : { Period } // The period during which this contact person or organization is valid to be contacted relating to this patient
}],
"communication" : [{ // A language which may be used to communicate with the patient about his or her health
"language" : { CodeableConcept }, // R! The language which can be used to communicate with the patient about his or her health
"preferred" : <boolean> // Language preference indicator
}],
"generalPractitioner" : [{ Reference(Organization|Practitioner|
PractitionerRole) }], // Patient's nominated primary care provider
"managingOrganization" : { Reference(Organization) }, // Organization that is the custodian of the patient record
"link" : [{ // Link to a Patient or RelatedPerson resource that concerns the same actual individual
"other" : { Reference(Patient|RelatedPerson) }, // R! The other patient or related person resource that the link refers to
"type" : "<code>" // R! replaced-by | replaces | refer | seealso
}]
}
ご覧のように、実質的に患者のすべての管理情報のニーズに対応しています。
HIS から患者の取得
前回の記事を覚えていれば、HIS システムのデータベースをシミュレーションする PostgreSQL データベースをデプロイしました。特定の HIS にあるサンプルテーブルを確認してみましょう。
多くはありませんが、ここでの例には十分です。 patient テーブルをもう少し詳しく見てみましょう。
ここに、3 つの患者の例があります。ご覧のように、それぞれに一意の識別子(ID)のほか、医療組織に関連性のある一連の管理データがあります。 最初の目標は、1 人の患者の FHIR リソースを取得することです。
患者のクエリ
サーバーから患者データをリクエストするにはどうすればよいでしょうか? FHIR が作成した実装仕様によると、サーバーのアドレス、リソース名、および識別子を使って REST 経由で URL に GET を実行する必要があり、 以下のように呼び出す必要があります。
http://SERVER_PATH/Patient/{id}
この例では、Juan López Hurtado という患者を検索します。この患者の ID は 1 であるため、呼び出す URL は以下のようになります。
http://localhost:52774/Adapter/r4/Patient/1
テストするには、クライアントとして Postman を使用します。 サーバーのレスポンスを確認しましょう。
{
"resourceType": "Patient",
"address": [
{
"city": "TERUEL",
"line": [
"CALLE SUSPIROS 39 2ºA"
],
"postalCode": "98345"
}
],
"birthDate": "1966-11-23",
"gender": "M",
"id": "1",
"identifier": [
{
"type": {
"text": "ID"
},
"value": "1"
},
{
"type": {
"text": "NHC"
},
"value": "588392"
},
{
"type": {
"text": "DNI"
},
"value": "12345678X"
}
],
"name": [
{
"family": "LÓPEZ HURTADO",
"given": [
"JUAN"
]
}
],
"telecom": [
{
"system": "phone",
"value": "844324239"
},
{
"system": "email",
"value": "juanitomaravilla@terra.es"
}
]
}
次に、本番環境内でリクエストが通過した経路を詳しく見てみましょう。
ルートは以下のようになっています。
BS InteropService にリクエストが到着。
BS の宛先として構成し、受信した呼び出しの患者識別子がクエリされる BP に転送。
BO FromAdapterToHIS から HIS データベースにクエリ。
患者データを BP に転送し、それを FHIR Patient リソースに変換。
レスポンスを BS に転送。
BP ProcessFHIRBP で受信するメッセージのタイプを見てみましょう。
クライアントからどの種類のオペレーションをリクエストされたかを特定するための鍵となる 3 つの属性を見てみましょう。
Request.RequestMethod: どの種のオペレーションを実行するかを示します。 この例では、患者の検索は GET になります。
Request.RequestPath: この属性には、サーバーに届いたリクエストのパスが含まれ、操作するリソースを示します。この場合、取得するための具体的な識別子が含まれます。
Quick.StreamId: FHIR Adapter は Stream に届いたすべての FHIR メッセージを変換し、この属性に保存される識別子が割り当てられます。 この例では、GET を実行しており、FHIR オブジェクトを送信していないため、これは必要ありません。
処理を行う GLP を詳しく分析して、メッセージの流れをさらに見ていきましょう。
ProcessFHIRBP:
本番環境に、ビジネスサービスから受け取る FHIR メッセージングを管理する BPL を実装しました。 どのように実装されているか見てみましょう。
ステップごとに実行する操作を見てみましょう。
FHIR オブジェクトの管理:
HIS データベースへの接続とデータベースクエリを処理する BO FromAdapterToHIS を呼び出します。
Method ManageFHIR(requestData As HS.FHIRServer.Interop.Request, response As Adapter.Message.FHIRResponse) As %Status
{
set sc = $$$OK
set response = ##class(Adapter.Message.FHIRResponse).%New()
if (requestData.Request.RequestPath = "Bundle")
{
If requestData.QuickStreamId '= "" {
Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)
set dynamicBundle = ##class(%DynamicAbstractObject).%FromJSON(quickStreamIn)
set sc = ..GetBundle(dynamicBundle, .response)
}
}
elseif (requestData.Request.RequestPath [ "Patient")
{
if (requestData.Request.RequestMethod = "POST")
{
If requestData.QuickStreamId '= "" {
Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)
set dynamicPatient = ##class(%DynamicAbstractObject).%FromJSON(quickStreamIn)
set sc = ..InsertPatient(dynamicPatient, .response)
}
}
elseif (requestData.Request.RequestMethod = "GET")
{
set patientId = $Piece(requestData.Request.RequestPath,"/",2)
set sc = ..GetPatient(patientId, .response)
}
}
Return sc
}
BO は受信した HS.FHIRServer.Interop.Request タイプのメッセージを確認します。この場合は、GET を設定して、Partient リソースに対応するパスに、以下の GetPatient メソッドが呼び出されることを示して確認します。
Method GetPatient(patientId As %String, Output patient As Adapter.Message.FHIRResponse) As %Status
{
Set tSC = $$$OK
set sql="SELECT id, name, lastname, phone, address, city, email, nhc, postal_code, birth_date, dni, gender FROM his.patient WHERE id = ?"
//perform the Select
set tSC = ..Adapter.ExecuteQuery(.resultSet, sql, patientId)
If resultSet.Next() {
set personResult = {"id":(resultSet.GetData(1)), "name": (resultSet.GetData(2)),
"lastname": (resultSet.GetData(3)), "phone": (resultSet.GetData(4)),
"address": (resultSet.GetData(5)), "city": (resultSet.GetData(6)),
"email": (resultSet.GetData(7)), "nhc": (resultSet.GetData(8)),
"postalCode": (resultSet.GetData(9)), "birthDate": (resultSet.GetData(10)),
"dni": (resultSet.GetData(11)), "gender": (resultSet.GetData(12)), "type": ("Patient")}
} else {
set personResult = {}
}
//create the response message
do patient.Resource.Insert(personResult.%ToJSON())
Return tSC
}
ご覧のように、このメソッドは HIS のデータベースにクエリを発行し、すべての患者情報を取得してから後で String に変換されて Adapter.Message.FHIRResponse タイプの変数に格納される DynamicObject を生成します。 後でトレースでレスポンスを表示できるように、Resource プロパティを String リストとして定義しました。 直接 DynamicObjects として定義し、後続の変換を省略することもできます。
Bundle かどうかの確認:
BO からのレスポンスによって、Bundle タイプであるか(今後公開される記事で説明します)単なる Resource であるかをチェックします。
DynamicObject の作成:
BO レスポンスを DynamicObject に変換し、それを一時コンテキスト変数(context.temporalDO)に割り当てます。 変換に使用される関数は以下のとおりです。
##class(%DynamicAbstractObject).%FromJSON(context.FHIRObject.Resource.GetAt(1))
FHIR の変換:
DynamicObject タイプの一時変数を使用して、クラス HS.FHIR.DTL.vR4.Model.Resource.Patient のオブジェクトへの変換を行います。 他のタイプのリソースを探す場合は、タイプごとに特定の変換を定義する必要があります。 では、変換を確認しましょう。
この変換によって、BS InteropService が解釈するオブジェクトが得られます。 結果を context.PatientResponse 変数に格納します。
Stream へのリソースの割り当て:
FHIR の変換で取得した変数 context.PatientResponse を Stream に変換します。
QuickStream への変換:
response 変数に、クライアントに戻す必要のあるすべてのデータを割り当てます。
set qs=##class(HS.SDA3.QuickStream).%New()
set response.QuickStreamId = qs.%Id()
set copyStatus = qs.CopyFrom(context.JSONPayloadStream)
set response.Response.ResponseFormatCode="JSON"
set response.Response.Status=200
set response.ContentType="application/fhir+json"
set response.CharSet = "utf8"
この場合、200 レスポンスを常に返します。 本番環境では、検索されたリソースが正しく取得されたことを確認し、取得されていない場合は、レスポンスのステータスを 200 から「見つかりません」に対応する 404 に変更します。 このコード箇所で見られるように、オブジェクト HS.FHIR.DTL.vR4.Model.Resource.Patient は Stream に変換されて、 HS.SDA3.QuickStream として格納されます。そのオブジェクトの識別子を QuickStreamID 属性に追加されるため、その後 InteropService サービスによって結果を JSON として正しく戻せるようになります。
まとめ:
この記事で行ったことをまとめましょう。
GET タイプのリクエストを送信して、定義された ID を持つ Patient リソースを検索しいました。
BS InteropService は構成済みの BP にリクエストを転送しました。
BP は、HIS データベースを操作する BO を呼び出しました。
構成済みの BO は、HIS データベースから患者データを取得しました。
BP は結果を、デフォルトの InteropService が作成された BS によって理解可能なオブジェクトに変換しました。
BS はレスポンスを受け取り、クライアントに転送しました。
ご覧のように、操作は比較的単純で、より多くのタイプのリソースをサーバーに追加する場合、BO に取得される新しいリソースに対応するデータベースのテーブルにクエリを追加し、BO の結果を対応する HS.FHIR.DTL.vR4.Model.Resource.* タイプのオブジェクトに変換する処理を BP に含めます。
次の記事では、Patient タイプの新しい FHIR リソースを HIS データベースに追加する方法を説明します。
お読みいただきありがとうございました!
記事
So Ochi · 2024年10月13日
# はじめに
生成AIを活用したアプリケーション開発は、Python、JavaScriptなどのメジャー言語による体験記事がよく見られます。一方、IRISのObjectScriptの開発に言及された記事は比較的少ないのが現状です。そこで、本記事では生成AIがObjectScriptの開発にどこまで活用できるのかを検証しました。
特にDevOpsのプロセスにおいて、生成AIは様々なシーンでの活用が期待できます。今回は開発工程に注目し、以下の観点から生成AIの有効性を調査しました。
- 開発
- コードの自動生成
- 環境構築のアシスタント(テーブルの作成)
- 検証
- テストデータ生成のサポート
# 環境
本記事の検証は以下の環境で行いました。
**開発環境**
- OS: macOS Sonoma
- IRIS: 2023.4 (linux)
**開発ツール**
IRISの開発にはStudioやVSCodeなどが利用可能ですが、今回は生成AIの活用に特化したエディタ「Cursor」を使用しました。
**Cursorを選定した理由**
Cursorは、生成AIによる支援機能に特化したコードエディタで、以下の特徴があります:
- 生成AIの支援:コードの自動生成や提案、バグの検出、修正提案を行います。また、外部のドキュメントや複数のソースを指定し、生成内容に反映させる簡易なRAG機能も搭載されています。
- VSCodeとの互換性:VSCodeをフォークして作られており、VSCodeユーザーはスムーズに移行できます。拡張機能もそのまま利用可能です。
IRISではv2024よりStudioは廃止となり、VSCodeが標準ツールとなります。そこで、VSCodeと互換性があり、生成AIとの親和性が高いCursorを選定しました。
**選択した生成AIモデル**
GPT-4を使用しましたが、Claudeでも検証を行ったところ、どのモデルも大差ない結果となりました。
利用しやすいモデルを選んで試してみてください。
# 検証内容
今回は以下の内容を検証しました。
- 簡単なプログラムの生成とターミナルでの実行
- IRISのREST通信機能を使った商品情報検索APIの作成
# サンプルプログラムの作成
まず、ユーザーに名前を入力させ、挨拶を返す簡単なルーチンを生成します。CursorでCommand+Lを押してチャットウィンドウを開き、以下のプロンプトを入力します。
```
あなたはObjectScriptの開発者です。
{仕様}をもとにコードを作成してください。
#仕様
- 以下の処理を実行するRoutineを作成する。
- macファイル名は"TEST.mac"とする。
1. ユーザーに名前の入力を促します。
2. 入力された名前が空であればエラーメッセージを表示します。
3. 名前が入力された場合、その名前を使って挨拶メッセージを表示します。
```

生成されたルーチンをTEST.macにコピーし、ターミナルで実行して動作を確認します。

# REST APIの作成
簡易なプログラムの生成が確認できたので、次はより複雑なアプリケーションを作成してみます。
**アプリケーションの仕様**
IRISのREST通信機能を利用したAPIを作成します。ユーザーからのリクエストに基づき、サーバー上のデータベースから該当する商品情報をJSON形式で返します。
**テーブルの作成**
まずは、テーブルを作成しましょう。DDLの作成をAIに依頼します。
```
InterSystemsのIRISで{テーブル}を作成するためのDDLを出力してください
#テーブル
##名前
- Sample.Product
##列
- JanCd VARCHAR(13)
- ProductName VARCHAR(100)
- Price INT
## プライマリキー
- JanCd
```

生成されたDDLをターミナルから実行し、テーブルが正常に作成されたことを確認します。

**テストデータの作成**
テストデータの作成もAIにアシストしてもらいましょう。

テストデータが正常に登録されたことを確認します。

**APIの作成**
準備が整ったので、APIクラスを生成します。以下のプロンプトを使ってコードを作成します。
```
あなたはObjectScriptの開発者です。
{仕様}をもとに"Api.Product"クラスを作成してください。
#仕様
- REST通信でJSON形式で商品情報を返す。
- apiのURLは/products/:jancd
- %CSP.RESTクラスを継承する。
- Jsonデータの生成は%DynamicObjectクラスを利用する。
- レスポンスコードは、%responseを利用する。
1. jancdが指定されなかった場合、404を返す。指定された場合、以降の処理を続行する。
2. "Sample.Product"テーブルをSQLで検索する。
- 取得項目: JanCd,ProductName,Price
- 検索条件: :jancdが指定された場合、テーブルをJanCdが一致するレコードを検索する。
3. 検索結果をJSON形式でREST通信結果として出力する。
```

生成されたコードを確認し、必要に応じて修正を加えます。クエリ結果の取得箇所で`%Next()`が実行されていなかったため、以下の修正を行いました。
```objectscript
Set tResult = tStatement.%Execute(jancd)
// -*-*-*-*- 処理を追加しました -*-*-*-*-
Do tResult.%Next()
If tResult.%SQLCODE = 100 {
```
curlコマンドを使用してAPIの動作を確認し、正常にJSONデータが返却されることを確認します。

最終的なソースは以下の通りです。今回は一部修正が必要でしたが、基本的な部分は生成AIが正しく対応していました。
```objectscript
Class Api.Product Extends %CSP.REST
{
/// URLマッピング
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
}
/// 商品情報を取得するメソッド
ClassMethod GetProductInfo(jancd As %String) As %Status
{
// jancdが指定されていない場合、404を返す
If jancd = "" {
Set %response.Status = 404
Quit $$$OK
}
// "Sample.Product"テーブルをSQLで検索する
Set tStatement = ##class(%SQL.Statement).%New()
Set tSQL = "SELECT JanCd, ProductName, Price FROM Sample.Product WHERE JanCd = ?"
Set tStatus = tStatement.%Prepare(tSQL)
If $$$ISERR(tStatus) {
Set %response.Status = 500
Quit tStatus
}
Set tResult = tStatement.%Execute(jancd)
Do tResult.%Next()
// 検索結果がない場合、404を返す
If tResult.%SQLCODE = 100 {
Set %response.Status = 404
Quit $$$OK
}
// 検索結果をJSON形式で出力する
Set tProduct = ##class(%DynamicObject).%New()
Do tProduct.%Set("JanCd", tResult.%Get("JanCd"))
Do tProduct.%Set("ProductName", tResult.%Get("ProductName"))
Do tProduct.%Set("Price", tResult.%Get("Price"))
Write tProduct.%ToJSON(),!
Quit $$$OK
}
}
```
# 既存コードの活用
次に、生成したAPIに商品名を部分一致で検索するメソッドを追加します。Cursorの外部ファイル参照機能を使い、先ほど生成したクラスを参考にしながら新しいメソッドを作成します。プロンプトは以下の通りです。
```
@Api.Product.cls
以下の{仕様}で、"Api.Product"クラスにメソッドを追加してください。
#仕様
- GetProductInfo()の処理を参考にすること。
- apiのURLは/products/name/:productname
- Sample.ProductテーブルからAPIで指定された値が、ProductNameと部分一致するレコードを検索し、検索した結果をJson形式で返す。
- 検索結果は配列にして返す。
```

生成したコードは以下になります。(追加部分のみ抜粋)
```objectscript
Class Api.Product Extends %CSP.REST
{
// ... 既存のコード ...
/// URLマッピング
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
// ... 既存のルート ...
}
// ... 既存のコード ...
/// 商品名で商品情報を取得するメソッド
ClassMethod GetProductInfoByName(productname As %String) As %Status
{
// productnameが指定されていない場合、404を返す
If productname = "" {
Set %response.Status = 404
Quit $$$OK
}
// "Sample.Product"テーブルをSQLで検索する
Set tStatement = ##class(%SQL.Statement).%New()
Set tSQL = "SELECT JanCd, ProductName, Price FROM Sample.Product WHERE ProductName LIKE ?"
Set tStatus = tStatement.%Prepare(tSQL)
If $$$ISERR(tStatus) {
Set %response.Status = 500
Quit tStatus
}
Set tResult = tStatement.%Execute("%" _ productname _ "%")
// 検索結果がない場合、404を返す
If tResult.%SQLCODE = 100 {
Set %response.Status = 404
Quit $$$OK
}
// 検索結果をJSON形式で出力する
Set tProducts = ##class(%DynamicArray).%New()
While tResult.%Next() {
Set tProduct = ##class(%DynamicObject).%New()
Do tProduct.%Set("JanCd", tResult.%Get("JanCd"))
Do tProduct.%Set("ProductName", tResult.%Get("ProductName"))
Do tProduct.%Set("Price", tResult.%Get("Price"))
Do tProducts.%Push(tProduct)
}
Write tProducts.%ToJSON(),!
Quit $$$OK
}
// ... 既存のコード ...
}
```
生成されたコードを確認し、curlコマンドで動作を確認します。正常に実行されました。外部ファイル参照機能の利用により、自動生成の精度が向上していることがうかがえます。

# 開発上の課題と解決策
**自動生成の精度向上**
基本コマンドを用いたプログラムは高い精度を発揮しますが、クラスを利用した複雑な処理では精度が低下する印象を受けました。プロンプトの指示を工夫することで、精度を高めることができそうです。
また、Cursorの外部ドキュメント、ファイル参照機能には大きな可能性を感じました。この機能を使えば、既存のリソースや、AIが学習していないライブラリの活用が期待できます。
**セキュリティとプライバシー**
Cursorはプライバシーモードを備えており、データをサーバーに保持しない設定が可能です。しかし、Cursorに限らず生成AIの業務利用には慎重な調査が必要です。
# 所感
今回の検証を通じて、生成AIのコード生成能力が向上していることを実感しました。特に、テストデータやDDLの生成は、開発の効率を大幅に向上させる可能性があります。アジャイル開発で迅速なモックアップの作成が求められる場面では、生成AIの効果的な活用が期待できそうです。一方で、既存システムの小規模な改修には、効果が限定的であるという印象を受けました。
この記事を作成したきっかけは、ObjectScriptの初学者向け演習問題を生成AIで作成した際、その問題と解答の品質が非常に高かったことです。業務での活用も十分可能であると思い、今回の検証を行いました。生成AIは、工夫次第でさらなる幅広い活用が期待できるツールだと感じています。
# 参考資料
- [Cursor公式HP](https://www.cursor.com)
- [VSCodeを使ってみよう](https://jp.community.intersystems.com/post/vscode-を使ってみよう-2021年4月20日版)
- [DockerにIRISを構築する簡単なチュートリアル](https://jp.community.intersystems.com/post/dockerにirisを構築する簡単なチュートリアル)
- [エンジニアにも知って欲しいChatGPT基本テクニック](https://qiita.com/b-mente/items/93ea3d9a4fc33a76b949)
記事
Tomohiro Iwamoto · 2020年6月8日
VMware vSphereで実行する大規模な本番データベースのCPUキャパシティプランニングについて、お客様やベンダー、または社内のチームから説明するように頼まれることが良くあります。
要約すると、大規模な本番データベースのCPUのサイジングには、いくつかの単純なベストプラクティスがあります。
物理CPUコア当たり1つのvCPUを計画する。
NUMAを考慮し、CPUとメモリをNUMAノードに対してローカルに維持できるようVMの理想的なサイズを決定する。
仮想マシンを適正化する。 vCPUは必要な場合にのみ追加する。
このことから、通常いくつかの一般的な疑問が生まれます。
ハイパースレッディングにより、VMwareでは物理CPUの2倍の数でVMを作成できます。 これはキャパシティが2倍になるということか? できるだけ多くのCPUを使ってVMを作成すべきではないのか?
NUMAノードとは? NUMAに配慮する必要があるのか?
VMを適正化する必要があるが、どうすれば適正化されたことがわかるのか?
こういった疑問につては、下の例を使って答えることにします。 ただし、ベストプラクティスは決定事項ではありません。 ときには妥協することも必要です。 たとえば、大規模なデータベースVMはNUMAノードに収まらない可能性が高く、それはそれでも良いのです。 ベストプラクティスは、アプリケーションと環境に合わせて評価・検証する必要のあるガイドラインです。
この記事は、InterSystemsデータプラットフォームで実行するデータベースを例として書いていますが、概念とルールは、一般的にあらゆる大規模(モンスター)VMのキャパシティとパフォーマンスプランニングにも適用されます。
仮想化のベストプラクティスとパフォーマンスとキャパシティ計画に関するその他の記事: 「InterSystemsデータプラットフォームとパフォーマンス」シリーズの「その他の記事」セクションを参照してください。
モンスターVM
この記事は主に、「ワイドVM」と呼ばれることもあるモンスターVMのデプロイについて書いています。 高トランザクションデータベースのCPUリソース要件は、こういったデータベースがモンスターVMにデプロイされることが多い事を意味します。
モンスターVMとは、単一の物理NUMAノードよりも多い仮想CPUまたはメモリを備えたVMです。
CPUアーキテクチャとNUMA
現行のIntelプロセッサアーキテクチャには、NUMA(Non-Uniform Memory Architecture)アーキテクチャがあります。 たとえば、この記事の検証用に使用しているサーバーは次の構成になっています。
CPUソケット2個。各ソケットに12コアのプロセッサを装着(Intel E5-2680 v3)。
256 GBメモリ(16 x 16 GB RDIMM)
各12コアプロセッサには、独自のローカルメモリ(128 GBのRDIMMとローカルキャッシュ)があり、同じホスト内のほかのプロセッサのメモリにもアクセスできます。 CPU、CPUキャッシュ、および128 GB RDIMMメモリを1つのパッケージとした12コアをそれぞれNUMAノードと呼びます。 別のプロセッサのメモリにアクセスするために、NUMAノードは高速インターコネクトで接続されます。
ローカルRDIMMとキャッシュメモリにアクセスするプロセッサで実行するプロセスは、別のプロセッサのリモートメモリにアクセスするためにインターコネクトを経由する場合に比べ、レイテンシが低くなります。 インターコネクト経由のアクセスではレイテンシが高くなるため、パフォーマンスが一定しません。 同じ設計は3つ以上のソケットを持つサーバーにも適用されます。 ソケットが4つあるIntelサーバーには、4つのNUMAノードがあります。
ESXiは物理NUMAを認識し、ESXi CPUスケジューラはNUMAシステムのパフォーマンスを最適化するように設計されています。 ESXiがパフォーマンスを最大化する方法の1つは、物理NUMAノードにデータの局所性を作成することです。 この記事の例では、vCPUが12でメモリが128 GB未満のVMがある場合、ESXiはそのVMを物理NUMAノードの1つで実行するように割り当てます。 このことから次のルールについて言うことができます。
可能であれば、CPUとメモリをNUMAノードに対してローカルに維持できるようにVMのサイズを決定する。
NUMAノードより大きなモンスターVMが必要な場合は、それでもかまいません。ESXiは非常に優れた機能によって、要件を自動的に計算して管理することができます。 たとえば、ESXiは、最適なパフォーマンスを得るために、物理NUMAノードにインテリジェントにスケジュールする仮想NUMAノード(vNUMA)を作成します。 vNUMA構造は、オペレーティングシステムに公開されます。 たとえば、2つの12コアプロセッサを搭載したホストサーバーと16 vCPUを備えたVMがある場合、ESXiは8つの物理コアを2つのプロセッサに使用して、VM vCPUをスケジュールするため、オペレーティングシステム(LinuxまたはWindows)に2つのNUMAノードが表示されます。
また、VMを適正化して、必要以上のリソースを割り当てないようにすることも重要です。リソースが無駄になり、パフォーマンスが低下する可能性があります。 VMを適正化すればNUMAのサイズ設定にも役立つだけでなく、特に低から中程度のCPU使用率のある24 vCPUのVMよりもCPU使用率の高い(ただし安全な)12 vCPUのVMの方が、より効率的でパフォーマンスの改善が得られます。特にこのホストに、スケジュールされる必要がありリソースを争っているほかのVMがある場合はなおさらです。 このこともルールをさらに強化する要因といえます。
仮想マシンを適正化する。
注意: NUMAのIntel実装とAMD実装には違いがあります。 AMDには、プロセッサごとに複数のNUMAがあります。 顧客のサーバーでAMDプロセッサを見たのはしばらく前になりますが、それを使用している場合は、計画の一環としてNUMAのレイアウトを確認してください。
ワイドVMとライセンス
最適なNUMAスケジュール設定を得る、ワイドVMの構成は 2017年6月訂正: ソケットあたり1 vCPUでVMを構成します。 たとえば、24 vCPUのVMは、デフォルトでそれぞれが1つのコアを持つ24 CPUソケットとして構成されます。
VMwareのベストプラクティスのルールに従ってください。
例については、VMwareブログに記載のこちらの記事を参照してください。
VMwareのブログ記事には詳細が説明されていますが、これを執筆したMark Achtemichukは次の経験則を推奨しています。
高度なvNUMA設定はたくさんあるが、デフォルトの設定を変更しなければいけないのは、まれなケースのみ。
仮想マシンvCPU数は、単一の物理NUMAノードの物理コア数を超えるまでは、必ず、ソケットあたりのコア数が反映されるように構成すること。
NUMAノードにある物理コア数以上のvCPUを構成する必要がある場合は、最少数のNUMAノード間でvCPU数を均一に分割すること。
仮想マシンのサイズが単一の物理NUMAノードを超える場合、奇数のvCPU数を割り当てないこと。
vCPUホットアド(Hot-add)は、vNUMAを無効にしてもかまわない場合にのみ有効にすること。
ホストの物理コアの合計数以上に大きいVMを作成しないこと。
Cachéライセンスはコアを計数するため問題ではありませんが、Caché以外のソフトウェアやデータベースの場合は、VMに24ソケットを指定するとソフトウェアライセンスに違いが生じる場合があるため、ベンダーと確認する必要があります。
ハイパースレッディングとCPUスケジューラ
ハイパースレッディング(HT)の話が持ち上がるとよく聞くのが「ハイパースレッディングはCPUコア数を2倍にする」ということです。 これは明らかに物理的に可能なことではありません。コア数が物理的に増えたり減ったりすることはありえません。 ハイパースレッディングは有効にすべきものであり、有効になればシステムのパフォーマンスを向上させることができます。 期待値はおそらくアプリケーションパフォーマンスにおいて20%増かもしれませんが、実際の量はアプリケーションとワークロードによって決まります。 が、それでも2倍ではありません。
VMwareのベストプラクティスの記事で説明したように、大規模な本番データベースVMをサイジングするには、vCPUに完全専用の物理コアがサーバーにあると仮定することから始まります。基本的に、キャパシティ計画を実施する際はハイパースレッディングを無視するということです。 たとえば:
24コアのホストサーバーの場合、利用可能なヘッドルームがあることを考えて、本番データベースに合計で最大24 vCPUを計画します。
ピーク処理期間のアプリケーション、オペレーティングシステム、およびVMwareのパフォーマンスをしばらく監視したら、より高い統合が可能であるかどうかを判断することができます。 ベストプラクティスの記事では、ルールを次のように述べました。
1つの物理CPU(ハイパースレッディングを含む)= 1つのvCPU(ハイパースレッディングを含む)
ハイパースレッディングでCPUが2倍にならない理由
Intel Xeonプロセッサ上のHTは、1つの物理コア上に2つの論理CPUを作成する方法です。 オペレーティングシステムは、2つの論理プロセッサに対して効率的にスケジュールを設定できます。論理プロセッサ上のプロセスまたはスレッドがIOなどを待機している場合、物理CPUリソースは、別の論理プロセッサによって使用されます。 ある時点において進行できるのは1つの論理プロセッサのみであるため、物理コアがより効果的に使用されていても、パフォーマンスは2倍にならないのです。
ホストBIOSでHTが有効化されている場合、VMを作成する際に、HT論理プロセッサあたりのvCPUを構成することができます。 たとえば、HTが有効になっている24物理コアのサーバーで、最大48 vCPUのVMを作成することができます。 ESXi CPUスケジューラは、最初に個別の物理コアで(NUMAを考慮しながら)VMプロセスを実行することにより、処理を最適化します。 モンスターデータベースVMに物理コア数以上のvCPUを割り当てるとスケーリングにメリットがあるのかについては、この記事の後の方で説明することにします。
co-stopとCPUスケジューリング
ホストとアプリケーションのパフォーマンスを監視した後、ホストCPUリソースのオーバーコミットが可能であると判断する場合があります。 これがよいことかどうかは、アプリケーションとワークロードに大きく依存します。 スケジューラと監視すべき重要なメトリックを理解することで、ホストリソースをオーバーコミットしていないことを確認できます。
たまに、VMが進行するには、VMのvCPUと同じ数の空き論理CPUが必要だということを聞くことがあります。 たとえば、12 vCPUのVMは、実行が進む前に12個の論理CPUが「利用可能」になるまで「待機」する必要があります。 ただし、バージョン3以降のESXiではそうでないことに注意してください。 ESXiは、アプリケーションのパフォーマンスを向上させるために効率的なスケジューリングを使用しています。
複数の協調動作するスレッドまたはプロセスは頻繁に同期するため、一緒にスケジューリングしない場合、操作のレイテンシが増加する可能性があります。 たとえば、スピンループ内で別のスレッドがスケジュールされるのを待機しているスレッドです。 最良のパフォーマンスを得るために、ESXiはできる限り多くの兄弟vCPUを一緒にスケジュールしようとします。 ただし、CPUスケジューラは、統合環境で複数のVMがCPUリソースを争う場合に、vCPUを柔軟にスケジュールすることができます。 兄弟vCPUが進行しないにも関わらず一部のvCPUが進行してしまうと大きな時間差(これをスキューと呼ぶ)が生まれてしまうため、この場合は先行のvCPUがそれ自体を停止するかどうかを決定します(co-stop)。 co-stop(またはco-start)するのは、VM全体ではなくvCPUであることに注意してください。 これは、リソースのオーバーコミットがある場合でもうまく機能しますが、CPUのオーバーコミットが多すぎると必然的にパフォーマンスに影響します。 後の例2で、オーバーコミットとco-stopの例を示します。
これは、VM間のCPUリソースの争奪戦ではありません。ESXiのCPUスケジューラの仕事は、CPUのシェア数、予約、限界といったポリシーを確実に守りながら、CPU使用率を最大化し、公平性、スループット、応答性、スケーラビリティを確保することにあります。 予約とシェアを使用して本番ワークロードに優先順を付ける話はこの記事の目的から外れてしまい、アプリケーションやワークロードの組み合わせによって異なります。 Caché固有の推奨事項がある場合は、これについて後で触れるかもしれません。 CPUスケジューラにはさまざまな要因がかかわっているため、このセクションではその表面をざっと紹介しています。 詳細については、この記事の最後に記載する参考情報にあるVMwareのホワイトペーパーやその他のリンクを参照してください。
例
さまざまなvCPU構成を説明するために、高トランザクションレートのブラウザベースの病院情報システムアプリケーションを使って、一連のベンチマークを実行しました。 VMwareが作成したDVDストアデータベースベンチマークの概念に似ています。
ベンチマークのスクリプトは、ライブの病院実装から得た観察とメトリックを基に作成されており、使用率の高いワークフロー、トランザクション、およびシステムリソースを最も使用するコンポーネントが含まれています。 ほかのホスト上のドライバーVMは、ランダム化された入力データを使用したスクリプトを設定されたワークフロートランザクションレートで実行することで、Webセッション(ユーザー)をシミュレートします。 レート 1x のベンチマークがベースラインです。 レートは段階的に増減できます。
データベースとオペレーティングシステムのメトリックに加え、ベンチマークデータベースVMのパフォーマンスを測定するのに適したメトリックは、サーバーで測定されるコンポーネント(またはトランザクション)の応答時間です。 コンポーネントとは、エンドユーザー画面の一部分などを指します。 コンポーネントの応答時間が増加すると、ユーザー側においてはアプリケーションの応答時間に悪影響がでます。 パフォーマンスの高いデータベースシステムは、一貫した高いパフォーマンスをエンドユーザーに提供できるものでなければなりません。 次のグラフでは、最も遅く使用頻度の高いコンポーネントの応答時間の平均を算出し、一貫したテストパフォーマンスとエンドユーザーエクスペリエンスの指標を測定しています。 平均的なコンポーネント応答時間は1秒未満であり、ユーザー画面は、1つのコンポーネント、または複雑な画面の場合は多くのコンポーネントで構成されています。
常にピーク時のワークロードと、アクティビティの予定外の急増に対応するためのバッファに対してサイジングを行っていることを覚えておきましょう。 私の場合は通常、平均80%のピークCPU使用率を目指しています。
ベンチマークのハードウェアとソフトウェアの全リストはこの記事の最後にあります。
例1. 適正化 - ホストあたり1つのモンスターVM
たとえば、24個の物理コアホスト上の24 vCPUのVMといったように、ホストサーバーのすべての物理コアを使用できるようにサイジングされたデータベースVMを作成することができます。 HAを得るためにCachéデータベースミラーで「ベアメタル」サーバーを実行したりオペレーティングシステムのフェイルオーバークラスタリングといった複雑なものを導入したりせずとも、データベースVMは、DRSやVMware HAなどの管理・HA向けvSphereクラスタに含まれます。
古い考え方で、5年後のハードウェア使用期間の最後に期待されるキャパシティに対してプライマリデータベースVMのサイズを決定するお客様を見てきましたが、上記で説明したように、適正化する方が有効と言えます。VMのサイズが過大に設定されていなければ、パフォーマンスと統合が改善され、HAの管理もより簡単になります。メンテナンスやホスト障害が発生した場合は、テトリスのように、データベースのモンスターVMを別のホストに移行して、そこで再起動しなければなりません。 トランザクションレートが大幅に増加すると予測される場合は、定期メンテナンス中に前もってvCPUを追加することができます。
「ホットアド」CPUオプションはvNUMAを無効にするため、モンスターVMには使用しないでください。
24コアホストでの一連の検証を示す次のグラフを考察してみましょう。 3x トランザクションレートは、この24コアシステムのスイートスポットであり、キャパシティプランニングの目標です。
ホストでは単一のVMが実行している。
12、24、36、48 vCPUでのパフォーマンスを示すために4つのVMサイズを使用している。
各VMサイズに対し、トランザクションレート(1x、2x、3x、4x、5x)が実行された(可能な場合)。
コンポーネント応答時間としてパフォーマンス/ユーザーエクスペリエンスを示している(棒グラフ表示)。
平均CPU%使用率はゲストVM(線グラフ表示)
すべてのVMサイズのホストCPU使用率は、4x レートで100%に達した(赤い破線)。
このグラフには多くのことが示されていますが、2つのことに注目できます。
24 vCPUのVM(オレンジ)は、目標の3x トランザクションレートにスムーズにスケールアップしています。 3x レートにおいては、ゲスト内のVMは平均76%CPUに達成しています(ピーク時は約91%)。 ホストCPUの使用率はゲストVMとあまり変わりません。 コンポーネント応答時間は3xまでほぼ一定しているため、ユーザーは満足しています。 目標のトランザクションレートに関して言えば、このVMは適正化されていると言えます。
適正化についてはわかりましたが、vCPUの増加についてはどうでしょうか。これは、ハイパースレッディングを使用するということです。 パフォーマンスとスケーラビリティを2倍にすることは可能でしょうか。 短く言えば、答えは「ノー!」です。
この場合、この答えは、4x 以降のコンポーネント応答時間を見るとわかります。 割り当てられる論理コア数(vCPU)が多いほどパフォーマンスは「向上」しますが、一定しておらず、3x までのような一貫性がありません。 割り当てられるvCPU数が増えても、ユーザーは4x での応答時間が遅くなっていることを報告するでしょう。 vSphereが示したように、4xでは、ホストはすでに100%CPU使用率で横ばいになっていたことを思い出しましょう。 vCPU数が高い場合、ゲスト内CPUメトリック(vmstat)が100%使用率未満を示していたとしても、これは物理リソースには当てはまりません。 ゲストオペレーティングシステムは仮想化されていることを認識しないため、それに与えられたリソースに対して示しているだけであることに注意してください。 また、ゲストオペレーティングシステムはHTスレッドも認識しないため、すべてのvCPUは物理コアとして示されます。
ポイントは、データベースプロセス(3x トランザクションレートのCachéプロセスは200個以上ある)は非常にビジーであり、プロセッサを非常に効率的に使用しますが、ほかの作業をスケジュールするかこのホストにほかのVMを統合するための論理プロセッサにはあまり余裕がないということです。 たとえば、Caché処理の大部分はメモリ内で行われるため、IOの待機時間はあまりありません。 そのため、物理コアより多いvCPUを割り当てることはできますが、ホストの使用率がすでに100%に達しているため、あまり得がないのです。
Cachéは高いワークロードの処理に非常に優れています。 ホストやVMが100%のCPU使用率に達していても、アプリケーションは実行し続けており、トランザクションレートも増加し続けます。スケーリングは線形ではなく、応答時間が長引けば、ユーザーエクスペリエンスに影響があります。ただし、アプリケーションが「崖から落ちる」わけではなく、快適な場所でないにしても、ユーザーは作業を続行できるでしょう。 応答時間の影響をそれほど受けないアプリケーションであれば、限界まで押したとしても、Cachéは安全に動作し続けます。
データベースVMやホストを100%CPUで実行するのは好ましいことではないことを覚えておいてください。 VMには予定外の急増や予測していなかった増加に対応できるキャパシティが必要であり、ESXiハイパーバイザーには、すべてのネットワーキング、ストレージなどでのアクティビティに使用するリソースが必要です。
私は常にピーク時のCPU使用率を80%で計画するようにしていますが、 それでも、vCPUのサイズを物理コア数までに設定するようにし、極端な状況でも論理スレッド上にESXiハイパーバイザー用のヘッドルームをいくらか残すようにしています。
ハイパーコンバージド(HCI)ソリューションを実行している場合は、HCIのCPU要件もホストレベルで考慮する必要があります。 詳細は、HCIに関する前の記事を参照してください。 HCIにデプロイされたVMの基本的なCPUサイジングは、ほかのVMと変わりません。
独自の環境とアプリケーションですべての設定を確認・検証する必要があることを忘れないでください。
例2. オーバーコミットされたリソース
アプリケーションパフォーマンスは「遅い」にもかかわらず、ゲストオペレーティングシステムではCPUリソースに余裕があることが示される顧客サイトを見たことがあります。
ゲストオペレーティングシステムは仮想化を認識しないことに注意してください。 残念ながらvmstat(pButtonsの場合)が示すゲスト内のメトリックは当てにならないことがあるため、システムの状態とキャパシティを正確に理解するには、ホストレベルのメトリックとESXiのメトリック(esxtopなど)も取得する必要があります。
上のグラフからもわかるように、ホストが100%使用率を示す場合、ゲストVMの使用率は低く示されることがあります。 36 vCPUのVM(赤)は4x レートで80%の平均CPU使用率を示していますが、ホストでは100%となっています。 公開後にほかのVMがホストに移行した場合や、きちんと構成されていないDRSルールによってリソースがオーバーコミットされている場合など、適正化されたVMでもリソース不足となることがあります。
主なメトリックを示すために、この一連の検証では以下のように構成しました。
ホストで実行する2つのデータベースVM
一定の2xトランザクションレートで実行する24vCPU(グラフには表示されていません)
1x、2x、3xレートで実行する24vCPU(グラフに3つのメトリックが表示されています)
リソースを使う別のデータベースでは、3x レートにおけるゲストOS(RHEL 7)のvmstatは86%の平均CPU使用率を示しており、実行キューは平均25です。 ただし、このシステムのユーザーは、プロセスが遅くなるにつれコンポーネント応答時間が急増するため、大きな不満を訴えると考えられます。
次のグラフが示すように、co-stopと準備時間からユーザーパフォーマンスがこれほど悪い理由を読み取ることができます。 準備時間(%Ready)とco-stop(%CoStop)メトリックは、CPUリソースが目標の3xレートで大幅にオーバーコミットされていることを示しています。 ホストの実行レートが2x(other VM)、かつこのデータベースVMは3xレートであるわけですから、特に驚くことでもないでしょう。
このグラフは、ホスト上の合計CPU負荷が増加すると準備時間が増加することを示しています。
準備時間とは、VMの実行準備は整っているが、CPUリソースが利用できないために実行できない時間です。
co-stopも増加します。 データベースVMを進行させられるのに十分な空き論理CPUがありません(上記のHTの項目で説明しました)。 その結果、物理CPUリソースの競合により処理が遅延しています。
pButtonsとvmstatのサポートビューに、仮想オペレーティングシステムのみが表示されるという状況を顧客サイトで見たことがあります。 vmstatはCPUヘッドルームを示していましたが、ユーザーのパフォーマンスエクスペリエンスはひどいものでした。
ここでの教訓は、ESXiメトリックとホストレベルビューが利用できるようになるまで実際の問題が診断されなかったということです。一般的なクラスタCPUリソース不足によってCPUリソースのオーバーコミットが発生し、状況を悪化すべく、粗末なDRSルールによって高トランザクションデータベースVMがまとめてホストリソースに移行され、ホストリソースを圧迫していたのです。
例3. オーバーコミットされたリソース
この例では、3xトランザクションレートで実行する24 vCPUデータベースVMをベースラインとし、一定した3xトランザクションレートで2つの24 vCPUデータベースVMを使用しました。
平均ベースラインCPU使用率(上の例1を参照)は、VMが76%でホストが85%でした。 1つの24 vCPUデータベースVMが24個のの物理プロセッサをすべて使用しています。 2つの24 vCPU VMを実行すると、VMはリソースを争い、サーバーで48個の論理実行スレッドすべてを使用することになります。
単一のVMではホストは100%使用されていませんでしたが、2つの非常にビジーな24 vCPU VMがホスト上の24個の物理コアを使用しようとすると(HTを使用してでさえも)、スループットとパフォーマンスが大幅に低下するのがわかります。 Cachéは利用可能なCPUリソースを非常に効率よく使用できますが、VMあたりのデータベーススループットは16%低下し、さらに重要なことに、コンポーネント(ユーザー)応答時間が50%以上増加しています。
最後に
この記事の目的は、一般的な疑問にお答えすることでした。 CPUホストリソースとVMware CPUスケジューラの詳細については、下の参考情報を参照してください。
システムの最後の一滴までパフォーマンスを絞り出す、特別に高度な調整やESXi設定がたくさんありますが、基本的なルールは非常にシンプルです。
大規模な本番データベースの場合:
物理CPUコアあたり1 vCPUで計画する。
NUMAを考慮し、CPUとメモリをNUMAノードに対してローカルに維持できるようVMの理想的なサイズを決定する。
仮想マシンを適正化する。 vCPUは必要な場合にのみ追加する。
VMを統合する場合は、大規模なデータベースは非常にビジーであり、ピーク時にCPU(物理と論理)を大量に使用することに注意してください。 監視で安全だということがわかるまで、オーバーサブスクライブしてはいけません。
参考情報
VMwareブログ「When to Overcommit vCPU:pCPU for Monster VMs」
Introduction 2016 NUMA Deep Dive Series
The CPU Scheduler in VMware vSphere 5.1
検証について
この記事で使用した例は、オールフラッシュアレイに接続された2つのDell R730プロセッサで構成されるvSphereクラスタで実行しました。 例では、ネットワークまたはストレージにボトルネックはありませんでした。
Caché 2016.2.1.803.0
PowerEdge R730
2x Intel(R) Xeon(R) CPU E5-2680 v3 @ 2.50 GHz
16x 16 GB RDIMM、2133 MT/秒、デュアルランク、x4 データ幅
SAS 12 Gbps HBA外部コントローラ
ハイパースレッディング(HT)有効
PowerVault MD3420、12G SAS、2U-24ドライブ
24x 24 960 GBソリッドステートドライブSAS読み取り処理集中型MLC 12 Gbps 2.5インチホットプラグドライブ、PX04SR
2 コントローラー、12G SAS、2U MD34xx、8Gキャッシュ
VMware ESXi 6.0.0ビルド2494585
VMはベストプラクティスに合わせて構成されています。VMXNET3、PVSCSIなど
RHEL 7
LargePages
ベースラインの1x レートの平均は700,000 glorefs/秒(データベースアクセス/秒)でした。 5x レートの平均は、24 vCPUで3,000,000 glorefs/秒超でした。 検証では、一貫したパフォーマンスが達成されるまで慣らし運転を行い、その後で15分間、サンプルを取得し平均を算出しました。
これらの例は理論を説明することだけを目的としているため、独自アプリケーションでは必ず検証する必要があります。
また、前の記事「InterSystemsデータプラットフォームとパフォーマンス - VMバックアップとCachéのFreeze/Thawスクリプト」もお読みください。
記事
Kosaku Ikeda · 2023年10月2日
皆さまこんにちは。IRIS for Healthを用いてFHIRの開発に携わっている者です。
FHIRリポジトリの導入を検討している方々に向けて、足がかり的な記事になればと思い投稿致します。
<アジェンダ>■IISでの環境構築■POSTMANを利用しないリソースへのデータアクセス■Patientリソースの作成について■FHIRリポジトリを使ってみての感想
■おまけEmbedded Pythonを使って、サンプルファイルからFHIRリソースへアクセスする方法
■IISでの環境構築
以前IISを利用しての環境構築を投稿しましたが、少し詳細に記載致します。
①FHIR endpointの作成
「URL」は極力短い名前が良いと考えます(例:「/test」)
②IISの設定
「エイリアス」をendpointの「URL」に揃える(例:「test」)
「物理パス」は任意
ハンドラーマッピング
「要求パス」は「*」 「モジュール」はCSPms 「名前」は任意 「要求の制限」では、「マップ」タブのチェックボックスをOFF
■POSTMANを利用しないリソースへのデータアクセス
Web APIの設計・開発・テストを行う際に、最初に候補として名前が挙がるのがPOSTMANだと思います。 とても使いやすく、機能が充実しているため開発・テストはこれのみで問題無いレベルです。
https://www.postman.com/
ただ今回は折角なのでPOSTMANを使用せず、ObjectscriptでFHIRリソースの登録・取得を確認する方法を記載致します。 FHIR機能とinteroperabilityを組み合わせる際も、Objectscriptで記述出来る方法を知っておいた方が色々都合が良いと考えます。
コードの書き方は、下記ドキュメントに記載されています。
https://docs.intersystems.com/irisforhealth20231/csp/docbookj/DocBook.UI.Page.cls?KEY=HXFHIR_server_applications
上記関数以外にも簡易的に使用できる関数があるので、そちらを利用して患者リソースの登録・更新・取得・検索・削除を行ってみます。
サンプルソース:https://github.com/ikeda-kou/writingContest.git
※GetEndpoint()・・・エンドポイント取得関数
JsonAnalysis()・・・jsonを見やすくパースする関数
【登録】
Create()関数の第二引数は、FHIRリソース(例:Patientリソース)で%Stream型か%String型の何れかを受け取ります
ClassMethod POSTTest()
{
s clientObj = ##class(HS.FHIRServer.RestClient.FHIRService).CreateInstance(..GetEndpoint())
s res = clientObj.Create("Patient", ..GetPatient().%ToJSON())
w !,$$$FormatText("id=%1, status=%2", res.Id, res.Status)
}
※テストデータは、jpcoreのサンプルデータを使用しています。
ClassMethod GetPatient() As %DynamicObject [ CodeMode = expression ]
{
{
"resourceType" : "Patient",
"meta" : {
"profile" : [
"http://jpfhir.jp/fhir/core/StructureDefinition/JP_Patient"
]
},
~~ 略 ~~
"address" : [
{
"text" : "東京都新宿区",
"postalCode" : "1600023"
}
]
}
}
登録したデータは^HSFNNS.PatientDに登録されます。
→「03」はエンドポイントの作成回数との事
【更新】
同じリソースIDで更新データを作成します。
→サンプルは苗字を山田→海田へ変更する
ClassMethod PUTTest(id As %String)
{
s clientObj = ##class(HS.FHIRServer.RestClient.FHIRService).CreateInstance(..GetEndpoint())
s json = ..GetPatient()
s json.id = id
s json.name."0".family = "海田"
s res = clientObj.Update("Patient", id, json.%ToJSON())
w !,$$$FormatText("id=%1, status=%2", res.Id, res.Status)
}
【取得】
リソースIDを使用して、データの取得を行います。
戻り値の「.Json」に取得したリソースがJSON形式で格納されています。
ClassMethod GETTest(id As %String)
{
s clientObj = ##class(HS.FHIRServer.RestClient.FHIRService).CreateInstance(..GetEndpoint())
s res = clientObj.Read("GET", "Patient", id)
d ..JsonAnalysis(res.Json)
}
↓ターミナルでの実行サンプル
【検索】
検索を行い、Bundleリソースとして結果を受け取ります。
ClassMethod SearchTest(patientId As %String)
{
s clientObj = ##class(HS.FHIRServer.RestClient.FHIRService).CreateInstance(..GetEndpoint())
s res = clientObj.Search("GET", "Patient", , , "identifier="_patientId)
d ..JsonAnalysis(res.Json)
}
【削除】
指定したリソースIDを削除します。
ClassMethod DELETETest(id As %String)
{
s clientObj = ##class(HS.FHIRServer.RestClient.FHIRService).CreateInstance(..GetEndpoint())
s res = clientObj.Delete("Patient", id)
w !,res.Status
}
上記関数が無事に実行できれば、Interoperabilityのオペレーション「HS.FHIRServer.Interop.Operation」にメッセージを投げても問題無く動作します。
※これで、InteroperabilityでのFHIR実装がスムーズに行えると思います。
■Patientリソースの作成について IRIS内でPatientリソースデータを作成する方法は、私の知っている中では下記3つの方法があります。
FHIRTempleteを利用する
自力でJSONを記述する (前述したGetPatient()メソッド等)
HS.FHIR.DTL.vR4.Model.Resourceのクラスを利用する
1. FHIRTempleteを利用する
https://github.com/Intersystems-jp/JSONTemplate
公式ではこちらを利用しての運用をすすめているようです。
→メンテナンス性が高く、packageのバージョンアップにも対応し易い
しかし、FHIRTemplate.Patient.clsをそのまま利用するのであれば、サンプルデータの「extension」を入力するフィールドがありません。 「extension」を出力するのであれば、下記何れかの方法を行う必要があります。
JSON化した後、追加を行う。
FHIRTemplate.Patient.clsをExtendして、継承したクラスにextensionプロパティを追加する
FHIRTemplate.Resource.clsをExtendして新規Patientクラスを作成する
3. HS.FHIR.DTL.vR4.Model.Resourceのクラスを利用する
少々癖が強いものの、簡易的な利用であればボチボチ使えるのでサンプルを作成してみました。
R4用のパッケージなので、今後のアップデートを踏まえてスキーマを変数としています。
関数の最後にある「ToJSON」関数を実行すると、戻り値として%Stream型のPatientリソースが返ってきます。
ClassMethod MakePatient(schema As %String, ver As %String) As %Stream.TmpCharacter
{
s o = $ClassMethod(schema_"Resource.Patient", "%New")
// meta
s o.meta = $ClassMethod(schema_"Base.Meta", "FromJSONHelper", {
"profile" : [ "http://jpfhir.jp/fhir/core/StructureDefinition/JP_Patient" ]
}, ver)
~~ 略 ~~
// address
d o.address.Insert($ClassMethod(schema_"Datatype.Address", "FromJSONHelper", {
"text" : "東京都新宿区",
"postalCode" : "1600023"
}, ver))
return o.ToJSON()
}
戻り値をさらにJSON型に変換する場合は、下記コマンドを実行する。
【使用した所感】
両者を使用した所感を下記に記載致します。 ※あくまで主観で記載しているので、参考程度に捉えていただけると幸いです。
HS.FHIR.DTL.vR4.Model.Resourceパッケージ メリット ・リソースを作成するクラスとしては完成しているので、そのままで使用可能 ・各プロパティの型が決まっている為、コーディングミスやマッピングミスが減る
デメリット ・パッケージのバージョンアップ対応が手間かも ・コーディング量が多い為可読性が下がる
FHIRTemplateパッケージ メリット ・メンテナンス性が高く自由な調整が可能 ・コーディング量が少なく可読性が高い
デメリット ・カスタマイズが必要になるリソースが多いイメージ
■FHIRリポジトリを使ってみて
FHIR環境の構築と聞いて、二の足を踏んでしまう方もいらっしゃるかと思います。 しかしIRISを使う事で、下記FHIRで必要な環境が容易に構築する事が可能となります。 ・FHIRリポジトリ、FHIRサーバ等々 ・REST API ・機器接続等(interopearbility[相互運用])
実際に作業を行ってみると、FHIR環境の導入へのハードルがかなり低いと感じました。 # むしろFHIRリソースへのマッピング作業の方がハードルが高く感じます。
ドキュメントもIRISのバージョンが上がるごとにFHIR関連の記事が増加しており、充実感を覚えます。 ※ただし、管理ポータルのHealth系画面は英語で構成されおり、ドキュメントはそれらを全て翻訳しているため、項目名に差異が発生しています。 多少の慣れが必要。今後の修正に期待致します。
■おまけEmbedded Pythonを使って、サンプルファイルにアクセスしFHIRリソースへアクセスする方法
<アジェンダ>
■準備
■Embedded Python実行
■所感
■準備
先ず管理ポータルからサービスを1つ有効にする必要があります。
[システム管理] > [セキュリティ] > [サービス] %Service_CallInの「サービス有効」「パスワード」にチェックを入れます。
サンプルファイルを[IRISインストールディレクトリ]\lib\python 直下に配置します。
[IRISインストールディレクトリ]\bin にてコマンドプロンプトを立ち上げ、下記を入力します。
set IRISUSERNAME=<user_name> set IRISPASSWORD=<password> set IRISNAMESPACE=<name_space>
■Embedded Python実行 全ての準備が終わったら、サンプルファイルを実行します。
コマンドは下記になります。
irispython D:\IRIS\lib\python\fhir.py GETTest -i 2276
■所感
そもそもRest APIを使用してFHIRリポジトリが構築できるので、直接Embedded Pythonを使用してFHIRリポジトリへアクセスする機会はないかもしれません。
ただ、このような簡単な手順でPythonとIRISを連携させる事ができます。
夢が広がりますね。
※ただし、pythonは3.9.5で固定されている為(IRIS 2023.1.1.380)、バージョン管理は注意が必要です。
こんにちは。初めまして。こちらの投稿について、1点質問がございます。REST APIを作成して、FHIRリポジトリからPatient情報を取得する際、登録されている全リソースをGETすることは可能でしょうか。Documentationで記載されていたGETのコード(※)では、指定したIDのリソースしか登録できなかったので、全件取得する方法があるのかと疑問になり質問させていただきました。※https://docs.intersystems.com/irisforhealth20231/csp/docbookj/DocBook.UI.Page.cls?KEY=HXFHIR_server_applicationsよろしくお願いいたします。 ご質問ありがとうございます。
回答が遅くなり大変申し訳ありません。
私の記憶している範囲ですが、GETでも名称検索等で複数リソースを取得する事は可能です。
→サンプルでは、「Search」が該当しており、検索文言の変更で全リソースを対象にすれば良いと思います。
→Documentのサンプルソースで対応するのであれば、下記になります。
set request.RequestPath = "/Patient" // リソースIDは省略
set request.QueryString = "xxx" // 全リソースを検索対象とする検索文言を設定
しかし、最大取得リソース数が設定されている為、その件数を超えて取得する事は出来ません。
→エンドポイントの設定の「Max Search Results」の設定値に依存していたと記憶しています。
全ての患者リソースを取得するのであれば、Bulk FHIR Coordinatorの機能を使うと実現できるかもしれません。
※私もこの機能は使ったことがないので、求める回答になっていなかったら申し訳ありません。
【以下は非推奨です!】
※若しくは・・・ですが、SQLで「HSFXXS.Patient」を検索する・・・方法もあります。
エンドポイントの作成回数でテーブル名の「XX」が変わるので、適宜対応できるよう工夫すれば実現可能かと思います ご回答ありがとうございます。また、返信ができておらず申し訳ございません。
私の記憶している範囲ですが、GETでも名称検索等で複数リソースを取得する事は可能です。
→サンプルでは、「Search」が該当しており、検索文言の変更で全リソースを対象にすれば良いと思います。
こちらの内容を実際に試してみたところ、求めていたことを実現することができました。ありがとうございました。
記事
Toshihiko Minamoto · 2021年8月31日
[最初の記事](https://jp.community.intersystems.com/node/501166)では、Caché Webアプリケーションのテストとデバッグを外部ツールを用いて行うことについて説明しました。 2回目となるこの記事では、Cachéツールの使用について説明します。
以下について説明します。
* CSP GatewayとWebappの構成
* CSP Gatewayのロギング
* CSP Gatewayのトレース
* ISCLOG
* カスタムロギング
* セッションイベント
* デバイスへの出力
### CSP GatewayとWebappの構成
まず初めに、フロントエンドアプリケーションをデバッグする場合、特にそれを開発している場合は、キャッシュは必要ありません。 本番システムでは役立ちますが、開発中には不要です。 Webアプリケーションのロギングを無効にするには、システム管理ポータル → メニュー → Webアプリケーションの管理 → <あなたのWebアプリ>に移動して、Serve Files Timeoutを0に設定します。 そして[保存]ボタンを押します。
次に、webアプリケーションの既存のキャッシュを消去する必要があります。 これを行うには、システム管理ポータル → システム管理 → 構成 → CSP Gatewayの管理 → システムステータスに移動します。 「Cached Forms」テーブルを探し、その最後の行にある「Total」の消去ボタン(ドット)を押してWebアプリケーションのキャッシュを消去します。

### CSP Gatewayのロギング
CSP Gatewayに関しては、受信リクエストのロギングがサポートされています(ドキュメント)。 [デフォルトのパラメーター]タブで、希望するログレベル(v9aなど)を指定して、変更を保存します。 v9aは、すべてのHTTPリクエストをGatewayホームディレクトリのhttp.logに記録します(ほかのオプションについてはドキュメントをご覧ください)。 リクエストは次のようにキャプチャされます。
GET /forms/form/info/Form.Test.Person HTTP/1.1
Host: localhost:57772
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost:57772/formsui/index.html
Cookie: CSPSESSIONID-SP-57772-UP-forms-=001000000000yxiocLLb8bbc9SVXQJC5WMU831n2sENf4OGeGa; CSPWSERVERID=144zwBTP
Dnt: 1
Connection: keep-alive
Cache-Control: max-age=0
また、パフォーマンスを記録するオプションもあります。 結果はファイルに出力されるか、[イベントログの表示]タブで閲覧することができます。
### CSP Gatewayのトレース
そして最後に、CSP Gatewayの[HTTPトレースの表示]タブでリクエストとレスポンスをトレースすることができます。 トレースを有効にすると、リクエストのキャプチャがすぐに開始します。 デバッグを終えたら、忘れずに無効にしてください。 デバッグセッションは次のように行われます。

注意: トレースのやり方がわかっている場合は、エラーを簡単に理解し、問題を再現することができます。 ロギングは統計収集、パフォーマンスプロファイリングなどに使用します。
また、ほとんどのWebサーバーにも、ロギングとパフォーマンス追跡ツールが備わっています。
### ISCLOG
CSP Gatewayは、ネットワークの問題の特定とパフォーマンスの追跡には役立ちますが、Caché内で起きていることをログするにほかのツールが必要となります。 そのようなツールの中でも汎用性に優れているのがISCLOGです。 [ドキュメント](http://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=GCSP_logging)をご覧ください。
これは、現在のリクエスト処理に関する情報を保存できるグローバル変数です。 ロギングを開始するには、以下を実行します。
set ^%ISCLOG = 2
ロギングを終了するには、以下を実行します。
set ^%ISCLOG = 0
リクエストは次のようにロギングされます。
^%ISCLOG=0
^%ISCLOG("Data")=24
^%ISCLOG("Data",1)=$lb(2,"CSPServer","Header from CSP Size:3744 CMD:h IdSource:3","4664","FORMS","2017-06-07 10:49:21.341","%SYS.cspServer2","","")
^%ISCLOG("Data",1,0)="^h30000 "_$c(14,0,0)_"A"
^%ISCLOG("Data",2)=$lb(2,"CSPServer","[UpdateURL] Looking up: //localhost/forms/form/info path found: //localhost/forms/ Appl= "_$c(2,1,3,4)_"@"_$c(3,4,1,2,1,9,1)_"/forms/"_$c(2,1,3,4,1,2,1,2,1,2,4,3,4,1,2,1,9,1,7,1)_":%All"_$c(8,1)_"/forms"_$c(7,1)_"FORMS"_$c(2,1,2,1,3,4,1,2,1,2,1,3,4,1,2,1,4,4,132,3,3,4,2,3,4,2,2,1,4,4,16,14,2,4,3,4,1,3,4,1,2,1,3,4,1,2,1,16,1)_"Form.REST.Main"_$c(2,4,2,4),"4664","FORMS","2017-06-07 10:49:21.342","%CSP.Request.1","124","L3DfNILTaE")
^%ISCLOG("Data",3)=$lb(2,"CSPServer","[UpdateURL] Found cls: Form.REST.Main nocharsetconvert: charset:UTF-8 convert charset:UTF8","4664","FORMS","2017-06-07 10:49:21.342","%CSP.Request.1","124","L3DfNILTaE")
^%ISCLOG("Data",4)=$lb(2,"CSPServer","[HTML] Determined request type","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer2","124","L3DfNILTaE")
^%ISCLOG("Data",4,0)=$lb("usesession",1,"i%Class","Form.REST.Main","i%Service","REST","NOLOCKITEM","","i%GatewayError","")
^%ISCLOG("Data",5)=$lb(2,"CSPSession","[%LoadData] Loading CSP session, nosave=0","4664","FORMS","2017-06-07 10:49:21.342","%CSP.Session.1","","L3DfNILTaE")
^%ISCLOG("Data",5,0)=$lb(900,,0,5567742244,$c(149)_"Ù"_$c(3)_"ó»à"_$c(127)_",½"_$c(149,10)_"\"_$c(18)_"v"_$c(128,135)_"3Vô"_$c(11)_"*"_$c(154)_"PÏG¥"_$c(140,157,145,10,131)_"*",2,"FORMS","001000000000L3DfNILTaE1cDBJNjyQdyLwKq4wCXP82ld8gic",,0,"ru","L3DfNILTaE",2,1,"/forms/",$lb("UnknownUser","%All,%Developer","%All,%Developer",64,-559038737),"","","","2017-06-07 10:48:51","2017-06-07 10:49:04","Basic ZGV2OjEyMw==","Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0","","",0,"/forms/","","","",4,"","","","","http://localhost:57772/formsui/index.html")
^%ISCLOG("Data",6)=$lb(2,"CSPServer","[CSPDispatch]Requested GET /forms/form/info","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",7)=$lb(2,"CSPServer","[CSPDispatch] ** Start processing request newSes=0","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",7,0)="/forms/form/info"
^%ISCLOG("Data",8)=$lb(2,"CSPServer","[CSPDispatch] Service type is REST has-soapaction=0 nosave=0","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",9)=$lb(2,"CSPServer","[CSPDispatch]About to run page: Form.REST.Main","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",9,0)=$lb("UnknownUser","%All,%Developer","%All,%Developer",64,-559038737)
^%ISCLOG("Data",10)=$lb(2,"CSPServer","[callPage] url=/forms/form/info ; Appl: /forms/ newsession=0","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",11)=$lb(2,"CSPServer","[callPage]Imported security context ; User: UnknownUser ; Roles: %All,%Developer","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",12)=$lb(2,"CSPServer","[OutputCSPGatewayData]: chd=1;","4664","FORMS","2017-06-07 10:49:21.431","%CSP.Response.1","","L3DfNILTaE")
^%ISCLOG("Data",13)=$lb(2,"CSPResponse","[WriteHTTPHeaderCookies] Session cookie: CSPSESSIONID-SP-57772-UP-forms-=001000000000L3DfNILTaE1cDBJNjyQdyLwKq4wCXP82ld8gic; path=/forms/; httpOnly;","4664","FORMS","2017-06-07 10:49:21.431","%CSP.Response.1","124","L3DfNILTaE")
^%ISCLOG("Data",14)=$lb(2,"CSPServer","[callPage] Return Status","4664","FORMS","2017-06-07 10:49:21.431","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",14,0)=1
^%ISCLOG("Data",15)=$lb(2,"CSPServer","[OutputCSPGatewayData]: chd=1;","4664","FORMS","2017-06-07 10:49:21.431","%CSP.Response.1","","L3DfNILTaE")
^%ISCLOG("Data",16)=$lb(2,"CSPServer","[Cleanup]Page EndSession=0; needToGetALicense=-1; nosave=0; loginredirect=0; sessionContext=1","4664","FORMS","2017-06-07 10:49:21.431","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",17)=$lb(2,"CSPSession","[Cleanup] EndSession=0 nosave=0","4664","FORMS","2017-06-07 10:49:21.431","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",18)=$lb(2,"CSPSession","[%SaveData] Saved: ","4664","FORMS","2017-06-07 10:49:21.431","%CSP.Session.1","","L3DfNILTaE")
^%ISCLOG("Data",18,0)=$lb(900,,0,5567742261,$c(149)_"Ù"_$c(3)_"ó»à"_$c(127)_",½"_$c(149,10)_"\"_$c(18)_"v"_$c(128,135)_"3Vô"_$c(11)_"*"_$c(154)_"PÏG¥"_$c(140,157,145,10,131)_"*",2,"FORMS","001000000000L3DfNILTaE1cDBJNjyQdyLwKq4wCXP82ld8gic",,0,"ru","L3DfNILTaE",2,1,"/forms/",$lb("UnknownUser","%All,%Developer","%All,%Developer",64,-559038737),"","","","2017-06-07 10:48:51","2017-06-07 10:49:21","Basic ZGV2OjEyMw==","Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0","","",0,"/forms/","","","",5,"","","","","http://localhost:57772/formsui/index.html")
^%ISCLOG("Data",19)=$lb(2,"CSPServer","[Cleanup] Restoring roles before running destructor","4664","FORMS","2017-06-07 10:49:21.431","%SYS.cspServer","","L3DfNILTaE")
^%ISCLOG("Data",19,0)=$lb("UnknownUser","%All,%Developer","%All,%Developer",64,-559038737)
^%ISCLOG("Data",20)=$lb(2,"CSPServer","[Cleanup] End","4664","FORMS","2017-06-07 10:49:21.431","%SYS.cspServer","","L3DfNILTaE")
^%ISCLOG("Data",20,0)=""
^%ISCLOG("Data",21)=$lb(2,"GatewayRequest","[CSPGWClientRequest] GWID: ed-pc:57772; Request: sys_get_system_metricsTimeout: 5","11112","%SYS","2017-06-07 10:49:23.141","%SYS.cspServer3","","")
^%ISCLOG("Data",22)=$lb(2,"GatewayRequest","[CSPGWClientRequest] GWID: 127.0.0.1:57772; Request: sys_get_system_metricsTimeout: 5","11112","%SYS","2017-06-07 10:49:23.141","%SYS.cspServer3","","")
^%ISCLOG("Data",23)=$lb(2,"GatewayRequest","[SendSimpleCmd:Server:Failed] WebServer: 127.0.0.1:57772; Gateway Server Request Failed","11112","%SYS","2017-06-07 10:49:23.141","%CSP.Mgr.GatewayMgrImpl.1","","")
^%ISCLOG("Data",23,0)=0
^%ISCLOG("Data",24)=$lb(2,"GatewayRequest","[GetMetrics]","11112","%SYS","2017-06-07 10:49:23.141","%CSP.Mgr.GatewayMgrImpl.1","","")
^%ISCLOG("Data",24,0)=""
また、以下のような簡単なスクリプトを使用して、グローバルをファイルに出力することができます。
set p="c:\temp\isclog.txt"
open p:"NW"
use p zw ^%ISCLOG
close p
### カスタムロギング
デフォルトのロギングツールも非常に優れていますが、いくつかの問題があります。
* 内容が一般的であり、アプリケーションが認識されない
* より詳細なオプションを使うと、パフォーマンスに影響する
* 構造化されていないため、情報の抽出が困難な場合がある
そのため、より具体的なケースを網羅するために、独自のカスタムロギングシステムを作成することができます。 以下は、%requestオブジェクトの一部を記録する永続クラスのサンプルです。
/// Incoming request
Class Log.Request Extends %Persistent
{
/// A string indicating HTTP method used for this request.
Property method As %String;
/// A string containing the URL up to and including the page name
/// and extension, but not including the query string.
Property url As %String(MAXLEN = "");
/// A string indicating the type of browser from which the request
/// originated, as determined from the HTTP_USER_AGENT header.
Property userAgent As %String(MAXLEN = "");
/// A string indicating the MIME Content-Type of the request.
Property contentType As %String(MAXLEN = "");
/// Character set this request was send in, if not specified in the HTTP headers
/// it defaults to the character set of the page it is being submitted to.
Property charSet As %String(MAXLEN = "");
/// A %CSP.Stream containing the content submitted
/// with this request.
Property content As %Stream.GlobalBinary;
/// True if the communication between the browser and the web server was using
/// the secure https protocol. False for a normal http connection.
Property secure As %Boolean;
Property cgiEnvs As array Of %String(MAXLEN = "", SQLPROJECTION = "table/column");
Property data As array Of %String(MAXLEN = "", SQLPROJECTION = "table/column");
ClassMethod add() As %Status
{
set request = ..%New()
quit request.%Save()
}
Method %OnNew() As %Status [ Private, ServerOnly = 1 ]
{
#dim %request As %CSP.Request
#dim sc As %Status = $$$OK
quit:'$isObject($g(%request)) $$$ERROR($$$GeneralError, "Not a web context")
set ..charSet = %request.CharSet
if $isObject(%request.Content) {
do ..content.CopyFromAndSave(%request.Content)
} else {
set ..content = ""
}
set ..contentType = %request.ContentType
set ..method = %request.Method
set ..secure = %request.Secure
set ..url = %request.URL
set ..userAgent = %request.UserAgent
set cgi = ""
for {
set cgi=$order(%request.CgiEnvs(cgi))
quit:cgi=""
do ..cgiEnvs.SetAt(%request.CgiEnvs(cgi), cgi)
}
// Only gets first data if more than one data with the same name is present
set data = ""
for {
set data=$order(%request.Data(data))
quit:data=""
do ..data.SetAt(%request.Get(data), data)
}
quit sc
}
}
Log.Request テーブルに新しいレコードを追加するには、コードに次の呼び出しを追加します。
do ##class(Log.Request).add()
これは非常に基本的なサンプルであり、必要に応じてコメント、変数、またはほかの様々なものを記録するように拡張することができ、そうすることが推奨されます。 このアプローチの主なメリットは、記録されたデータに対してSQLクエリを実行できることにあります。 独自のロギングシステムの構築に関する詳細は、[こちらの記事](https://community.intersystems.com/post/logging-using-macros-intersystems-cach%C3%A9)をご覧ください。
### セッションイベント
イベントクラスは、[%CSP.Session](http://docs.intersystems.com/latestj/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25CSP.Session)オブジェクトの寿命中に呼び出されるインターフェースを定義するクラスです。 これを使用するには、 [%CSP.SessionEvents](http://docs.intersystems.com/latestj/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25CSP.SessionEvents)をサブクラス化して、実行するメソッドコードを実装する必要があります。 次に、CSPアプリケーション構成内で、イベントクラスを作成したクラスに設定します。
次のコールバックを利用できます。
OnApplicationChange
OnEndRequest
OnEndSession
OnLogin
OnLogout
OnStartRequest
OnStartSession
* OnTimeout
たとえば上記のカスタムロギングは、これらのメソッドから呼び出すことができます。
### デバイスへの出力
最も単純なオプションの1つは、すべてのオブジェクトをレスポンスとして出力するCSPユーティリティメソッドです。 これをコードの任意の場所に追加するだけで出力できます。
set %response.ContentType = "html"
do ##class(%CSP.Utils).DisplayAllObjects()
return $$$OK
### まとめ
Webアプリケーションのデバッグにはさまざまなツールを使用することができます。 手元のタスクに最適なものを選択するようにしてください。
皆さんは、CachéからWebアプリケーションをデバッグする際には、どのようなヒントやトリックを使用していますか?
記事
Toshihiko Minamoto · 2020年12月16日
今回は、InterSystems IRIS に特有のことではなく、職場で Windows 10 Pro または Enterprise を搭載した PC またはノートパソコンがサーバーとして使用されている環境で Docker を使用する場合に重要と思われる点について触れたいと思います。
ご存知かと思いますが、コンテナテクノロジーは基本的に Linux の世界で生まれ、最近では Linux のホストで使用されており、その最大のポテンシャルを伺わせています。 Windows を普段から使用するユーザーの間では、Microsoft と Docker 両社によるここ数年の重大な試みにより、Windows のシステムで Linux イメージを基にコンテナを実行することがとても簡単になったと理解されています。しかし、生産システムでの使用がサポートされておらず、それが大きな問題となっています。特に、Windows と Linux のファイルシステムに大きな違いがあるため、安心してホストシステム内でコンテナの外に持続データを保管するということができないのです。 ついには、コンテナを実行するために、_Docker for Windows _自体で Linux の小さな仮想マシン (_MobiLinux_) が使用されるようになり、Windows ユーザーに対しては透過的に実行されます。また、先ほど述べたように、データベースの存続がコンテナよりも短くて構わないのであれば、パーフェクトに動作します。
では、何が言いたいかというと、問題を回避して処理を単純化するには、完全な Linux システムが必要になるが、Windows ベースのサーバーを使用していると、仮想マシンを使用する以外に方法がない場合が多い、ということです。 少なくとも、Windows の WSL2 がリリースされるまでの話ですが、それはまた別の機会に触れたいと思います。もちろん、十分堅牢に動作するまでは時間がかかるでしょう。
この記事では、必要に応じて、Windows サーバーの Ubuntu システムで Docker コンテナを使用できる環境をインストールする方法について分かりやすく説明します。 それでは、始めましょう。
1. Hyper-V を有効にする
まだ有効にしていない方は、`Windows Features`を追加し、Hyper-V を有効にしてください。 そして再起動します (現在の私のロケールにより、テキストはスペイン語になっています。 ドン・キホーテの言語をご存知ない方は手順を参照しながら「解明」できることを願っています 😉 )
.png)
2. Hyper-V 上で Ubuntu の仮想マシンを作成する
これ以上簡単に仮想マシン (VM) を作成する方法はないと思います。 `Hyper-V Manager` のウィンドウを開き、 Quick Create... オプション (画面一番上近く) を選択します。そして、画面に表示されている Ubuntu バージョンのいずれかを使って仮想マシンを作成します (どの Linux の場合でも、_iso _file をダウンロードすれば、別のディストリビューションの VM を作成できます)。 私は、使用可能な Ubuntu の最新リリース 19.10 を選択しました。 ここで表示されるものはすべて 18.04 に対応しています。 イメージのダウンロードにかかる時間によりますが、15~20 分ほどで新しい VM が作成され、使用を開始できます。
重要: Default Switch オプションはそのままにしておいてください。. ホストと仮想マシンの両方からインターネットにアクセスできることが保証されます。

3. ローカルサブネットを作成する
仮想マシンを使用するときによく目にする問題の 1 つに、ネットワーク設定に関連して起こるものがあります。動作が不安定であったり、WiFi に接続しているときは動作してもケーブルだと駄目な場合があったり、もしくはその逆の場合もあります。また、Windows ホストで VPN を確立すると、VM のインターネットアクセスを失ったり、VM (Linux) とホスト (Windows) 間のコミュニケーションが遮断されたりすることもあり、はっきり言って大変なんです! ノートパソコンで開発作業を行ったり、簡単なデモやプレゼン資料を作成したりするとき、つまりインターネットにアクセスできることよりも、自分のホストと VM が確実に通信できることの方が重要であるときに、自分の作業環境を信頼できなくなってしまいます。
アドホックのローカルサブネットを Windows ホストと仮想マシン間で共有すれば、それを解決できます。 相互に通信し合えるようにするには、そのサブネットを使用します。 ホストと VM に特定の IP を割り当てれば準備完了です。
その方法はいたって簡単。 まずは、`Hyper-V Manager` にある Virtual Switch Manager... に移動します。
.png)
そこで、_New Virtual Switch __ _オプションを選択します (VM の新しいネットワークカードとして機能します)。
.png)
_内部ネットワーク _ として定義し、好きな名前を選び、他のオプションはデフォルトのままにしておきます。
.png)
_`Windows Control Panel --> Network and Sharing Center`_ と移動すれば、作成したばかりのスイッチが既に表示されています。
.png)
4. ホストと仮想マシン間で共有されるローカルサブネットを設定する
この時点で、新しいローカルネットワークの設定を完了します。 そのためには、_Mi Nuevo Conmutador LOCAL_ にカーソルを合わせて、Properties をクリックし、それから IPv4 protocol へと移動して固定 IP アドレスを割り当てます。
.png)
重要: ここで割り当てる IP は、このローカルサブネットのホスト (Windows) IP となります。
5. 新しいローカルネットワークを設定し、仮想マシンにリンクする
ここで、 `Hyper-V Manager` に戻ります。 VM が実行中であれば、停止してください。 停止したら、その設定に移動し、新しい内部仮想スイッチを追加します。
.png)
_(注意: 画像には他のスイッチ Hyper-V Conmutador INTERNO が表示されています。 これは私のもう 1 つのサブネット用のもので、 この設定には必要ありません) _
「Add」をクリックしたら、 前回作成したスイッチを選択します。
.png)
これが済んだら、「Apply」、「Accept」と順にクリックして準備完了です! 仮想マシンを起動し、もう一度ログインしてから内部接続の設定を完了します。 そうするには、VM が起動した後に画面右上のネットワークアイコンをクリックします。すると 2 つのネットワーク _eth0_ y _eth1_ が表示されます。 _eth1_ は、この時点では切断された状態で表示されます。
.png)
Ethernet (eht1) の設定に移動し、このローカルサブネットに固定 IP を割り当てます (例: IP _155.100.101.1_、サブネットマスク _ 255.255.255.0_)。
.png)
これで完了です。 これで仮想マシンが出来上がり、ホストと同じサブネットを共有する IP 155.100.101.1 として識別されます。
7. 仮想マシンから Windows 10 へのアクセスを許可する
大抵の場合、Windows 10 のデフォルト設定では、他のサーバーからのアクセスが許可されていません。Windows システムについては、たった今作成した VM がまさにそのサーバー (外部の潜在的に危険なサーバー) に該当します。このため、こういった仮想マシンからホストに接続するには、ファイアウォールにルールを追加する必要があります。 どうするのかって? いたって簡単です。`Windows Control Panel` の中から `Windows Defender Firewall` を見つけて、Advance Configuration に移動し、新規の *Entry Rule* を作成します。
.png)
1 つのポートまたは 1 つ以上の範囲を設定できます... (すべてのポートに対しルールを設定することもできます)...
.png)
ここで役に立つのが _Allow Connection_ です。
.png)
_ネットワークのすべてのタイプ_オプションを選択し...
.png)
ルールに名前を付けます。
.png)
ここで**重要**なことがあります。上記の手順が終わったら、新しく作成したルールのプロパティをもう一度開き、ルールをローカルサブネット内の接続に対してのみ適用できるよう*適用範囲を制限*しておきます。
.png)
8. 準備。 Docker と他に使用するアプリケーションを新しい Ubuntu 仮想マシンにインストールする
インストールプロセスを完了し、新しい VM を最新の状態に整え、インターネットアクセスなども準備ができたら、 好きなアプリをインストールできます。 少なくとも、元々の目的である Docker、また会社のネットワークへの接続に必要であれば VPN クライアント、さらには VS Code、Eclipse+Atelier などのアプリもインストールできます。
具体的に、VM 内で Docker をインストールする場合は、こちらの手順に従ってください: 。
Docker ランタイムの動作確認、テストイメージのダウンロードなどが済んだら、準備完了です。
_**¡You're all set!**_ が表示されたら、Ubuntu 仮想マシンでコンテナを (ハードウェアのキャパシティ内で) 制限なしに動作させることができます。同仮想マシンには、Windows 10 ホストやブラウザー、アプリなどから接続したり、またはその逆、Ubuntu VM から Windows 10 ホストに接続したりもできます。 このすべてに共有ローカルサブネット内で設定した IP アドレスが使用されるため、VPN を確立するしないに関わらず、またインターネットへのアクセスに Wi-Fi アダプターを使用するのか、イーサネットケーブルを使用するのかに関わらず、接続に失敗することはありません。
それから、アドバイスがもう 1 つだけあります。 Windows 10 と仮想マシンの間でファイルを交換する場合は、[WinSCP](https://winscp.net/eng/download.php) を使用すると簡単で便利です。 無料ですし、とても良く機能します。
もちろん、使える設定は他にもありますが、私は一番確実な方法としてこれを使っています。 お役に立つことを期待しています。 皆さんの悩みの種を取り除くことができれば、この記事を書いた価値があったと言えるでしょう。
¡ハッピーコーディング!
Windows10 ホストで実行される Hyper-V Ubuntu 仮想マシンで Docker を使用できるように環境を設定する
今回は、InterSystems IRIS に特有のことではなく、職場で Windows 10 Pro または Enterprise を搭載した PC またはノートパソコンがサーバーとして使用されている環境で Docker を使用する場合に重要と思われる点について触れたいと思います。
ご存知かと思いますが、コンテナテクノロジーは基本的に Linux の世界で生まれ、最近では Linux のホストで使用されており、その最大のポテンシャルを伺わせています。 Windows を普段から使用するユーザーの間では、Microsoft と Docker 両社によるここ数年の重大な試みにより、Windows のシステムで Linux イメージを基にコンテナを実行することがとても簡単になったと理解されています。しかし、生産システムでの使用がサポートされておらず、それが大きな問題となっています。特に、Windows と Linux のファイルシステムに大きな違いがあるため、安心してホストシステム内でコンテナの外に持続データを保管するということができないのです。 ついには、コンテナを実行するために、_Docker for Windows _自体で Linux の小さな仮想マシン (_MobiLinux_) が使用されるようになり、Windows ユーザーに対しては透過的に実行されます。また、先ほど述べたように、データベースの存続がコンテナよりも短くて構わないのであれば、パーフェクトに動作します。
では、何が言いたいかというと、問題を回避して処理を単純化するには、完全な Linux システムが必要になるが、Windows ベースのサーバーを使用していると、仮想マシンを使用する以外に方法がない場合が多い、ということです。 少なくとも、Windows の WSL2 がリリースされるまでの話ですが、それはまた別の機会に触れたいと思います。もちろん、十分堅牢に動作するまでは時間がかかるでしょう。
この記事では、必要に応じて、Windows サーバーの Ubuntu システムで Docker コンテナを使用できる環境をインストールする方法について分かりやすく説明します。 それでは、始めましょう。
1. Hyper-V を有効にする
まだ有効にしていない方は、`Windows Features`を追加し、Hyper-V を有効にしてください。 そして再起動します (現在の私のロケールにより、テキストはスペイン語になっています。 ドン・キホーテの言語をご存知ない方は手順を参照しながら「解明」できることを願っています 😉 )
.png)
2. Hyper-V 上で Ubuntu の仮想マシンを作成する
これ以上簡単に仮想マシン (VM) を作成する方法はないと思います。 `Hyper-V Manager` のウィンドウを開き、 Quick Create... オプション (画面一番上近く) を選択します。そして、画面に表示されている Ubuntu バージョンのいずれかを使って仮想マシンを作成します (どの Linux の場合でも、_iso _file をダウンロードすれば、別のディストリビューションの VM を作成できます)。 私は、使用可能な Ubuntu の最新リリース 19.10 を選択しました。 ここで表示されるものはすべて 18.04 に対応しています。 イメージのダウンロードにかかる時間によりますが、15~20 分ほどで新しい VM が作成され、使用を開始できます。
重要: Default Switch オプションはそのままにしておいてください。. ホストと仮想マシンの両方からインターネットにアクセスできることが保証されます。

3. ローカルサブネットを作成する
仮想マシンを使用するときによく目にする問題の 1 つに、ネットワーク設定に関連して起こるものがあります。動作が不安定であったり、WiFi に接続しているときは動作してもケーブルだと駄目な場合があったり、もしくはその逆の場合もあります。また、Windows ホストで VPN を確立すると、VM のインターネットアクセスを失ったり、VM (Linux) とホスト (Windows) 間のコミュニケーションが遮断されたりすることもあり、はっきり言って大変なんです! ノートパソコンで開発作業を行ったり、簡単なデモやプレゼン資料を作成したりするとき、つまりインターネットにアクセスできることよりも、自分のホストと VM が確実に通信できることの方が重要であるときに、自分の作業環境を信頼できなくなってしまいます。
アドホックのローカルサブネットを Windows ホストと仮想マシン間で共有すれば、それを解決できます。 相互に通信し合えるようにするには、そのサブネットを使用します。 ホストと VM に特定の IP を割り当てれば準備完了です。
その方法はいたって簡単。 まずは、`Hyper-V Manager` にある Virtual Switch Manager... に移動します。
.png)
そこで、_New Virtual Switch __ _オプションを選択します (VM の新しいネットワークカードとして機能します)。
.png)
_内部ネットワーク _ として定義し、好きな名前を選び、他のオプションはデフォルトのままにしておきます。
.png)
_`Windows Control Panel --> Network and Sharing Center`_ と移動すれば、作成したばかりのスイッチが既に表示されています。
.png)
4. ホストと仮想マシン間で共有されるローカルサブネットを設定する
この時点で、新しいローカルネットワークの設定を完了します。 そのためには、_Mi Nuevo Conmutador LOCAL_ にカーソルを合わせて、Properties をクリックし、それから IPv4 protocol へと移動して固定 IP アドレスを割り当てます。
.png)
重要: ここで割り当てる IP は、このローカルサブネットのホスト (Windows) IP となります。
5. 新しいローカルネットワークを設定し、仮想マシンにリンクする
ここで、 `Hyper-V Manager` に戻ります。 VM が実行中であれば、停止してください。 停止したら、その設定に移動し、新しい内部仮想スイッチを追加します。
.png)
_(注意: 画像には他のスイッチ Hyper-V Conmutador INTERNO が表示されています。 これは私のもう 1 つのサブネット用のもので、 この設定には必要ありません) _
「Add」をクリックしたら、 前回作成したスイッチを選択します。
.png)
これが済んだら、「Apply」、「Accept」と順にクリックして準備完了です! 仮想マシンを起動し、もう一度ログインしてから内部接続の設定を完了します。 そうするには、VM が起動した後に画面右上のネットワークアイコンをクリックします。すると 2 つのネットワーク _eth0_ y _eth1_ が表示されます。 _eth1_ は、この時点では切断された状態で表示されます。
.png)
Ethernet (eht1) の設定に移動し、このローカルサブネットに固定 IP を割り当てます (例: IP _155.100.101.1_、サブネットマスク _ 255.255.255.0_)。
.png)
これで完了です。 これで仮想マシンが出来上がり、ホストと同じサブネットを共有する IP 155.100.101.1 として識別されます。
7. 仮想マシンから Windows 10 へのアクセスを許可する
大抵の場合、Windows 10 のデフォルト設定では、他のサーバーからのアクセスが許可されていません。Windows システムについては、たった今作成した VM がまさにそのサーバー (外部の潜在的に危険なサーバー) に該当します。このため、こういった仮想マシンからホストに接続するには、ファイアウォールにルールを追加する必要があります。 どうするのかって? いたって簡単です。`Windows Control Panel` の中から `Windows Defender Firewall` を見つけて、Advance Configuration に移動し、新規の *Entry Rule* を作成します。
.png)
1 つのポートまたは 1 つ以上の範囲を設定できます... (すべてのポートに対しルールを設定することもできます)...
.png)
ここで役に立つのが _Allow Connection_ です。
.png)
_ネットワークのすべてのタイプ_オプションを選択し...
.png)
ルールに名前を付けます。
.png)
ここで**重要**なことがあります。上記の手順が終わったら、新しく作成したルールのプロパティをもう一度開き、ルールをローカルサブネット内の接続に対してのみ適用できるよう*適用範囲を制限*しておきます。
.png)
8. 準備。 Docker と他に使用するアプリケーションを新しい Ubuntu 仮想マシンにインストールする
インストールプロセスを完了し、新しい VM を最新の状態に整え、インターネットアクセスなども準備ができたら、 好きなアプリをインストールできます。 少なくとも、元々の目的である Docker、また会社のネットワークへの接続に必要であれば VPN クライアント、さらには VS Code、Eclipse+Atelier などのアプリもインストールできます。
具体的に、VM 内で Docker をインストールする場合は、こちらの手順に従ってください: 。
Docker ランタイムの動作確認、テストイメージのダウンロードなどが済んだら、準備完了です。
_**¡You're all set!**_ が表示されたら、Ubuntu 仮想マシンでコンテナを (ハードウェアのキャパシティ内で) 制限なしに動作させることができます。同仮想マシンには、Windows 10 ホストやブラウザー、アプリなどから接続したり、またはその逆、Ubuntu VM から Windows 10 ホストに接続したりもできます。 このすべてに共有ローカルサブネット内で設定した IP アドレスが使用されるため、VPN を確立するしないに関わらず、またインターネットへのアクセスに Wi-Fi アダプターを使用するのか、イーサネットケーブルを使用するのかに関わらず、接続に失敗することはありません。
それから、アドバイスがもう 1 つだけあります。 Windows 10 と仮想マシンの間でファイルを交換する場合は、[WinSCP](https://winscp.net/eng/download.php) を使用すると簡単で便利です。 無料ですし、とても良く機能します。
もちろん、使える設定は他にもありますが、私は一番確実な方法としてこれを使っています。 お役に立つことを期待しています。 皆さんの悩みの種を取り除くことができれば、この記事を書いた価値があったと言えるでしょう。
¡ハッピーコーディング!
記事
Mihoko Iijima · 2020年7月6日
前回はシンプルなIRISアプリケーション をGoogleクラウドにデプロイしました。 今回は、同じプロジェクトを Amazon Web Services(アマゾンウェブサービス) のElastic Kubernetes Service (EKS)を使って、デプロイします。
IRISプロジェクトをあなた自身のプライベート・リポジトリにすでにFORKしていると想定します。この記事では<username>/my-objectscript-rest-docker-templateという名前にしています。 <root_repo_dir>は、そのルートディレクトリです。
開始する前に、 AWSコマンドラインインターフェースと、Kubernetesクラスタ作成用のシンプルなCLIユーティリティeksctlをインストールします。 AWSの場合 aws2 の使用を試すことができますが、ここで説明するようにkube設定ファイルでaws2の使用法を設定する必要があります 。
AWS EKS
一般的なAWSリソースと同様に、EKSは無料ではありません 。 ただし、無料利用枠のアカウントを作成して、AWSの機能を試すことができます。 ただし、試してみたい機能のすべてが無料枠に含まれているわけではないことに注意してください。 ですから、現在の予算を管理し、金銭的な問題を理解するには、 これと これを読んでください。
ここでは既にAWSアカウントとrootアクセス権があり、このrootアクセス権を使用せず、管理者権限のあるユーザーが作成されていると想定します。 このユーザーのアクセスキーと秘密キーを [dev] プロファイル(またはあなたがつけたプロファイル名)の下のAWS認証情報ファイルに配置する必要があります。
$ cat ~/.aws/credentials[dev]aws_access_key_id = ABCDEFGHIJKLMNOPQRSTaws_secret_access_key = 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234
今回は、 AWS「eu-west-1」リージョンにリソースを作成しますが、あなたが今いる場所に最も近いリージョンを選択し、以下に記載されている「eu-west-1」のすべてを選択したリージョンで置き換えてください。
ちなみに、必要なすべてのファイル(.circleci 、eks/、k8s/)も、ここに保存されており 、簡単にコピーと貼り付けができます。 必要なすべてのEKSリソースは最初から作成されます。 Amazon EKS Workshop は、良いリソースだと思います。
次に、AWSへのアクセスを確認します(ここではダミーのアカウントを使用しています)。
$ export AWS_PROFILE=dev
$ aws sts get-caller-identity{ "Account": "012345678910", "UserId": " ABCDEFGHIJKLMNOPQRSTU", "Arn": "arn:aws:iam::012345678910:user/FirstName.LastName"}
$ eksctl version[ℹ] version.Info{BuiltAt:"", GitCommit:"", GitTag:"0.10.2"}
すべてのデフォルト設定が適切であるという事実に従い、「 eksctl create cluster <cluster_name> --region eu-west-1 」を実行することもできますし、設定ファイルを作成して独自の設定を管理することもできます。
後者は、そのようなファイルをバージョン管理システム(VCS)に保存できるため、よりよい方法です。設定の例はここにあります。 さまざまな設定に関するここの記述を読んだら、独自の設定を作成してみましょう。
mkdir <root_repo_dir>/eks; cd <root_repo_dir>/eks
$ cat cluster.yaml
apiVersion: eksctl.io/v1alpha5kind: ClusterConfig
metadata: name: dev-cluster region: eu-west-1 version: '1.14'
vpc: cidr: 10.42.0.0/16 nat: gateway: Single clusterEndpoints: publicAccess: true privateAccess: false
nodeGroups: - name: ng-1 amiFamily: AmazonLinux2 ami: ami-059c6874350e63ca9 # AMI is specific for a region instanceType: t2.medium desiredCapacity: 1 minSize: 1 maxSize: 1
# Worker nodes won't have an access FROM the Internet # But have an access TO the Internet through NAT-gateway privateNetworking: true
# We don't need to SSH to nodes for demo ssh: allow: false
# Labels are Kubernetes labels, shown when 'kubectl get nodes --show-labels' labels: role: eks-demo # Tags are AWS tags, shown in 'Tags' tab on AWS console' tags: role: eks-demo
# CloudWatch logging is disabled by default to save money# Mentioned here just to show a way to manage it#cloudWatch: # clusterLogging:# enableTypes: []
「nodeGroups.desiredCapacity = 1」は本番環境では意味がありませんが、デモでは問題ありません。また、AMIイメージはリージョン間で異なる可能性があることに注意してください。 「amazon-eks-node-1.14」を探し、最新の1つを選択します。
次に、クラスター(コントロールプレーンとワーカーノード)を作成します。
$ eksctl create cluster -f cluster.yaml
ちなみに、クラスターが不要になった場合は、以下を使用してクラスターを削除できます。
$ eksctl delete cluster --name dev-cluster --region eu-west-1 --wait
クラスターの作成には約15分かかります。 この間、eksctlの出力を確認できます。
CloudFormationコンソールを参照すると、2つのスタックがあります。それぞれにドリルダウンして、[リソース] タブを参照すると、何が作成されるかを正確に確認でき、[イベント] タブで、リソース作成の現在の状態を確認できます。
クラスターは正常に作成されましたが、eksctl の出力で「EKSクラスターでkubectlを使用できません」というメッセージがあり、接続に問題があったことがわかります。aws-iam-authenticator(IAM)をインストールしてkubeコンテキストを作成し、これを解決しましょう。
$ which aws-iam-authenticator/usr/local/bin/aws-iam-authenticator
$ aws eks update-kubeconfig --name dev-cluster --region eu-west-1
$ kubectl get nodesNAME STATUS ROLES AGE VERSIONip-10-42-140-98.eu-west-1.compute.internal Ready <none> 1m v1.14.7-eks-1861c5
これで動作するはずですが、管理者権限を持つユーザーでクラスターを作成しました。 CircleCIからの通常のデプロイ処理では、プログラムによるアクセスのみで、次のポリシーが付与されている特別なAWSユーザー(この例ではCircleCIと名付けられたユーザー)を作成する と良いでしょう。
最初のポリシーはAWSに組み込まれているため、それを選択するだけで済みます。 2つ目は自分で作成する必要があります。 作成プロセスの説明はここにあります。 ポリシー「 AmazonEKSDescribePolicy 」は次のようになります。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "eks:DescribeCluster", "eks:ListClusters" ], "Resource": "*" } ]}
ユーザーの作成後、ユーザーのアクセスキーと秘密のアクセスキーを保存します。これらのキーはすぐに必要になります。
また、この記事で説明されているように、Kubernetesクラスター内でこのユーザー権限を付与したいと考えています。 つまり、EKSクラスターを作成した後は、IAMユーザー、すなわち作成者のみがそれにアクセスできます。 CircleCIユーザーを追加するには、クラスターのAWS認証設定(configmap aws-auth、 'data'セクション)のデフォルトの空の「mapUsers」セクションをkubectl editを使って(‘01234567890’の代わりに自分のアカウントIDを使います)次の行に置き換える必要があります。
$ kubectl -n kube-system edit configmap aws-auth...data:... mapUsers: | - userarn: arn:aws:iam::01234567890:user/CircleCI username: circle-ci groups: - system:masters
以前の記事ののKubernetesマニフェストを使用します (「Googleクラウドの前提条件」のセクションを参照)。以前のやり方と違う点は、デプロイのイメージフィールドでプレースホルダを使うということだけです。 これらのマニフェストを<root_repo_dir>/k8sディレクトリに保存します。 デプロイファイルの名前がdeployment.tplに変更されたことに注意してください。
$ cat <root_repo_dir>/k8s/deployment.tpl...spec:containers:- image: DOCKER_REPO_NAME/iris-rest:DOCKER_IMAGE_TAG...
CircleCI
CircleCI側のデプロイ処理は、GKEに使用される処理に似ています。
リポジトリをPullする
Dockerイメージをビルドする
Amazon クラウドで認証する
イメージをAmazon Elastic Container Registry(ECR)にアップロードする
このイメージを基にしたAWS EKSでコンテナを実行する
前回と同様に、作成およびテスト済みのCircleCI構成テンプレートorbsを利用します。
イメージを構築してECRにPushするためのaws-ecr orb
AWS認証のためのaws-eks orb
Kubernetesマニフェストのデプロイのためのkubernetes orb
デプロイ構成は次のようになります。
$ cat <root_repo_dir>/.circleci/config.ymlversion: 2.1orbs: aws-ecr: circleci/aws-ecr@6.5.0 aws-eks: circleci/aws-eks@0.2.6 kubernetes: circleci/kubernetes@0.10.1
jobs: deploy-application: executor: aws-eks/python3 parameters: cluster-name: description: | Name of the EKS cluster type: string aws-region: description: | AWS region type: string account-url: description: | Docker AWS ECR repository url type: string tag: description: | Docker image tag type: string steps: - checkout - run: name: Replace placeholders with values in deployment template command: | cat k8s/deployment.tpl |\ sed "s|DOCKER_REPO_NAME|<< parameters.account-url >>|" |\ sed "s|DOCKER_IMAGE_TAG|<< parameters.tag >>|" > k8s/deployment.yaml; \ cat k8s/deployment.yaml - aws-eks/update-kubeconfig-with-authenticator: cluster-name: << parameters.cluster-name >> install-kubectl: true aws-region: << parameters.aws-region >> - kubernetes/create-or-update-resource: action-type: apply resource-file-path: "k8s/namespace.yaml" show-kubectl-command: true - kubernetes/create-or-update-resource: action-type: apply resource-file-path: "k8s/deployment.yaml" show-kubectl-command: true get-rollout-status: true resource-name: deployment/iris-rest namespace: iris - kubernetes/create-or-update-resource: action-type: apply resource-file-path: "k8s/service.yaml" show-kubectl-command: true namespace: irisworkflows: main: jobs: - aws-ecr/build-and-push-image: aws-access-key-id: AWS_ACCESS_KEY_ID aws-secret-access-key: AWS_SECRET_ACCESS_KEY region: AWS_REGION account-url: AWS_ECR_ACCOUNT_URL repo: iris-rest create-repo: true dockerfile: Dockerfile-zpm path: . tag: ${CIRCLE_SHA1} - deploy-application: cluster-name: dev-cluster aws-region: eu-west-1 account-url: ${AWS_ECR_ACCOUNT_URL} tag: ${CIRCLE_SHA1} requires: - aws-ecr/build-and-push-image
ワークフローのセクションにはジョブリストが含まれ、各ジョブはaws-ecr/build-and-push-imageなどのorbから呼び出すか、構成で「deploy-application」を使って直接定義できます。
次のコードは、aws-ecr/build-and-push-imageジョブが終了した後で、deploy-applicationジョブが呼び出されることを意味します。
requires:- aws-ecr/build-and-push-image
[ジョブ] セクションには、デプロイ・アプリケーションジョブの説明と、次のような定義された手順のリストが含まれています。
checkoutで、GitリポジトリからPullする
runで、Docker-imageリポジトリとタグを動的に設定するスクリプトを実行する
aws-iam-authenticatorを使用する aws-eks /update-kubeconfig-with-authenticatorを使用して Kubernetesへの接続を設定する
CircleCIから「kubectl apply」を実行する方法として数回使用されるkubernetes/create-or-update-resource
変数を使用しますが、もちろんそれらはCircleCIの「環境変数」タブで定義する必要があります。
次の表は、使用される変数の意味を示しています。
AWS_ACCESS_KEY_ID
CircleCI IAMユーザーのアクセスキー
AWS_SECRET_ACCESS_KEY
CircleCI IAMユーザーの秘密キー
AWS_REGION
eu-west-1、この場合
AWS_ECR_ACCOUNT_URL
01234567890.dkr.ecr.eu-west-1.amazonaws.comなどのAWS ECR Docker レジストリのURL
「01234567890」がアカウント IDの場合
デプロイ処理をトリガーする方法は次のとおりです。
$ git add .circleci/ eks/ k8s/$ git commit -m “AWS EKS deployment”$ git push
これにより、このワークフローにおける2つのジョブが表示されます。
どちらのジョブもクリック可能であり、これにより、実行した手順の詳細を確認できます。デプロイには数分かかります。 完了したら、KubernetesリソースとIRISアプリケーション自体のステータスを確認できます。
$ kubectl -n iris get pods -w # Ctrl+C to stop $ kubectl -n iris get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEiris-rest LoadBalancer 172.20.190.211 a3de52988147a11eaaaff02ca6b647c2-663499201.eu-west-1.elb.amazonaws.com 52773:32573/TCP 15s
DNSレコードが反映されるまで数分かかります。 それまでは、curlを実行すると「ホストを解決できませんでした」というエラーが表示されます。
$ curl -XPOST -H "Content-Type: application/json" -u _system:SYS a3de52988147a11eaaaff02ca6b647c2-663499201.eu-west-1.elb.amazonaws.com:52773/person/ -d '{"Name":"John Dou"}' $ curl -XGET -u _system:SYS a3de52988147a11eaaaff02ca6b647c2-663499201.eu-west-1.elb.amazonaws.com:52773/person/all[{"Name":"John Dou"},]
まとめ
一見するとAWS EKSへのデプロイはGKEへのデプロイよりも複雑に見えますが、それほど大きな違いはありません。 組織でAWSを使用している場合は、Kubernetesをスタックに追加する方法を理解されたと思います。
最近、EKS APIが拡張され、管理グループをサポートできるようになりました。これにより、コントロールプレーンとデータプレーンを全体としてデプロイでき、これは将来有望と思われます。 さらに、コンテナ用のAWSサーバーレスコンピューティングエンジンであるFargateが利用可能になりました。
最後に、AWS ECRに関する簡単な注意事項を記します:イメージにライフサイクルポリシーを設定することを忘れないでください。
InterSystems Open Exchangeで関連アプリケーション をご確認ください。
記事
Toshihiko Minamoto · 2020年10月5日
CachéとCosFakerを使ったテスト駆動開発の簡単な紹介
**読了****目安時間**: 6分
皆さん、こんにちは。
私がTDDに初めて出会ったのは約9年前のことです。すぐに夢中になってしまいました。
最近は非常に人気が出てきているようですが、残念ながら多くの企業ではあまり使われていないようです。 また、主に初心者の方ではありますが、一体それがなんであるのか、どのように使うのかといったことさえも知らない開発者もたくさんいます。
#### 概要
この記事は、%UnitTestでTDDを使用する方法を紹介することを目標としています。 ワークフローを示し、私の最初のプロジェクトであった[cosFaker](https://github.com/henryhamon/cosfaker)の使用方法を説明します。これはCachéを使って作成したものであり、最近になって[OpenExchange](https://openexchange.intersystems.com/package/CosFaker)にアップロードしたものです。
では、ベルトを締めて出発しましょう。
#### TDDとは?
テスト駆動開発(TDD)は、自動テストが失敗した場合に、開発者に新しいコードの書き方のみを示すプログラミング実践として定義できます。
このメリットに関する記事、講義、講演などは数多く存在しますが、どれもが正しい内容です。
コードはテスト済みで生成されること、過度なエンジニアリングを避けるために定義された要件に、システムが実際に適合していることを確認できること、継続的にフィードバックを得ることが挙げられます。
では、TDDを使用しない理由は何でしょうか。 TDDにはどのような問題があるのでしょうか。 答えは単純です。そう、コストです! とにかくコストがかかります!
TDDではより多くの行のコードを記述する必要があるため、その処理には時間がかかります。 しかし、TDDを使用すると、製品を作成するための最終コストは現時点で発生し、後で追加コストをかける必要がありません。
常にテストを実行すれば、早期にエラーを検出できるため、修正にかかるコストが削減されるのです。
というわけで、私からのアドバイスは、ただ実行に移しましょう!
#### セットアップ
InterSystemsは、%UnitTestの使用方法に関するドキュメントとチュートリアルを用意しています。[こちらからお読みください。](https://irisdocs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=TUNT)
開発にはvscodeを使用します。 この方法で、テスト用に別のフォルダを作成し、 UnitTestRootにプロジェクトコードパスを追加して、テストを実行する際に、テストサブフォルダの名前を渡します。 そして必ず、修飾子loadudlを渡します。
Set ^UnitTestRoot = "~/code"
Do ##class(%UnitTest.Manager).RunTest("myPack","/loadudl")
#### 手順
おそらく、「レッド ➡グリーン➡リファクタリング」という有名なTDDサイクルを耳にしたことがあるでしょう。 失敗するテストを書き、合格する単純なプロダクションコードを書き、そしてそのプロダクションコードをリファクタリングするというサイクルです。
では、実際に手を動かして、計算を行うクラスとそれをテストする別のクラスを作成することにしましょう。 後者のクラスは、%UnitTest.TestCaseを拡張します。
では、整数の2乗を返すClassMethodを作成しましょう。
Class Production.Math
{
ClassMethod Square(pValue As %Integer) As %Integer
{
}
}
そして、2を渡すとどうなるかテストします。 4を返すはずです。
Class TDD.Math Extends %UnitTest.TestCase
{
Method TestSquare()
{
Do $$$AssertEquals(##class(Production.Math).Square(2), 4)
}
}
次のコードを実行します。
Do ##class(%UnitTest.Manager).RunTest("TDD","/loadudl")
テストは失敗します。

レッド! 次のステップは、これをグリーンにすることです。
グリーンにするために、Squareメソッドの実行結果として4を返すようにしましょう。
Class Production.Math
{
ClassMethod Square(pValue As %Integer) As %Integer
{
Quit 4
}
}
そしてテストを再実行します。

1つのシナリオでしか機能しないのですから、このソリューションでは、おそらくあまり嬉しくないのではないでしょうか。 わかりました! では次のステップに進みましょう。 別のシナリオを作成することにします。今度は負の数を渡してみます。
Class TDD.Math Extends %UnitTest.TestCase
{
Method TestSquare()
{
Do $$$AssertEquals(##class(Production.Math).Square(2), 4)
}
Method TestSquareNegativeNumber()
{
Do $$$AssertEquals(##class(Production.Math).Square(-3), 9)
}
}
テストを実行します。

また失敗してしまうので、プロダクションコードをリファクタリングしましょう。
Class Production.Math
{
ClassMethod Square(pValue As %Integer) As %Integer
{
Quit pValue * pValue
}
}
そして、テストを再実行します。

これですべてがうまく機能するようになりました... これが凝縮版のTDDサイクルです。
なぜこの手順に従う必要があるのか、と思っていることでしょう。 なぜテストを失敗させる必要があるのか、と。
私はプロダクションコードを記述したチームで作業したことがありますが、テストを作成したのはその後でした。 それでも、この小さな一歩に従う方を好むのには、次の理由があります。
ボブおじさん(Robert C. Martin)は、コードを書いた後でテストを書くことは「TDD」ではなく「時間の無駄」だと呼んだのです。
もう少し述べれば、テストが失敗し、次に合格することから、テストをテストしていることになります。
このテストはコードに変わりなく、誤りが含まれることもあります。 そして、それをテストする方法こそ、失敗して合格する必要のある場合に失敗と合格を保証することになります。 つまり、「テストをテストした」ということになります。
#### cosFaker
適切なテストを作成するには、最初にテストデータを生成する必要があります。 これを行う方法の1つが、データのダンプを生成して、テストに使うという方法です。
別の方法には、[cosFaker](https://openexchange.intersystems.com/package/CosFaker)を使用して、必要なときに偽のデータを簡単に作り出す手があります。
xml ファイルをダウンロードするだけです。その後、管理ポータル -> システムエクスプローラ -> Classes -> Importに移動します。 インポートする xml ファイルを選択するか、そのファイルを Studio にドラッグします。
また、ターミナルを使用してインポートすることもできます。
Do $system.OBJ.Load("yourpath/cosFaker.vX.X.X.xml","ck")
##### ローカリゼーション
cosFakerは、デフォルトのCSPアプリケーションフォルダにロケールファイルを追加します。 現時点では、英語とブラジルポルトガル語(私の母国語)の2言語しかありません。
データの言語は、Cachéの構成に応じて選択されます。
cosFakerのローカリゼーションは進行中のプロセスです。ご協力いただける方は、ぜひ、自身のロケール向けにローカライズされるプロバイダを作成してプルリクエストを提出してください。
cosFakerを使用すれば、ランダムな語、段落、電話番号、名前、住所、メール、価格、製品名、日付、16進色コードなどを生成することができます。
すべてのメソッドは、クラスでサブジェクトごとにグループ化されます。つまり、Latitudeを生成するには、AddressクラスのLatitudeメソッドを呼び出します。
Write ##class(cosFaker.Address).Latitude()
-37.6806
また、テスト用のJsonを生成することもできます。
Write ##class(cosFaker.JSON).GetDataJSONFromJSON("{ip:'ipv4',created_at:'date.backward 40',login:'username', text: 'words 3'}")
{
"created_at":"2019-03-08",
"ip":"95.226.124.187",
"login":"john46",
"text":"temporibus fugit deserunt"
}
以下は、**cosFaker**クラスとメソッドの全リストです。
* **cosFaker.Address**
* StreetSuffix
* StreetPrefix
* PostCode
* StreetName
* Latitude
* _出力:_ -54.7274
* Longitude
* _出力:_ -43.9504
* Capital( Location = “” )
* State( FullName = 0 )
* City( State = “” )
* Country( Abrev = 0 )
* SecondaryAddress
* BuildingNumber
* **cosFaker.App**
* FunctionName( Group= “”, Separator = “” )
* AppAction( Group= “” )
* AppType
* **cosFaker.Coffee**
* BlendName
* _出力:_ Cascara Cake
* Variety
* _出力:_ Mundo Novo
* Notes
* _出力:_ crisp, slick, nutella, potato defect!, red apple
* Origin
* _出力:_ Rulindo, Rwanda
* **cosFaker.Color**
* Hexadecimal
* _出力:_ #A50BD7
* RGB
* _出力:_ 189,180,195
* Name
* **cosFaker.Commerce**
* ProductName
* Product
* PromotionCode
* Color
* Department
* Price( Min = 0, Max = 1000, Dec = 2, Symbol = “” )
* _出力:_ 556.88
* CNPJ( Pretty = 1 )
* CNPJはブラジルの法人用税務登記番号です
* _出力:_ 44.383.315/0001-30
* **cosFaker.Company**
* Name
* Profession
* Industry
* **cosFaker.Dates**
* Forward( Days = 365, Format = 3 )
* Backward( Days = 365, Format = 3 )
* **cosFaker.DragonBall**
* Character
* _出力:_ Gogeta
* **cosFaker.File**
* Extension
* _出力:_ txt
* MimeType
* _出力:_ application/font-woff
* Filename( Dir = “”, Name = “”, Ext = “”, DirectorySeparator = “/” )
* _出力:_ repellat.architecto.aut/aliquid.gif
* **cosFaker.Finance**
* Amount( Min = 0, Max = 10000, Dec = 2, Separator= “,”, Symbol = “” )
* _出力:_ 3949,18
* CreditCard( Type = “” )
* _出力:_ 3476-581511-6349
* BitcoinAddress( Min = 24, Max = 34 )
* _出力:_ 1WoR6fYvsE8gNXkBkeXvNqGECPUZ
* **cosFaker.Game**
* MortalKombat
* _出力:_ Raiden
* StreetFighter
* _出力:_ Akuma
* Card( Abrev = 0 )
* _出力:_ 5 of Diamonds
* **cosFaker.Internet**
* UserName( FirstName = “”, LastName = “” )
* Email( FirstName = “”, LastName = “”, Provider = “” )
* Protocol
* _出力:_ http
* DomainWord
* DomainName
* Url
* Avatar( Size = “” )
* _出力:_ http://www.avatarpro.biz/avatar?s=150
* Slug( Words = “”, Glue = “” )
* IPV4
* _出力:_ 226.7.213.228
* IPV6
* _出力:_ 0532:0b70:35f6:00fd:041f:5655:74c8:83fe
* MAC
* _出力:_ 73:B0:82:D0:BC:70
* **cosFaker.JSON**
* GetDataOBJFromJSON( Json = “” // JSON template string to create data )
* _パラメーターの例_: "{dates:'5 date'}"
* _出力_: {"dates":["2019-02-19","2019-12-21","2018-07-02","2017-05-25","2016-08-14"]}
* **cosFaker.Job**
* Title
* Field
* Skills
* **cosFaker.Lorem**
* Word
* Words( Num = “” )
* Sentence( WordCount = “”, Min = 3, Max = 10 )
* _出力:_ Sapiente et accusamus reiciendis iure qui est.
* Sentences( SentenceCount = “”, Separator = “” )
* Paragraph( SentenceCount = “” )
* Paragraphs( ParagraphCount = “”, Separator = “” )
* Lines( LineCount = “” )
* Text( Times = 1 )
* Hipster( ParagraphCount = “”, Separator = “” )
* **cosFaker.Name**
* FirstName( Gender = “” )
* LastName
* FullName( Gender = “” )
* Suffix
* **cosFaker.Person**
* cpf( Pretty = 1 )
* CPFはブラジルの社会保障番号です
* _出力:_ 469.655.208-09
* **cosFaker.Phone**
* PhoneNumber( Area = 1 )
* _出力:_ (36) 9560-9757
* CellPhone( Area = 1 )
* _出力:_ (77) 94497-9538
* AreaCode
* _出力:_ 17
* **cosFaker.Pokemon**
* Pokemon( EvolvesFrom = “” )
* _出力:_ Kingdra
* **cosFaker.StarWars**
* Characters
* _出力:_ Darth Vader
* Droids
* _出力:_ C-3PO
* Planets
* _出力:_ Takodana
* Quotes
* _出力:_ Only at the end do you realize the power of the Dark Side.
* Species
* _出力:_ Hutt
* Vehicles
* _出力:_ ATT Battle Tank
* WookieWords
* _出力:_ nng
* WookieSentence( SentenceCount = “” )
* _出力:_ ruh ga ru hnn-rowr mumwa ru ru mumwa.
* **cosFaker.UFC**
* Category
* _出力:_ Middleweight
* Fighter( Category = “”, Country = “”, WithISOCountry = 0 )
* _出力:_ Dmitry Poberezhets
* Featherweight( Country = “” )
* _出力:_ Yair Rodriguez
* Middleweight( Country = “” )
* _出力:_ Elias Theodorou
* Welterweight( Country = “” )
* _出力:_ Charlie Ward
* Lightweight( Country = “” )
* _出力:_ Tae Hyun Bang
* Bantamweight( Country = “” )
* _出力:_ Alejandro Pérez
* Flyweight( Country = “” )
* _出力:_ Ben Nguyen
* Heavyweight( Country = “” )
* _出力:_ Francis Ngannou
* LightHeavyweight( Country = “” )
* _出力:_ Paul Craig
* Nickname( Fighter = “” )
* _出力:_ Abacus
ユーザー名を返すメソッドでユーザーのクラスを作成してみましょう。ユーザー名はFirstNameとLastNameを連結したものになります。
Class Production.User Extends %RegisteredObject
{
Property FirstName As %String;
Property LastName As %String;
Method Username() As %String
{
}
}
Class TDD.User Extends %UnitTest.TestCase
{
Method TestUsername()
{
Set firstName = ##class(cosFaker.Name).FirstName(),
lastName = ##class(cosFaker.Name).LastName(),
user = ##class(Production.User).%New(),
user.FirstName = firstName,
user.LastName = lastName
Do $$$AssertEquals(user.Username(), firstName _ "." _ lastName)
}
}

リファクタリング:
Class Production.User Extends %RegisteredObject
{
Property FirstName As %String;
Property LastName As %String;
Method Username() As %String
{
Quit ..FirstName _ "." _ ..LastName
}
}

アカウントの有効期限を追加して、検証することにします。
Class Production.User Extends %RegisteredObject
{
Property FirstName As %String;
Property LastName As %String;
Property AccountExpires As %Date;
Method Username() As %String
{
Quit ..FirstName _ "." _ ..LastName
}
Method Expired() As %Boolean
{
}
}
Class TDD.User Extends %UnitTest.TestCase
{
Method TestUsername()
{
Set firstName = ##class(cosFaker.Name).FirstName(),
lastName = ##class(cosFaker.Name).LastName(),
user = ##class(Production.User).%New(),
user.FirstName = firstName,
user.LastName = lastName
Do $$$AssertEquals(user.Username(), firstName _ "." _ lastName)
}
Method TestWhenIsNotExpired() As %Status
{
Set user = ##class(Production.User).%New(),
user.AccountExpires = ##class(cosFaker.Dates).Forward(40)
Do $$$AssertNotTrue(user.Expired())
}
}

リファクタリング:
Method Expired() As %Boolean
{
Quit ($system.SQL.DATEDIFF("dd", ..AccountExpires, +$Horolog) > 0)
}

では、アカウントの有効期限が切れた場合をテストしてみましょう。
Method TestWhenIsExpired() As %Status
{
Set user = ##class(Production.User).%New(),
user.AccountExpires = ##class(cosFaker.Dates).Backward(40)
Do $$$AssertTrue(user.Expired())
}

すべてがグリーンです。
これらはあまり大したことのない例かもしれませんが、このようにすることで、コードだけでなくクラスの設計も単純にすることができます。
#### まとめ
この記事では、テスト駆動開発と%UnitTestクラスの使用方法について少しだけ学習しました。
また、cosFakerとテスト用の偽のデータの生成方法についても説明しました。
テストとTDDについては、これらの実践をレガシーコードで使用する方法、統合テスト、受け入れテスト駆動開発など、ほかにも学習することがたくさんあります。
詳細については、次の2冊が私のイチオシです。
[『Test Driven Development Teste e design no mundo real com Ruby』Mauricio Aniche著](https://www.amazon.com/Test-driven-development-Teste-design-Portuguese-ebook/dp/B01B6MSVBK/ref=sr_1_3?qid=1553909895&refinements=p_27%3AMauricio+Aniche&s=digital-text&sr=1-3&text=Mauricio+Aniche) - これについては英語版が出版されているかわかりません。 Java、C#、Ruby、およびPHP版があります。 あまりの素晴らしさに感銘を受けた一冊です。
そしてもちろん、Kent Beckの『[Test Driven Development by Example](https://www.amazon.com.br/Test-Driven-Development-Kent-Beck/dp/0321146530/ref=pd_sbs_14_2/131-5080621-0921627?_encoding=UTF8&pd_rd_i=0321146530&pd_rd_r=23604a58-528d-11e9-8a77-f188713467c0&pd_rd_w=TSR2y&pd_rd_wg=JBqE6&pf_rd_p=80c6065d-57d3-41bf-b15e-ee01dd80424f&pf_rd_r=CJ3QPX0H0P6H0EMNFZYJ&psc=1&refRID=CJ3QPX0H0P6H0EMNFZYJ)』。
コメントやご質問はお気軽にどうぞ。
これで、おしまいです。
記事
Toshihiko Minamoto · 2021年1月21日
InterSystems Caché のグローバルは、デベロッパーにとって非常に便利な機能を提供します。 しかし、グローバルが高速な上に効率が良いのはなぜでしょう?
### 理論
基本的に、Caché データベースとは、データベースと同じ名前を持ち、CACHE.DAT ファイルを含んだカタログのことです。 Unix システムでは、このデータベースを普通のディスクパーティションにすることもできます。
Caché のデータはすべてブロックとして保管され、バランスド B\* ツリーとして整理されます。 基本的にすべてのグローバルがツリーに保管されると考えると、グローバルのサブスクリプトはツリーの枝を意味する一方で、グローバルのサブスクリプトの値はツリーの葉として保管されると言えます。 バランスド B\* ツリーと通常の B ツリーの違いは、前者の枝には [$Order](http://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_forder) と [$Query](http://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fquery) の両関数を使ってサブスクリプト (この記事ではグローバル) のイテレーションをスピーディに実行するのに役立つ適切なリンクがあり、ツリーの幹に戻る必要がないという点です。
デフォルトで、データベースファイルの各ブロックのサイズは 8,192 バイトに固定されています。 既存のデータベースのブロックサイズは変更できません。 新しいデータベースを作成する場合は、保管するデータ型に合わせて、16kB、32 kB、または 64 kB をブロックサイズに選択できます。 但し、すべてのデータはブロック毎に読み取られることを覚えておきましょう。つまり、1 バイトの値を 1 つだけリクエストする場合でも、システムはリクエストされたデータブロックの前にいくつかのブロックを読み取ります。 また、Caché はデータを再利用するためにデータベースブロックをメモリに格納するグローバルバッファを使うことと、ブロックサイズと同じサイズとグローバルバッファを使用することを覚えておきましょう。 対応するブロックサイズのグローバルバッファがシステムにない場合は、そのブロックサイズの既存のデータベースをマウントしたり、新しいデータベースを作成したりはできません。 実際使用するブロックサイズでメモリサイズを定義することをおすすめします。 データベースブロックよりも大きいバッファブロックを使用することは可能ですが、その場合、各バッファブロックには 1 つのデータベースブロック、もしくはさらに小さいブロックしか保管されません。

この画像では、8 kB のグローバルバッファのメモリがそれぞれ 8 kB のブロックで構成されるデータベースに割り当てられています。 このデータベースのブロックのうち、空でないものは、それぞれのマップが 62,464 個のブロック (それぞれ 8 kB のブロック) を定義するかたちでマップに定義されています。
### ブロックタイプ
システムは複数のブロックタイプに対応しています。 各レベルにおいて、ブロックの適切なリンクは同じタイプのブロックまたはデータの終わりを意味する Null ブロックにポイントしている必要があります。
* **タイプ 9**: グローバルカタログのブロック。 通常このブロックでは、既存のすべてのグローバルがそれぞれのパラメーターと併せて説明されます。このパラメーターには、グローバルのサブスクリプトのコレーション (照合順序) が含まれています。大切なパラメーターの 1 つで、グローバル作成後には変更できません。
* **タイプ 66**: 高レベルポインタのブロック。 このブロックの上に置けるのは、グローバルカタログのブロックのみです。
* **タイプ 6**: 低レベルポインタのブロック。 このブロックの上に置けるのは高レベルポインタのブロックのみで、これより下に置けるのはデータブロックのみです。
* **タイプ 70**: 高レベルポインタと低レベルポイントの両方で構成されるブロック。 このブロックは、対応するグローバルに保管される値の数が少ないため、複数のレベルのブロックを設ける必要がない場合に使用されます。 このブロックは通常、グローバルカタログのブロックと同様、データブロックにポイントします。
* **タイプ 2**: 比較的大きなグローバルを保管するポインターのブロック。 値を複数のデータブロックに均等に割り当てるには、ポインタのブロックにレベルを追加すると便利です。 このブロックは通常、ポインタのブロックの間に置かれます。
* **タイプ 8**: データブロック。 通常このブロックには、1 つのノードの値ではなく、複数のグローバルノードの値が保管されます。
* **タイプ 24**: ラージストリングのブロック。 1 つのグローバルの値がブロックサイズよりも大きい場合、その値はラージストリングの特殊ブロックに記録される一方で、データブロックのノードにはラージストリングのブロック一覧へのリンクが合計サイズと共に保管されます。
* **タイプ 16**: マップブロック。 このブロックは、未割り当てのブロックに関する情報を保管するようにデザインされています。
典型的な Caché データベースの最初のブロックには、データベースファイルそのものに関するサービス情報、2 つ目のブロックには複数のブロックからなるマップが含まれます。 最初のカタログブロックは 3 番目 (ブロック #3) に位置付けされ、1 つのデータベースに複数のカタログブロックを保管できます。 この後は、ポインタブロック (枝)、データブロック (葉)、そしてラージストリングのブロックが続きます。 先ほども触れましたが、グローバルカタログのブロックには、データベースやグローバル設定に存在するすべてのグローバルに関する情報が保管されます (そうでない場合は、そのようなグローバルに置かれます)。 この場合、そのようなグローバルを説明するノードには、低レベルの Null ポインタが割り当てられます。 既存のグローバルの一覧は、管理ポータルのグローバルカタログから表示できます。 また、このポータルでは、削除したグローバルをカタログに保存 (照合順序を保存するなど) したり、デフォルトの、またはカスタマイズしたコレ―ションを設定した新しいグローバルを作成したりできます。


一般的に、ブロックのツリーは下の画像のように描くことができます。 赤く表示されているのは、ブロックへのリンクです。

### データベースの整合性
Caché の現在のバージョンでは、データベースの最も重大な問題を解決しましたので、データベースのパフォーマンスが低下するリスクは極めて低くなっています。 それでも、定期的に ^Integrity ツールを使って自動整合性チェックを実行することをおすすめします。同ツールは、データベースページもしくはタスクマネージャーのマネジメントポータルを使って、%SYS ネームスペースのターミナルで起動できます。 自動整合性チェックは、デフォルトでセットアップされており、事前に定義もされているので、有効化するだけで実行できます。


整合性チェックでは、OSI モデルの下位層でのリンク検証、ブロックタイプの確認、適切なリンクの分析、グローバルノードと適用される照合順序のマッチングが行われます。 整合性チェックの最中にエラーが出る場合は、%SYS ネームスペースから ^REPAIR ツールを実行できます。 このツールを使えば、どのブロックでも表示できる上に、必要であれば、データベースを修正するなどの変更を施すことができます。
### 実践
しかし、ここまで紹介した内容は理論にすぎません。 グローバルとそのブロックの実態を見極めるのは未だに容易なことではありません。 現時点で、ブロックを表示するには、上述した ^REPAIR ツールを使う方法しかありません。 下に示すのがこのプログラムの典型的な出力です。

1 年前、私は新しいツールを開発するプロジェクトに着手しました。それは、データベースを破損させるリスクなしにブロックのツリーをイテレーションし、それらのブロックをウェブ UI に視覚化し、その視覚化されたイメージを SVG または PNG に保存するオプションを提供するというものです。 このプロジェクトは名付けて CacheBlocksExplorer。ソースコードは、[Github](https://github.com/daimor/CacheBlocksExplorer) からダウンロードしていただけます。

実装した機能には以下の通りです。
* システム内の構成済みのデータベースや単純にマウントされたデータベースを表示する。
* 情報をブロック別、ブロックタイプ別、右ポインタ別、リンク付ノードの一覧別に表示する。
* 前述のレベルよりも低いレベルのブロックにポイントしているノードに関する詳細を表示する。
* ブロックへのリンクを削除することにより、そのブロックを非表示にする (そのブロックに保管されているデータを損傷することはありません)。
To-Do リスト:
適切なリンクを表示する: 現在のバージョンでは、適切なリンクはブロックに関する情報の中に表示されていますが、矢印として表示する方がベターでしょう。
ラージストリングのブロックを表示する: 現在のバージョンでは表示されていない。
グローバルカタログのブロックを3 番目のブロックだけでなく、すべて表示する。
ツリー全体も表示したいのですが、何十万個もあるブロックとそれぞれのリンクを一緒にすばやくレンダリングできるライブラリは、まだ見つかっていません。現在のライブラリは、ウェブブラウザーにレンダリングしていますが、そのスピードは Caché が構造全体を読み取るスピードよりもかなり遅いです。
次回の記事では、私の自作ツール Cache Block Explorer を実際に使い、その機能をさらに詳しく説明するほか、使用例をいくつか紹介し、グローバルやブロックに関する実行可能なデータを数多く取得する方法を披露したいと思います。
記事
Toshihiko Minamoto · 2024年4月8日
人工知能は、命令によってテキストから画像を生成したり、単純な指示によって物語を差作成したりすることだけに限られていません。
多様な写真を作成したり、既存の写真に特殊な背景を含めたりすることもできます。
また、話者の言語や速度に関係なく、音声のトランスクリプションを取得することも可能です。
では、ファイル管理の仕組みを調べてみましょう。
問題
入力値としてファイルが必要なメソッドについて OpenAI 情報を分析する場合、multipart/form-data を使用してパラメーターを指定する必要があります。
IRIS では、JSON コンテンツを使って POST メソッドへの呼び出しを作成する方法をすでに知っていますが、 この場合、Base64 フォーマットのファイルコンテンツを持つパラメーターを使用するのは非現実的です。
multipart/form-data にファイルコンテンツを含めるには、クラス %Net.MIMEPart を使用する必要があります。
呼び出しにファイルを含めるには、クラスオブジェクト %Net.MIMEPart に関連付けられた Content-Disposition ヘッダーを作成します。
set content = ##class(%Net.MIMEPart).%New()
set contentDisposition = "form-data; name="_$CHAR(34)_"image"_$CHAR(34)
set contentDisposition = contentDisposition_"; filename="_$CHAR(34)_fileName_$CHAR(34)
do content.SetHeader("Content-Disposition",contentDisposition)
Request クラスを使用してプロセスの値を保持するため、Base64 コンテンツを、コンテンツのボディを構成するストリームに変換する必要があります。
StreamUtils ユーティリティを使用して、Base64 を Stream に変換できます。
注意: 「pImage」変数には、ファイルコンテンツの Base64 文字列が含まれます。
Do ##class(HS.Util.StreamUtils).Base64Encode(pImage, .tStream)
Set content.Body = tStream
ただし、Global Summit 2023 において、幸運にも InterSystems エキスパートからもっと有用なコツを得られました。 実行は StreamUtils よりも高い効果があり、最終的には String と Stream のレコードを読み取るループがあることを教えていただきました。
これは、JSON を使用してそれを Stream に変換する Get を実行するだけの単純なソリューションです。
set contentfile = {}
set contentfile.file = pImage
set content.Body = contentfile.%Get("file",,"stream<base64")
呼び出しに必要なすべてのパラメーターを含めたら、ようやくパーツをエンクローズする新しい MIMEPart クラスを作成できます。
Set rootMIME = ##class(%Net.MIMEPart).%New()
do rootMIME.Parts.Insert(content)
set writer = ##class(%Net.MIMEWriter).%New()
set tSC = writer.OutputToStream(tHttpRequest.EntityBody)
set tSC = writer.WriteMIMEBody(rootMIME)
Set tContentType = "multipart/form-data; boundary="_rootMIME.Boundary
set tSC = ..Adapter.SendFormDataArray(.tHttpResponse, "POST", tHttpRequest,,,url)
このようにして、OpenAI で必要なメソッドにファイルコンテンツを送信します。
画像ファイル
Image メソッドでは、写真を送信してバリエーションを実行することができます。 すべてのイラストは PNG 形式である必要があるため、Base64 形式のファイルコンテンツを指定する場合、ファイル名はランダムに生成され、PNG 拡張子が付けられます。
以下は、写真の変更例です。
元の写真
バリエーション
ご覧のように、プログラムは命令を独自に解釈しています。
会社ロゴは円形と判断して入れ替えています。 また、オフィスにはガラスのドアが使用されていることを認識し、別のガラスのドアに入れ替えて、この時点ではレンガの壁が適用されています。
さらに、シャツの色を変更し、男性の腕の位置も変わっています。
また、OpenAI ではプロンプトに示されたコンテンツを挿入する領域にマスクを指定することで、画像を編集できます。
同じ画像を使用して、画像の背景を取り除くマスクを適用しました。
元の写真
マスク
OpenAI にジャマイカのビーチに転送してほしいと指示すると、以下のような結果になりました。
家族や友人に会うときに、自分の休暇を自慢できるようになりました 😊
Image
エンドポイント: POST https://api.openai.com/v1/images/variations
既存の画像を変更した画像を作成できます。 変更方法を指定するプロンプトは不要であるため、この画像の解釈方法については AI のセンスを信用する必要があります。 また、結果のサイズと、リンクまたは Base64 のコンテンツのどちらの戻し方を希望するかを定義できます。
入力パラメーターは以下のようになります。
image: 必須
変換する画像ファイルを指定します。
n: オプション。 デフォルトは 1 です。
このエリアでは、生成する画像の最大数を指定します。 (1~10 の数値を使用します)。
size: オプション。 デフォルトは 1024x1024 です。
このパラメーターは生成される画像のサイズを特徴づけます。 値は “256x256”、“512x512”、または “1024x1024” です。
response_format: オプション。 デフォルトで “url” になります。
この要素は、生成された画像を戻す際の形式を指定します。 値は “url” または “b64_json” です。
エンドポイント: POST https://api.openai.com/v1/images/edits
マスク ファイルに基づいて既存の画像を変更し、プロンプトに従って画像を作成できます。 また、結果の寸法と、リンクまたは Base64 のコンテンツのどちらの戻し方を希望するかを指定できます。
入力パラメーターは以下のようになります。
image: 必須
変更する画像ファイルを指定します。
mask: 必須
このパートでは、適用するマスク画像ファイルを指定します。
n: オプション。 デフォルトは 1 です。
このエリアでは、生成する画像の最大数を指定します。 (1~10 の数値を使用します)。
size: オプション。 デフォルトは 1024x1024 です。
このパラメーターは生成される画像のサイズを特徴づけます。 値は “256x256”、“512x512”、または “1024x1024” です。
response_format: オプション。 デフォルトで “url” になります。
この要素は、生成された画像を戻す際の形式を指定します。 値は “url” または “b64_json” です。
音声ファイル
OpenAI が管理するのは画像だけではありません。 音声ファイルを使用して、提供されたレコーディングのトランスクリプションまたは翻訳を取得することもできます。
このメソッドは、固有名詞、ブランド、およびスラングを区別して正しいトランス来居プションと翻訳を提供できる Whisper モデルを使用します。 たとえば、「micromachine」というブランドについて言及するのと、一般名詞の「micro machines」をスペイン語に翻訳するのは同じことではありません。
次の例は、80 年代の有名な広告のトランスクリプションです。
Whisper にこの音声のトランスクリプションを作成する命令の結果は以下のようになります。
{
"text": "This is the Micromachine Man presenting the most midget miniature motorcade of micromachines.
Each one has dramatic details, terrific trim, precision paint jobs, plus incredible micromachine pocket playsets.
There's a police station, fire station, restaurant, service station, and more. Perfect pocket portables to take anyplace.
And there are many miniature playsets to play with and each one comes with its own special edition micromachine vehicle and
fun fantastic features that miraculously move. Raise the boat lift at the airport, marina, man the gun turret at
the army base, clean your car at the car wash, raise the toll bridge. And these playsets fit together to form a micromachine world.
Micromachine pocket playsets, so tremendously tiny, so perfectly precise, so dazzlingly detailed, you'll want to pocket them all.
Micromachines and micromachine pocket playsets sold separately from Galoob. The smaller they are, the better they are."
}
素晴らしいです! そう思いませんか?
上記の結果は、Whisper モデルが受けたトレーニングによって実現しています。 これに関する情報は、OpeanAI ページに記載されている以下のダイアグラムでご覧いただけます。
詳細は、https://openai.com/research/whisper をご覧ください。
サービスは処理するファイルのタイプ(WAV、MP3、OGG など)を知っておく必要があるため、ファイル名に関する情報をプログラムに提供することが重要であることに注意してください。
呼び出しには Base64 コンテンツのみを含めるため、ランダムなテキストと指定拡張子でファイル名を作成できるように、ファイル拡張子も指定する必要があります。
たとえば、St.OpenAi.Msg.Audio.AudioRequest メッセージには、音声の種類を示す MP3、OGG、WAV、FLAC などの “type” プロパティがあります。
エンドポイント: https://api.openai.com/v1/audio/transcriptions
このメソッドでは、音声のコンテンツを音声の言語に文字起こしできます。
入力パラメーターは以下のようになります。
file: 必須
文字起こしする音声ファイル(ファイル名ではありません)を指定します。 次の形式がサポートされています: FLAC、MP3、MP4、MPEG、MPGA、M4A、OGG、WAV、または WEBM
model: 必須。
トランスクリプションを作成するのに使用するモデル。 現時点では、“whisper-1” のみを使用できます。
language: オプション。 デフォルトでは、音声の言語です。
ISO-639-1 によると、指定されている場合、精度とレイテンシーが改善されます。
prompt: オプション。
これは、モデルのスタイルをガイドするため、または前の音声セグメントを継続するためのオプションのテキストです。 このメッセージは音声の言語に一致している必要があります。
response_format。 オプション。 デフォルトで “json” になります。
このパートでは、トランスクリプション出力のフォーマットを明確にします。 “json”、“text”、“verbose_json” のいずれかのオプションを使用します。
temperature: オプション。 デフォルト値は 0 です。
サンプリング温度は 0~1 です。 0.8 のように値が高くなるほど出力がよりランダムになり、0.2 のように低くなるほど集中的で確定的になります。 0 に設定すると、モデルは対数尤度を使用して、特定のしきい値に達するまで温度を自動的に高めます。
このメソッドのドキュメントは https://platform.openai.com/docs/api-reference/audio/createTranscription<をご覧ください。
エンドポイント: https://api.openai.com/v1/audio/translations
このメソッドでは、音声のコンテンツを英語に翻訳できます。
入力パラメーターは以下のようになります。
file: 必須
翻訳する音声ファイルです(ファイル名ではありません)。 次の形式がサポートされています: FLAC、MP3、MP4、MPEG、MPGA、M4A、OGG、WAV、または WEBM
model: 必須。
このフィールドには、トランスクリプションを作成するために使用するモデルを入力します。 現時点では、“whisper-1” のみを使用できます。
prompt: オプション。
これは、モデルのスタイルをガイドするため、または前の音声セグメントを継続するためのオプションのテキストです。 このメッセージは英語である必要があります。
response_format。 オプション。 デフォルトで “json” になります。
ここでは、トランスクリプションの出力の形式を “json”、“text”、“verbose_json” から指定します。
temperature: オプション。 デフォルト値は 0 です。
サンプリング温度は 0~1 です。 0.8 のように値が高くなるほど出力がよりランダムになり、0.2 のように低くなるほど集中的で確定的になります。 0 に設定すると、モデルは対数尤度を使用して、特定のしきい値に達するまで温度を自動的に高めます。
このメソッドのドキュメントは https://platform.openai.com/docs/api-reference/audio/createTranscriptionをご覧ください。
次の内容
OpenAI は継続的に進化しているため、次回の記事ではテキストから音声への変換方法とその他の新機能をいくつか紹介します。
記事を気に入っていただけたなら、「いいね」を押してください。