0 フォロワー · 496 投稿

  

InterSystems CachéはマルチモデルのDBMSおよびアプリケーションサーバーです

詳細はこちらをご覧ください

ドキュメント

記事 Toshihiko Minamoto · 10月 8, 2020 18m read

Ansible は Caché とアプリケーションコンポーネントをいかに迅速にデータプラットフォームのベンチマークにデプロイするかという課題を解決するのに役立ちました。 同じツールと方法をテストラボ、トレーニングシステム、開発環境、またはその他の環境の立ち上げも使うことができます。 顧客サイトにアプリケーションをデプロイする場合、デプロイの大部分を自動化し、アプリケーションのベストプラクティス標準に合わせてシステム、Caché、アプリケーションを確実に構成することができます。

概要

テクノロジーアーキテクトである私たちのグループの仕事の一つに、さまざまなベンダーのハードウェアやオペレーティングシステムでの InterSystems データプラットフォームのベンチマークがあります。 多くの場合、インフラストラクチャはリリース前のものであり、返却や他者への引き渡しが必要になるまでの時間が決まっているため、ベンチマークを迅速かつ正確に設定し、実際のベンチマーク作業にできるだけ多くの時間をかけることが不可欠です。

私たちは長年にわたってシェルスクリプトレットとチートシートやチェックリストからのカットアンドペーストを使用し、多くのベンチマークインストール作業を自動化していましたが、多くのサーバーがあり、異なるオペレーティングシステムを使用する場合は特に非常に激しくエラーが発生する傾向がありました。SLES 11、Red Hat 6、Red Hat 7、AIX などでのサービスのインストールや使用には、微妙で厄介な違いがある場合があるからです。

私はシステムの自動構成と管理に使用できるいくつかのソフトウェアオプションを検討した後、データプラットフォームアプリケーションとベンチマークコンポーネントをプロビジョニングする作業のために Ansible を選択しました。 なお、Ansible がデプロイと構成に最適なソリューションであると決めつけているわけではありませんのでご了承ください。 私は Ansible を選択する前に、Puppet や Chef などの他のツールの機能や操作を確認しました。 あなたの組織がすでに他のツールを使用しており、あなたがそれを使用できるのなら、私が Ansible で使用する方法やコマンドなどは他のソフトウェアに読み替えることができるはずです。この投稿が、使用しているツールに関係なく役立つことを願っています。

これは、InterSystems データプラットフォームのアプリケーションをデプロイする際に Ansible を使用する方法を説明する連載の最初の投稿です。 この投稿では、Caché をインストールして基盤を構築する方法について説明します。次の投稿では、ソリューションを拡張し、%installer クラスの使用を含むアプリケーションのインストールについて掲載します。 この記事では以下について説明します。

  • Ansible の概要とインストール。
  • 簡単な管理とスケーリングのための Ansible のレイアウト。
  • 1つ以上のサーバーでの Caché のインストール。

Ansible とは?

Ansible では複雑なタスクを自動化しながら1つ以上のサーバーを構成でき、新しいサーバーを非常に簡単に追加できます。 タスクは冪等性が保たれるように設計されています(同じサーバーで同じスクリプトを何度でも実行でき、結果のサーバー構成は同じになります)。

Ansible をプロビジョニングタスク用に選択した主な理由は、システム要件が最小(Linux サーバーに Python 2.7 がインストールされていること)であり、自己完結型のソリューションであることです。Ansible のコードは管理サーバーにのみインストールされ、プッシュアーキテクチャを使用して OpenSSH 経由でターゲットサーバー上のコマンドとスクリプトを実行します。 プロビジョニングされるサーバーにはエージェントは必要ありません。 対照的に、Chef と Puppet はプルクライアントアーキテクチャであり、クライアントサーバー(Web、データベースなど)にソフトウェアが読み込まれ、クライアントは継続的に更新をマスターにポーリングします。 Ansible のプッシュアーキテクチャは、スケジュールに応じてサーバーを段階的に実装するのにも適しています。

Ansible はオープンソースであり、コミュニティによってメンテナンスされています。 Ansible, Inc は 2015 年 から Red Hat が所有しています。 Ansible, Inc は付加価値の高いライフサイクル製品(Ansible Tower)と共に有償のサポートとトレーニングを提供していますが、この投稿で使用されているものはすべてオープンソースのコマンドラインバージョンです。 活発なコミュニティ(Ansible Galaxy)があり、Web サーバー、ftp、Kerbros のインストールなどの多数のタスク用にダウンロードできる既成のソリューションが豊富に存在し、拡充され続けています。 完全なベンチマークのデプロイプロジェクトの例に、RHEL、SLES、または Solaris に(他のプラットフォームと共に)Apache 2.x をインストールして構成するためにダウンロードしてカスタマイズした Apache モジュールを含めています。

Ansible のダウンロードとインストールの手順は、Ansible の Web サイトと github にあります。 質問がある場合や貢献したい場合は、活発なコミュニティがあります。

https://www.ansible.com/get-started
http://docs.ansible.com

Ansible のインストール

この投稿の例は、Red Hat 7.0 および 7.2 が稼働中の VM でテストされています。また、Centos 7 を搭載した私のノートパソコンで VirtualBox と Vagrant を使用し、Ansible コントローラーサーバーの初期テストも行いました。 Caché をコントローラーにインストールする必要はないため、Caché でサポートされているよりも多くのプラットフォームからオペレーティングシステムを選択できます。 話を簡単にするため、私は Red Hat で利用可能な Ansible の最新 rpm バージョン(Ansible 1.9.4)を使用しました。それ以降のバージョンは github から入手できます。

この例では、cache-2015.2.2.805.0-lnxrhx64 をインストールしていますが、HealthShare または Ensemble ディストリビューションにも同じ一般的な手順を適用できます。 後述するように、特定のファイル名、ディレクトリパスなどの変数を使用してインストールオプションをパラメーター化します。

この最初の投稿ではタスクを基本的な Caché のインストールに限定しています。そのため、ほとんどのタスクはプラットフォームに依存しません。 Ansible プレイブックが開始する最初のタスクの 1 つは、ターゲットマシンのマニフェスト(オペレーティングシステム、インターフェースカード、メモリ情報、CPU 数、ディスクレイアウトなど)を取得することです。このターゲットオペレーティングシステムの情報は、ターゲットで実行される実際のコマンドから Ansible スクリプトのコマンドを抽象化するためにコマンドが実行される際に使用されます(Red Hat の service httpd on や SLES の /etc/init.d/apache2 など)。

ここでは皆さんがプラットフォームの説明に従い、説明を読んで管理マシンに Ansible をインストールしたと想定します。

https://docs.ansible.com/ansible/2.9_ja/installation_guide/intro_installation.html

Ansible では Linux システムをコントローラーとして使用する必要がありますが、ターゲットシステムは Linux または Windows にすることができます。 Windows ターゲットの詳細については、Ansible のドキュメントを参照してください。

https://docs.ansible.com/ansible/2.9_ja/user_guide/windows.html

コントローラーシステムのインストール例:RHEL/CentOS 7 64ビット上の Ansible

Red Hat または CentOSでは、Ansible を含む epel-release(Enterprise Linux 用の追加パッケージ)RPM を先にインストールする必要があります。 epel プロジェクトは多くの有用なオープンソースパッケージ(ネットワーク、システム管理、監視など)をまとめて提供しており、主要な Linux ディストリビューション向けに設計されています。

[root@localhost tmp]# wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm 
: 
: 
[root@localhost tmp]# rpm -ivh epel-release-7-5.noarch.rpm 
: 
: 
[root@localhost tmp]# yum --enablerepo=epel info ansible 
Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager
Installed Packages
Name : ansible
Arch : noarch
Version : 1.9.4
Release : 1.el7
Size : 7.0 M
Repo : installed
From repo : epel
Summary : SSH-based configuration management, deployment, and task execution system
URL : http://ansible.com
License : GPLv3+
Description : 
 : Ansible is a radically simple model-driven configuration management,
 : multi-node deployment, and remote task execution system. Ansible works
 : over SSH and does not require any software or daemons to be installed
 : on remote nodes. Extension modules can be written in any language and  
 : are transferred to managed machines automatically. 
[root@localhost tmp]# 
[root@localhost tmp]# sudo yum install ansible 
: 
: 
[root@localhost tmp]# ansible --version
  ansible 1.9.4  
  configured module search path = None

成功しました。 始めましょう!

Ansible について

インベントリ、プレイブック、モジュール、ロールなどのさまざまな Ansible コンポーネントの使用方法を Ansible のドキュメントでよく確認する必要があります。

管理を単純化し、大規模で複雑なスクリプトファイルを回避するため、事前定義されたディレクトリ構造と検索パスが使用されています。 この投稿では Ansible の推奨事項を使用し、より大きなインストールの例を作ることを検討する際のモデルとして使用できるファイル構造を使用する予定です。

使用されている Ansible モジュールはコメントが付いた分かりやすいもので、github から入手できます。 ファイルをダウンロードして内容を読み、ワークフローの感触をつかんでください。

https://github.com/intersystems/ansible-deploy-cache

この例のベースディレクトリには、次のファイルがあります。

  • ansible.cfg:Ansible のデフォルト値を変更します。
  • inventory:作業環境を定義し、記述します (サーバー名/IPなど)。
  • >任意の名前<.yml ファイル:これらのファイルは、特定のサーバーのロールに対して実行されるタスクセットを記述します。

用語集

今後の説明をより分かりやすくするため、いくつかの Ansible 用語について簡単に説明します。

モジュールは、システムで実行する自動化アクションを作成するビルディングブロックです。 各モジュールは特定のタスク用に構築されており、パラメーターを使用してそのタスクを変更できます。 例えば、ファイルのコピー、ユーザーの作成、コマンドの実行、サービスの開始などがあります。 現在、デフォルトの Ansible インストールには 400 種類以上のモジュールが含まれており、さらにはコミュニティからも多くのモジュールが提供されていますが、独自に作成することもできます。

モジュールは自動化ワークフローを実行する手段として、プレイとプレイブックを作成するために組み合わされます。 プレイは複数のタスクを持つことができ、プレイブックは複数のプレイを持つことができます。

ロールを使うと、複数のプレイブックを組み合わせることができます。 ロールは、ターゲットサーバーの使用状況に応じてサーバーのコンポーネント構成をグループ化するものと考えることができます。 この投稿のロールの例では、サーバーを構築するための構成レイヤーを構築します。

私のベンチマーク環境では、次のロールを使用してサーバーを構築しています。

  • hs_server_common: OS の構成、Apache のインストール、Caché のインストール。
  •  webserver: Webファイル(csp、html、js など)のコピー、アプリケーション用のApacheの構成。
  • generator: ファイルのコピー、Webストレスジェネレーターのデータベース / ネームスペース / グローバルマッピングなどの作成と構成。
  • dbserver:ファイルのコピー、DBサーバーシステムの設定 / アプリケーションデータベース / ネームスペース / グローバルマッピングなどの構成。

これらのロールを組み合わせて、さまざまなサーバータイプを構築できます。

  • hs_server_common + webserver + generator = Web ストレスジェネレーターサーバー。
  • hs_server_common + webserver = アプリケーション Web サーバー。
  • hs_server_common + dbsevrer = データベースサーバー。

ロールを構成するものや各ロールに含まれる構成は、デプロイされるアプリケーションによって大きく異なります。 この投稿の例では、オペレーティングシステムの事前構成を前提とする最小限のタスクセットを使用しますが、Ansible や Galaxy で利用可能なモジュールを使用すると、はるかに複雑なフル機能のシステム構成が可能になります。

Caché のインストールに関するメモ {.MsoNormal}

私はいくつかの興味く便利な機能を紹介するため例を記載しましたが、その中でも以下は特に注目すべきものです。 注: これらの例は、InterSystems データプラットフォーム(Caché、HealthShare、Ensemble)をインストールするためのガイドとして使用できます。 HealthShare をインストールする例を書きましたが、HealthShare と Caché には同じ機能があります。

./testserver/roles/hs_server_common/tasks/main.yml

これは、一般的な OS の構成、Apache のインストール、Caché のインストールの各タスクに使えるメインラインスクリプトです。 この投稿ではCaché をインストールおよび構成するため、Red Hat のインクルードファイルのみに短縮しています。 Ansible が起動した後、オペレーティングシステムの情報がスクリプトで決定を行うために使用できる ansible_os_family などの ansible* _変数に保持されていることがわかります。

./testserver/roles/hs_server_common/tasks/configure-healthshare2015.yml

これは、Caché をインストールするためのメインスクリプトです。 スクリプトを見ると、次のようなターゲット上のタスクの論理ワークフローがわかります。

  • オペレーティングシステムのユーザーとグループを作成する。
  • コントローラーのマニフェストフォルダーからインストールファイルをコピーする。
  • インストールファイルを解凍する。
  • サイレントインストールを使用して Caché をインストールする(以下の注意書きを参照)。
  • Caché キーファイルをコピーする。
  • デフォルト Caché インスタンスを設定する。
  • Apache を再起動する。
  • Caché を再起動する。

Caché のサイレントインストールには、次のようないくつかのオプションがあります。

  • parameters.is ファイルを使用する。 テンプレートの .isc ファイルは以前のインストールによって作成されたもので、そのまま使用することも、変更して使用することもできます。
  • 環境で設定されたキーと値のペアで cinstall_silent を使用する。
  • %installer クラスを使用する。

この例では、install_silent を使用することを選択しましたが、Ansible でテンプレートファイルを使用する方法を示すため、パラメーターファイルを使用する代替方法もコメントに含めています(./roles/hs_server_common/templates/parameters_hs20152_rh64.isc を参照してください)。

後の投稿では、Caché をインストールする際とデータベースおよびネームスペースを設定する際に %installer クラスを使用する方法を説明します。 インストールオプションの詳細は、Caché のオンラインドキュメントで確認できます。また、コミュニティには %installer クラスの使用に関する素晴らしい投稿もあります。

パラメーターファイルは、旧バージョンの Caché で Caché 組み込みの Apache バージョン以外の Web サーバで CSP Gateway を使用するように Cachéをインストールして構成する場合に役立ちます。 この機能は、Caché 2016.1の %installer で使用できます。

./testserver/roles/hs_server_common/tasks/setup_RedHat.yml

この例は、システム固有の変数(ansible_*)の使用方法とオペレーティングシステム変数の設定を説明するためのものです。

./testserver/roles/hs_server_common/vars/*

変数ファイルのキーと値のペアには変数が含まれています。ご覧のとおり、さまざまな環境や状況で同じスクリプトを再利用することができます。

Caché インストールの実行

この例では、システムが使用可能であり、次のように設定されていることを想定しています。

  1. コントローラーに Ansible がインストールされており、次のディレクトリに GitHub からファイルと構造が読み込まれていること。
  • ./testserver/* :  インベントリ、.yml ファイルなどを含むディレクトリツリー。 次のディレクトリを含みます。
  • ./testserver/Distribution_Files/Cache :  (Caché ディストリビューションと cache.key を含むマニフェスト)。
  1. ターゲットマシンに Red Hat と Apache がインストールされていること。

テスト環境に合わせてインストールをカスタマイズするには、次のファイルを編集する必要があります。

  1. inventory_test

テストサーバー名または IP アドレスを編集する必要があります。

  1. ./testserver/roles/hs_server_common/vars/healthshare2015.yml

テスト環境に合わせてパスを編集する必要があります。 ターゲットサーバーの次のパスを確認します。

  • common_install_base_path: マニフェストファイルがコピーされ、解凍され、Caché インストールが実行されるパスです。
  • ISC_PACKAGE_INSTALLDIR: Caché のインストールディレクトリです。

ディレクトリパスが存在しない場合は、ターゲットサーバー上に作成されます。

注意:自動デプロイの機能の一つに、複数サーバーの並行構築があります。 インベントリファイルに複数のサーバーがある場合、各ステップが各ターゲットサーバーで同時に完了した後、次のステップがグループ内の各サーバーで開始します。 いずれかのサーバーでステップが失敗した場合、スクリプトは停止します。 また、問題解決に役立つエラーメッセージが表示されます。 エラーを解消したら、再び最初から実行するだけです。これは、冪等性が保たれるように設計されているというスクリプトの重要な特徴です。 冪等性とは、あるモジュールが例えばファイルコピーを目的としてステップ内で実行される際、ファイルがすでに存在した場合はそのステップが再実行されず、スクリプトが次のステップに進むことを意味します。 コピーなどのモジュールにはコピーを強制するように設定できるパラメーターがありますが、これはデフォルトではありません。 スクリプトを詳しく調べると、"creates" パラメータがいくつかのケースで使用されていることがわかります。次に例を示します。
- name: unattended install of hs using cinstall_silent
 shell: >
 ISC_PACKAGE_INSTANCENAME="{{ ISC_PACKAGE_INSTANCENAME }}" 
 ISC_PACKAGE_INSTALLDIR="{{ ISC_PACKAGE_INSTALLDIR }}" 
 ISC_PACKAGE_UNICODE="{{ ISC_PACKAGE_UNICODE }}" 
 ISC_PACKAGE_INITIAL_SECURITY="{{ ISC_PACKAGE_INITIAL_SECURITY }}" 
 ISC_PACKAGE_MGRUSER="{{ ISC_PACKAGE_MGRUSER }}" 
 ISC_PACKAGE_MGRGROUP="{{ ISC_PACKAGE_MGRGROUP }}" 
 ISC_PACKAGE_USER_PASSWORD="{{ ISC_PACKAGE_USER_PASSWORD }}" 
 ISC_PACKAGE_CACHEUSER="{{ ISC_PACKAGE_CACHEUSER }}" 
 ISC_PACKAGE_CACHEGROUP="{{ ISC_PACKAGE_CACHEGROUP }}" ./cinstall_silent
 chdir="{{ common_install_base_path }}/{{ hs_install_unpack_path }}" 
 args:
 creates: "{{ ISC_PACKAGE_INSTALLDIR }}/cinstall.log"

上の節は creates パラメータを使用し、このアクションが cinstall.log ファイルを作成することを Ansible モジュール(この場合は shell モジュール)に知らせています。 モジュールがこのファイルを検出した場合(Caché はすでにインストールされている状態)、このステップは実行されません。

すべての設定が完了したら、インストールを実行できます。

$ ansible-playbook dbserver.yml
PLAY [dbservers] **************************************************************
GATHERING FACTS *************************************************************** 
ok: [db1]
TASK: [hs_server_common | include_vars healthshare2015.yml] ******************* 
ok: [db1]
TASK: [hs_server_common | include_vars os-RedHat.yml] ************************* 
ok: [db1]
etc
etc
etc
TASK: [hs_server_common | Create default cache group] ************************* 
changed: [db1]
TASK: [hs_server_common | Create default cache manager group] ***************** 
changed: [db1]
TASK: [hs_server_common | Create default cache user] ************************** 
changed: [db1]
TASK: [hs_server_common | Create default cache system users] ****************** 
changed: [db1]
TASK: [hs_server_common | Create full hs install temp directory] ************** 
changed: [db1]
TASK: [hs_server_common | Check tar file (gunzipped already) does not exist] *** 
ok: [db1]
TASK: [hs_server_common | Copy healthshare install file] ********************** 
changed: [db1]
TASK: [hs_server_common | un zip hs folder] *********************************** 
changed: [db1]
TASK: [hs_server_common | un tar hs install] ********************************** 
changed: [db1]
TASK: [hs_server_common | Create hs install directory] ************************ 
changed: [db1]
TASK: [hs_server_common | touch ztrak.conf.] ********************************** 
changed: [db1]
TASK: [hs_server_common | Process parameters file] **************************** 
changed: [db1]
TASK: [hs_server_common | unattended install of hs using cinstall_silent] ***** 
changed: [db1]
TASK: [hs_server_common | copy hs key] **************************************** 
changed: [db1]
TASK: [hs_server_common | Set default hs instance] **************************** 
changed: [db1]
TASK: [hs_server_common | restart apache to initialize CSP.ini file] ********** 
changed: [db1]
NOTIFIED: [hs_server_common | restart healthshare] **************************** 
changed: [db1]
PLAY RECAP ******************************************************************** 
db1 : ok=32 changed=21 unreachable=0 failed=0

ターゲットサーバーを見ると、db サーバーの Caché が稼働しています。

$ ccontrol list
Configuration 'H2015' (default)
 directory: /test/hs2015
 versionid: 2015.2.1.705.0
 conf file: cache.cpf (SuperServer port = 1972, WebServer = 57772)
 status: running, since Wed Feb 17 15:59:11 2016
 state: ok

最後に

次の投稿では、構成ファイルの編集や %installer クラスを使用したアプリケーションの構成など、他のタスクでスクリプトを構築します。

この投稿に興味を持ち、独自のデプロイを作成し始めた方は、ご質問やご提案がございましたらお気軽にお問い合わせください。 私は仮想化とパフォーマンスについて Global Summit で定期的に講演を行っています。今年の Global Summit に参加される予定の方は自己紹介をお願いします。Ansible やその他のシステムアーキテクチャに関する皆様のご経験をお聞かせください。

0
0 264
記事 Toshihiko Minamoto · 10月 5, 2020 15m read

CachéとCosFakerを使ったテスト駆動開発の簡単な紹介

読了****目安時間: 6分
 

皆さん、こんにちは。

私がTDDに初めて出会ったのは約9年前のことです。すぐに夢中になってしまいました。
最近は非常に人気が出てきているようですが、残念ながら多くの企業ではあまり使われていないようです。 また、主に初心者の方ではありますが、一体それがなんであるのか、どのように使うのかといったことさえも知らない開発者もたくさんいます。

概要

この記事は、%UnitTestでTDDを使用する方法を紹介することを目標としています。 ワークフローを示し、私の最初のプロジェクトであったcosFakerの使用方法を説明します。これはCachéを使って作成したものであり、最近になってOpenExchangeにアップロードしたものです。

では、ベルトを締めて出発しましょう。

TDDとは?

テスト駆動開発(TDD)は、自動テストが失敗した場合に、開発者に新しいコードの書き方のみを示すプログラミング実践として定義できます。
このメリットに関する記事、講義、講演などは数多く存在しますが、どれもが正しい内容です。
コードはテスト済みで生成されること、過度なエンジニアリングを避けるために定義された要件に、システムが実際に適合していることを確認できること、継続的にフィードバックを得ることが挙げられます。

では、TDDを使用しない理由は何でしょうか。 TDDにはどのような問題があるのでしょうか。 答えは単純です。そう、コストです! とにかくコストがかかります!
TDDではより多くの行のコードを記述する必要があるため、その処理には時間がかかります。 しかし、TDDを使用すると、製品を作成するための最終コストは現時点で発生し、後で追加コストをかける必要がありません。
常にテストを実行すれば、早期にエラーを検出できるため、修正にかかるコストが削減されるのです。
というわけで、私からのアドバイスは、ただ実行に移しましょう!

セットアップ

InterSystemsは、%UnitTestの使用方法に関するドキュメントとチュートリアルを用意しています。こちらからお読みください。

開発には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 = “” )
    • 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著 - これについては英語版が出版されているかわかりません。 Java、C#、Ruby、およびPHP版があります。 あまりの素晴らしさに感銘を受けた一冊です。

そしてもちろん、Kent Beckの『Test Driven Development by Example』。

コメントやご質問はお気軽にどうぞ。
これで、おしまいです。

0
0 392
記事 Toshihiko Minamoto · 9月 30, 2020 14m read

InterSystems IRIS のクラスクエリ

InterSystems IRIS(および Cache、Ensemble、HealthShare)のクラスクエリは、SQL クエリを Object Script のコードから分離する便利なツールです。 このクエリの基本的な機能は、同じ SQL クエリを複数の場所で異なる引数で使用する場合にクエリの本文をクラスクエリとして宣言し、このクエリを名前で呼び出すことでコードの重複を回避できるというものです。 このアプローチは、次のレコードを取得するタスクを開発者が定義するカスタムクエリにも便利です。 興味が湧きましたか? それではこのまま読み進めてください!

基本クラスクエリ

簡単に言うと、基本クラスクエリは SQL の SELECT クエリを表現できるようにするものです。 基本クラスクエリは SQL オプティマイザとコンパイラによって通常の SQL クエリと同様に処理されますが、Caché Object Script のコンテキストから実行する際はより便利になります。 また、次のようにクラス定義(メソッドまたはプロパティと同様)でクエリ項目として宣言されます。

  • タイプ: %SQLQuery
  • SQL クエリのすべての引数を引数リストに含める必要があります。
  • クエリタイプ: SELECT
  • コロンを使用して各引数にアクセスします(静的SQLと同様)。
  • 出力結果の名前とデータタイプに関する情報を含む ROWSPEC パラメーターをフィールドの順序と共に定義します。
  • (任意)フィールドに ID が含まれている場合、 CONTAINID パラメーターにその列番号を定義します。 ID を返す必要がない場合は、CONTAINID に0を割り当てます。
  • (任意)静的 SQL の同様のパラメーターに対応し、SQL 式をコンパイルするタイミングを指定する COMPILEMODE パラメーターを定義します。 このパラメーターが IMMEDIATE(デフォルト)に設定されている場合、クエリはクラスと同時にコンパイルされます。 このパラメーターを DYNAMIC に設定すると、クエリは初回実行の前にコンパイルされます(動的 SQL と同様)。
  • (任意)クエリ結果の形式を指定する SELECTMODE パラメーターを定義します。
  • このクエリを SQL プロシージャとして呼び出す場合は、SqlProc プロパティを追加します。
  • クエリの名前を変更する場合は、SqlName プロパティを設定します。 SQL コンテキストでのクエリのデフォルト名は PackageName.ClassName_QueryName です。
  • Caché Studio には、クラスクエリを作成するためのウィザードが搭載されています。


指定した文字で始まるすべてのユーザー名を返す ByName クエリを使った Sample.Person クラスのサンプル定義

Class Sample.Person Extends %Persistent
{
Property Name As %String;
Property DOB As %Date;
Property SSN As %String;
Query ByName(name As %String = "") As %SQLQuery
    (ROWSPEC="ID:%Integer,Name:%String,DOB:%Date,SSN:%String", 
     CONTAINID = 1, SELECTMODE = "RUNTIME", 
     COMPILEMODE = "IMMEDIATE") [ SqlName = SP_Sample_By_Name, SqlProc ]
{
SELECT ID, Name, DOB, SSN
FROM Sample.Person
WHERE (Name %STARTSWITH :name)
ORDER BY Name
}
}

このクエリを次のように Caché Object Script から呼び出すことができます。 

Set statement=##class(%SQL.Statement).%New()   
Set status=statement.%PrepareClassQuery("Sample.Person","ByName")   
If $$$ISERR(status) {
    Do $system.OBJ.DisplayError(status) 
}   
Set resultset=statement.%Execute("A")   
While resultset.%Next() {
    Write !, resultset.%Get("Name")   
}

または、自動生成されたメソッドである「クエリ名Func」を使用して結果セットを取得することもできます。

Set resultset = ##class(Sample.Person).ByNameFunc("A")    
While resultset.%Next() {
    Write !, resultset.%Get("Name")   
}

このクエリは、SQL コンテキストから次の 2 つの方法で呼び出すこともできます。

Call Sample.SP_Sample_By_Name('A')
Select * from Sample.SP_Sample_By_Name('A')

このクラスは、SAMPLES のデフォルト Caché ネームスペースにあります。単純なクエリに関する説明は以上となります。 今度はカスタムクエリを説明します。

カスタムクラスクエリ

基本クラスクエリはほとんどの場合に正常に動作しますが、次のような場合にはアプリケーションでクエリの動作を完全に制御しなければならないことがあります。

  • 選択条件が複雑な場合。 カスタムクエリでは次のレコードを返す Caché Object Script メソッドが独自に実装されているため、選択条件が要件に応じて複雑化している可能性があります。
  • 使用したくない形式の API経由でしかデータにアクセスできない場合。
  • データが(クラスなしで)グローバルに保存されている場合。
  • データにアクセスするために権利を昇格する必要がある場合。
  • データにアクセスするために外部 API を呼び出す必要がある場合。
  • データにアクセスするためにファイルシステムにアクセスする必要がある場合。
  • クエリを実行する前に、追加の操作を実行する必要があります(接続の確立、アクセス許可の確認など)。

では、どのようにカスタムクラスクエリを作成しますか? まず、クエリのワークフロー全体(初期化から破棄まで)を実装する 4 つのメソッドを定義する必要があります。

  • queryName — クエリに関する情報を提供します(基本クラスクエリと同様)
  • queryNameExecute — クエリを作成します
  • queryNameFetch — クエリの次のレコードの結果を取得します
  • queryNameClose — クエリを破棄します

次に、これらのメソッドをさらに詳しく分析します。

queryName メソッド

queryName メソッドはクエリに関する情報を提供します。

  • タイプ: %Query
  • 本文を空白のままにします。
  • 出力結果の名前とデータタイプに関する情報を含む ROWSPEC パラメーターをフィールドの順序と共に定義します。
  • (任意)フィールドに ID が含まれている場合、番号順に対応する CONTAINID パラメーターを定義します。 ID を返さない場合は、CONTAINID に値を割り当てないでください。

例えば、新しい永続クラス Utils.CustomQuery のすべてのインスタンスを 1 つずつ出力する AllRecords クエリ(queryName = AllRecords、メソッドは単に AllRecords と呼ばれる)を作成してみましょう。 まず、新しい永続クラス Utils.CustomQuery を作成します。

Class Utils.CustomQuery Extends (%Persistent, %Populate){ 
Property Prop1 As %String; 
Property Prop2 As %Integer;
}

次に、AllRecords クエリを書きます。

Query AllRecords() As %Query(CONTAINID = 1, ROWSPEC = "Id:%String,Prop1:%String,Prop2:%Integer") [ SqlName = AllRecords, SqlProc ]
{
}

queryNameExecute メソッド
queryNameExecute メソッドはクエリを完全に初期化します。 このメソッドのシグネチャは次のとおりです。

ClassMethod queryNameExecute(ByRef qHandle As %Binary, args) As %Status

説明:

  • qHandle はクエリ実装の他のメソッドとの通信に使用されます。
  • このメソッドは、qHandle を後で queryNameFetch メソッドに渡す状態に設定する必要があります。
  • qHandle は OREF、変数、または多次元変数に設定できます。
  • args はクエリに渡される追加のパラメーターです。 必要な数の引数を追加できます(または引数をまったく使用しないでください)。
  • このメソッドはクエリの初期化ステータスを返します。

では、再び例に戻りましょう。 複数の方法で範囲内を自由に反復処理できます(以下でカスタムクエリの基本的な処理方法を説明します)。ただし、この例では関数 $Order を使用してグローバルを反復処理します。 この場合、qHandle は現在の ID を格納します。追加の引数は必要ないため、arg 引数は必要ありません。 結果は次のようになります。

ClassMethod AllRecordsExecute(ByRef qHandle As %Binary) As %Status {  
    Set qHandle = ""    Quit $$$OK
}

queryNameFetch メソッド
queryNameFetch メソッドは、単一の結果を $List 形式で返します。 このメソッドのシグネチャは次のとおりです。

ClassMethod queryNameFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = queryNameExecute ]

説明:

  • qHandle はクエリ実装の他のメソッドとの通信に使用されます。
  • クエリが実行されると、qHandleには queryNameExecute または queryNameFetch の以前の呼び出しによって指定された値が割り当てられます。
  • すべてのデータが処理されると、レコードが値 %List または空の文字列に設定されます。
  • データの終端に達したら、AtEnd が 1 に設定されます。
  • PlaceAfter キーワードは、int コードでのメソッドの位置を識別します。 "Fetch" メソッドは "Execute" メソッドの後に配置する必要がありますが、これは 静的 SQL(つまり、クエリ内の カーソル)にのみ重要です。

一般に、このメソッド内では次の処理が実行されます。

  1. データの終端に達したかどうかを確認します。
  2. まだデータが残っている場合は新しい %List を作成し、Row 変数に値を割り当てます。
  3. それ以外の場合は、AtEnd を 1 に設定します。
  4. 次の結果を取得するために qHandle を準備します。
  5. ステータスを返します。

この例では次のようになります。

ClassMethod AllRecordsFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status {
    #; ^Utils.CustomQueryD を反復する    
    #; qHandle に次の id を書き込み、新しい id を持つグローバルの値を val に書き込む
    Set qHandle = $Order(^Utils.CustomQueryD(qHandle),1,val)
    #; 残っているデータがあるかどうかを確認する
       If qHandle = "" {
        Set AtEnd = 1
        Set Row = ""
        Quit $$$OK    
    }
    #; 残っていなければ %List を作成する
    #; val = $Lb("", Prop1, Prop2) Storage の定義を参照
    #; Row =$lb(Id,Prop1, Prop2)  AllRecords リクエストについては ROWSPEC を参照
    Set Row = $Lb(qHandle, $Lg(val,2), $Lg(val,3))
    Quit $$$OK 
}

queryNameClose メソッド
queryNameClose メソッドはすべてのデータが取得された時点でクエリを終了します。 このメソッドのシグネチャは次のとおりです。

ClassMethod queryNameClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = queryNameFetch ]

説明:

  • Caché は queryNameFetch メソッドの最後の呼び出しの後にこのメソッドを実行します。
  • つまり、クエリのデストラクタとも言い換えることができます。
  • したがって、その実装ではすべての SQL カーソル、クエリ、ローカル変数を破棄する必要があります。
  • このメソッドは現在のステータスを返します。

この例では、ローカル変数 qHandle を削除する必要があります。

ClassMethod AllRecordsClose(ByRef qHandle As %Binary) As %Status {
    Kill qHandle
    Quit $$$OK
  }

以上です! クラスをコンパイルすると、基本クラスクエリと同様に %SQL.Statement から AllRecords クエリを使用できるようになります。

カスタムクエリの反復ロジック手法

では、カスタムクエリにはどのような手法を使用できるのでしょうか? 一般的には、次の 3 つの基本的な手法があります。

グローバルによる反復
この手法は、グローバルによる反復に $Order などの関数を使用することを基本にしています。 この手法は次の場合に使用できます。

  • データが(クラスなしで)グローバルに保存されている場合。
  • コード内の glorefs の数を減らしたい場合。
  • 結果をグローバルの添え字で並べ替える必要がある/並べ替え可能な場合。


静的 SQL
この手法は、カーソルと静的 SQL を基本にしています。 この手法は次の目的で使用されます。

  • int コードの可読性を高める。
  • カーソルの処理を簡単にする。
  • コンパイルプロセスの高速化(静的 SQL はクラスクエリに含まれるため、コンパイルは 1 回だけ実行されます)。

注意事項:

  • %SQLQuery タイプのクエリから生成されたカーソルには、Q14 などのように自動的に名前が付けられます。
  • クラス内で使用されるすべてのカーソルは、異なる名前を持つ必要があります。
  • エラーメッセージは、名前の末尾に追加の文字があるカーソルの内部名が関係しています。 例えば、カーソル Q140 のエラーは実際にはカーソル Q14 によって引き起こされたものです。
  • PlaceAfter を使用し、カーソルが宣言箇所と同じ int ルーチンで使用されるようにしてください。
  • INTO は FETCH と組み合わせて使用する必要がありますが、DECLARE とは組み合わせないでください。


Utils.CustomQuery の 静的 SQL の例:

Query AllStatic() As %Query(CONTAINID = 1, ROWSPEC = "Id:%String,Prop1:%String,Prop2:%Integer") [ SqlName = AllStatic, SqlProc ]
{
}

ClassMethod AllStaticExecute(ByRef qHandle As %Binary) As %Status
{
    &sql(DECLARE C CURSOR FOR
        SELECT Id, Prop1, Prop2
        FROM Utils.CustomQuery
     )
     &sql(OPEN C)
    Quit $$$OK
}

ClassMethod AllStaticFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = AllStaticExecute ]
{
    #; INTO は FETCH と共に使用する必要あり
    &sql(FETCH C INTO :Id, :Prop1, :Prop2)
    #; データの終端に達したかどうかを確認する
    If (SQLCODE'=0) {
        Set AtEnd = 1
        Set Row = ""
        Quit $$$OK
    }
    Set Row = $Lb(Id, Prop1, Prop2)
    Quit $$$OK
}

ClassMethod AllStaticClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = AllStaticFetch ]
{
    &sql(CLOSE C)
    Quit $$$OK
}

動的 SQL
この手法は、他のクラスクエリと動的 SQL を基本にしています。 この手法は、複数のネームスペースで SQL クエリを実行する、クエリを実行する前に権限を昇格するなどの SQL クエリ自体以外の追加処理を実行する必要がある場合に適切です。

Utils.CustomQuery の動的 SQL の例:

Query AllDynamic() As %Query(CONTAINID = 1, ROWSPEC = "Id:%String,Prop1:%String,Prop2:%Integer") [ SqlName = AllDynamic, SqlProc ]
{
}

ClassMethod AllDynamicExecute(ByRef qHandle As %Binary) As %Status
{
    Set qHandle = ##class(%SQL.Statement).%ExecDirect(,"SELECT * FROM Utils.CustomQuery")
    Quit $$$OK
}

ClassMethod AllDynamicFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status
{
    If qHandle.%Next()=0 {
        Set AtEnd = 1
        Set Row = ""
        Quit $$$OK
    } 
    Set Row = $Lb(qHandle.%Get("Id"), qHandle.%Get("Prop1"), qHandle.%Get("Prop2"))
    Quit $$$OK
}

ClassMethod AllDynamicClose(ByRef qHandle As %Binary) As %Status
{
    Kill qHandle
    Quit $$$OK
}

代替手法:%SQL.CustomResultSet

%SQL.CustomResultSet クラスからサブクラス化してクエリを作成することもできます。 この手法のメリットは次のとおりです。

  • 若干の速度改善
  • すべてのメタデータがクラス定義から取得されるため、ROWSPEC が不要であること
  • オブジェクト指向の設計原則に準拠できること

%SQL.CustomResultSet クラスのサブクラスからクエリを作成するには、次の手順を実行してください。

  1. 結果のフィールドに対応するプロパティを定義します。
  2. クエリコンテキストが格納される private プロパティを定義します。
  3. コンテキストを開始する %OpenCursor メソッド(queryNameExecute と同様)をオーバーライドします。 エラーが発生時に %SQLCODE と %Message も設定します。
  4. 次の結果を取得する %Next メソッド(queryNameFetch と同様)をオーバーライドします。 プロパティを入力します。 このメソッドはすべてのデータが処理された場合に 0 を返し、一部のデータがまだ残っている場合に 1 を返します。
  5. 必要に応じて %CloseCursor メソッド(queryNameClose と同様)をオーバーライドします。


Utils.CustomQuery の %SQL.CustomResultSet の例:

Class Utils.CustomQueryRS Extends %SQL.CustomResultSet
{
Property Id As %String;
Property Prop1 As %String;
Property Prop2 As %Integer;
Method %OpenCursor() As %Library.Status
{
    Set ..Id = ""
    Quit $$$OK
}

Method %Next(ByRef sc As %Library.Status) As %Library.Integer [ PlaceAfter = %Execute ]
{
    Set sc = $$$OK
    Set ..Id = $Order(^Utils.CustomQueryD(..Id),1,val)
    Quit:..Id="" 0
    Set ..Prop1 = $Lg(val,2)
    Set ..Prop2 = $Lg(val,3)
    Quit $$$OK
}
}

これは、次のように Caché Object Script コードから呼び出すことができます。

Set resultset= ##class(Utils.CustomQueryRS).%New()
       While resultset.%Next() {
        Write resultset.Id,!
 }

また、SAMPLES ネームスペースでは、Samples.Person のクエリを実装する Sample.CustomResultSet クラスという別の例を使用することもできます。

最後に

カスタムクエリは、Caché Object Script コードから SQL 式を分離し、純粋な SQL では難しすぎる可能性がある高度な動作を実装するのに役立ちます。

参考情報

クラスクエリ

グローバルによる反復

静的 SQL

動的 SQL

%SQL.CustomResultSet

Utils.CustomQuery クラス

Utils.CustomQueryRS クラス

この記事の執筆にご協力いただいた Alexander Koblov 氏に感謝します。

0
0 801
記事 Megumi Kakechi · 9月 30, 2020 2m read

これはInterSystems FAQ サイトの記事です。

DBLatency の Warning メッセージは、ヘルス・モニタプロセスが定期的にデータベースからのランダム読み取りが完了するまでに要した時間(ミリ秒)を計測していて、設定されている閾値(1000 msec)を超えた場合に出力されます。

mm/dd/yy-18:31:15:060 (2932) 1 [SYSTEM MONITOR] DBLatency(c:\xxx\) Warning: DBLatency = 1510 ( Warnvalue is 1000).


上記例では、C:\xxx\IRIS.DAT(または C:\xxx\CACHE.DAT)へのディスク読み取り I/O に 1510 msec かかったことを示していて、メッセージ出力時のディスク I/O 応答速度が遅いことが考えられます。

ディスク I/O 応答速度が遅い原因としては、ディスク I/O 負荷が高いことが考えられます。

  • 大量のデータ登録や変更を行う処理が実施されていた。
  • 弊社製品以外のソフト(アンチウイルスソフト、バックアップソフト)が動作していた。
  • 弊社製品以外のアプリケーションによるディスク負荷など。
  • 仮想環境の場合に、他の仮想マシン(VM)で上記のような負荷の高い処理が行われ、その影響を受けていた。
0
0 462
記事 Toshihiko Minamoto · 9月 28, 2020 6m read

InterSystems ハッカソンの時、Artem Viznyuk と私のチームは Arduino ボード(1 台)とその各種パーツ(大量)を所有していました。 そのため、私たちは活動方針を決めました。どの Arduino 初心者もそうであるように、気象観測所を作ることにしたのです。 ただし、Caché のデータ永続ストレージと DeepSee による視覚化を利用しました!

デバイスの操作


InterSystems の Caché は、次のようなさまざまな物理デバイスと論理デバイスを直接操作することができます。

  • ターミナル
  • TCP
  • スプーラー
  • プリンター
  • 磁気テープ
  • COM ポート
  • その他多数

Arduino は通信に COM ポートを使用しているため、私たちの準備は万端でした。
一般的に、デバイスの操作は次の5つのステップに分けることができます。

  1. OPEN コマンドでデバイスを現在のプロセスに登録し、デバイスにアクセスする。
  2. USE コマンドでデバイスをプライマリにする。
  3. 実際の操作を行う。 READ でデバイスからデータを受信し、WRITE でデータを送信する。
  4. もう一度 USE でプライマリデバイスを切り替える。
  5. CLOSE コマンドでデバイスを解放する。

理論上はこうなりますが、実際はどうなのでしょうか?

Caché からの点滅操作


まず、私たちはCOM ポートから数値を読み取り、指定したミリ秒の間だけ LED に電力を供給する Arduino デバイスを作りました。

回路:
<div class="spoiler_text">
  <span class="confluence-embedded-file-wrapper"><img class="confluence-embedded-image confluence-external-resource" data-image-src="https://habrastorage.org/files/ade/d64/954/aded6495459b49a4a632d36001f7b5ce.jpg" src="https://habrastorage.org/files/ade/d64/954/aded6495459b49a4a632d36001f7b5ce.jpg" /></span>
</div>
C のコード(Arduino 用)
<div class="spoiler_text">
  <pre><code class="cpp hljs"><span class="hljs-comment">/* Led.ino
  • COM ポートでデータを受信
  • led を ledPin に接続 *

// led を接続するピン#define ledpin 8

// 受信したデータバッファ String inString = "";

// 開始時に 1 回だけ実行 voidsetup(){ Serial.begin(9600); pinMode(ledpin, OUTPUT); digitalWrite(ledpin, LOW); }

// 無期限に実行 voidloop(){ // com からデータを取得 while (Serial.available() > ) { int inChar = Serial.read(); if (isDigit(inChar)) { // 同時に 1 文字// さらにデータバッファに追加 inString += (char)inChar; }

<span class="hljs-comment">// 改行を検出</span>
<span class="hljs-keyword">if</span> (inChar == <span class="hljs-string">'\n'</span>) {
    <span class="hljs-comment">// led の電源をオン</span>
    digitalWrite(ledpin, HIGH);
    <span class="hljs-keyword">int</span> time = inString.toInt();
    delay(time);
    digitalWrite(ledpin, LOW);
    <span class="hljs-comment">// バッファをフラッシュ</span>
    inString = <span class="hljs-string">""</span>;
}

}

}

最後に、COM ポートに 1000\n を送信する Caché のメソッドを掲載します。

/// 1000\n を com ポートに送信ClassMethod SendSerial()
{
    set port = "COM1"open port:(:::" 0801n0":/BAUD=9600)     // デバイスを開くset old = $IO// 現在のプライマリデバイスを記録use port  // com ポートに切り替えwrite$Char(10) // テストデータを送信hang1write1000 _ $Char(10) // 1000\n を送信use old // 古いターミナルに戻るclose port // デバイスを解放
}


«0801n0» は Com ポートにアクセスするためのパラメーターを含む文字列であり、ドキュメントに記載されています。 また、/BAUD=9600 は言うまでもなく接続速度です。

このメソッドをターミナルで実行する場合、次のようになります。

do ##class(Arduino.Habr).SendSerial()


上記を実行しても何も出力されませんが、LED が 1 秒間点滅します。

データ受信

次はキーパッドを Cache に接続し、入力データを受信します。 これは、 認証委任と ZAUTHENTICATE.mac ルーチンを使用するカスタムユーザー認証として使用できます。

回路:
<div class="spoiler_text">
  <span class="confluence-embedded-file-wrapper"><img class="confluence-embedded-image confluence-external-resource" data-image-src="https://habrastorage.org/files/c19/196/d9b/c19196d9b9b64d51974b87c1e94a7650.png" src="https://habrastorage.org/files/c19/196/d9b/c19196d9b9b64d51974b87c1e94a7650.png" /></span>
</div>
C のコード
<div class="spoiler_text">
  <pre><code class="cpp hljs"><span class="hljs-comment">/* Keypadtest.ino * 
  • Keypad ライブラリを使用します。
  • Keypad を rowPins[] と colPins[] で指定されているように
  • Arduino のピンに接続します。

*/

// リポジトリ:// https://github.com/Chris--A/Keypad#include<Keypad.h>

const byte ROWS = 4; // 4 行const byte COLS = 4; // 3 列// 記号をキーにマッピングしますchar keys[ROWS][COLS] = { {'1','2','3','A'}, {'4','5','6','B'}, {'7','8','9','C'}, {'*','0','#','D'} }; // キーパッドのピン 1-8(上下)を Arduino のピン 11-4 に接続します: 1->11, 2->10, ... , 8->4 // キーパッド ROW0, ROW1, ROW2, ROW3 をこの Arduino ピンに接続します byte rowPins[ROWS] = { 7, 6, 5, 4 };

// キーパッド COL0, COL1, COL2 をこの Arduino ピンに接続します byte colPins[COLS] = { 8, 9, 10, 11 };

// Keypad の初期化 Keypad kpd = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

voidsetup(){ Serial.begin(9600); }

voidloop(){ char key = kpd.getKey(); // 押されたキーを取得if(key) { switch (key) { case'#': Serial.println(); default: Serial.print(key); } } }


また、以下は同時に 1 行ずつ COM ポートからデータを取得するために使用される Caché メソッドです。

/// 行末文字を検出するまで COM1 から 1 行を受信しますClassMethod ReceiveOneLine() As%String
{
    port = "COM1"set str=""try {
        open port:(:::" 0801n0":/BAUD=9600)
        set old = $iouse port
        read str // 行末文字を検出するまで読み込みますuse old
        close port
    } catch ex {
        close port
    }
    return str
}


ターミナルで以下を実行します。

write ##class(Arduino.Habr).ReceiveOneLine()

また、(行末文字として送信される)# が押されるまで入力待ち状態になります。その後、入力されたデータがターミナルに表示されます。

以上が Arduino-Caché I/O の基本です。これで、自前の気象観測所を作る準備が整いました。

気象観測所

これで気象観測所に着手できる状態になりました。 私たちはフォトレジスタと DHT11 温湿度センサを使用し、データを収集しました。

回路:

C のコード
<div class="spoiler_text">
  <pre><code class="cpp hljs"><span class="hljs-comment">/* Meteo.ino * 
  • 湿度、気温、光の強度を記録して
  • それらを COM ポートに送信します
  • 出力例: H=1.0;T=1.0;LL=1; */

// フォトレジスタのピン(アナログ)int lightPin = ;

// DHT-11 のピン(デジタル)int DHpin = 8;

// DHT-11 の一次データを保存する配列 byte dat[5];

voidsetup(){ Serial.begin(9600); pinMode(DHpin,OUTPUT); }  voidloop(){ delay(1000); // 1 秒ごとにすべてを測定int lightLevel = analogRead(lightPin); //輝度レベルを取得

temp_hum(); <span class="hljs-comment">// 気温と湿度を dat 変数に格納</span>
<span class="hljs-comment">// And output the result</span>
Serial.print(<span class="hljs-string">"H="</span>); 
Serial.print(dat[<span class="hljs-number"></span>], DEC);   
Serial.print(<span class="hljs-string">'.'</span>); 
Serial.print(dat[<span class="hljs-number">1</span>],DEC);  
Serial.print(<span class="hljs-string">";T="</span>); 
Serial.print(dat[<span class="hljs-number">2</span>], DEC); 
Serial.print(<span class="hljs-string">'.'</span>); 
Serial.print(dat[<span class="hljs-number">3</span>],DEC);   
Serial.print(<span class="hljs-string">";LL="</span>); 
Serial.print(lightLevel);
Serial.println(<span class="hljs-string">";"</span>);

}

// DHT-11 のデータを dat に格納voidtemp_hum(){ digitalWrite(DHpin,LOW); delay(30);
digitalWrite(DHpin,HIGH); delayMicroseconds(40); pinMode(DHpin,INPUT); while(digitalRead(DHpin) == HIGH); delayMicroseconds(80); if(digitalRead(DHpin) == LOW); delayMicroseconds(80); for(int i=;i<4;i++) { dat[i] = read_data(); } pinMode(DHpin,OUTPUT); digitalWrite(DHpin,HIGH); }

// DHT-11 からデータの塊を取得byte read_data(){ byte data; for(int i=; i<8; i++) { if(digitalRead(DHpin) == LOW) { while(digitalRead(DHpin) == LOW); delayMicroseconds(30); if(digitalRead(DHpin) == HIGH) { data |= (1<<(7-i)); } while(digitalRead(DHpin) == HIGH); } } return data; }


このコードを Arduino に読み込んだ後、次の形式で COM ポートからデータの送信を開始しました。

H=34.0;T=24.0;LL=605;


説明:

  • H — 湿度(0〜100%)
  • T — 気温(摂氏)
  • LL — 輝度(0〜1023)


新しい Arduino.Info クラスを作成するため、このデータを Caché に保存します。

 
<div class="spoiler_text">
  <pre><code class="hljs cos"><span class="hljs-keyword">Class</span> Arduino.Info <span class="hljs-keyword">Extends</span> <span class="hljs-built_in">%Persistent</span>

{

Parameter SerialPort As%String = "com1";

Property DateTime As%DateTime;

Property Temperature As%Double;

Property Humidity As%Double(MAXVAL = 100, MINVAL = 0);

Property Brightness As%Double(MAXVAL = 100, MINVAL = 0);

Property Volume As%Double(MAXVAL = 100, MINVAL = 0);

ClassMethod AddNew(Temperature = 0, Humidity = 0, Brightness = 0, Volume = 0) { set obj = ..%New() set obj.DateTime=$ZDT($H,3,1) set obj.Temperature=Temperature set obj.Humidity=Humidity set obj.Brightness=Brightness/1023*100set obj.Volume=Volume write$SYSTEM.Status.DisplayError(obj.%Save()) }

その後は Arduino からデータを受信し、次のように Arduino.Info クラスオブジェクトに変換するメソッドを作成しました。

/// 次の形式で生データを受信します: H=34.0;T=24.0;LL=605;\n
/// Arduino.Info オブジェクトに変換します
ClassMethod ReceiveSerial(port = {..#SerialPort})
{
    try {
        open port:(:::" 0801n0":/BAUD=9600)
        set old = $IO
        use port
        for {
            read x //1 行読み込む
            if (x '= "") {
                   set Humidity = $Piece($Piece(x,";",1),"=",2)
                set Temperature =  $Piece($Piece(x,";",2),"=",2)
                set Brightness =  $Piece($Piece(x,";",3),"=",2)
                do ..AddNew(Temperature,Humidity,Brightness) // データを追加
            }
        }
    } catch anyError {
        close port
    }
}


最後に Arduino を接続し、ReceiveSerial メソッドを実行しました。

write ##class(Arduino.Info).ReceiveSerial()

このメソッドは、Arduino から無期限にデータを受信して保存します。

データの視覚化


デバイスを作成後、室外に設置して夜間にデータを収集するようにしました。

翌朝には 36000 件以上のレコードを取得できましたので、そのデータを DeepSeeMDX2JSON サーバーサイド REST API と DeepSeeWeb ダッシュボードレンダラーを使用して視覚化することにしました。結果は次のとおりです。


以下は輝度です。 5:50 頃に日の出をはっきりと確認できます。


以下は気温と湿度のグラフです。

湿度と気温の負の相関がはっきりとわかります。

デモ

こちらからアクセスできます。

まとめ


InterSystems Cachéを使用すると、多数の異なるデバイスと直接通信できます。 データ処理および視覚化ソリューションを迅速に開発できます。自前の気象観測所を作成して Caché に接続し、結果を視覚化するまでには約 4 時間かかりましたが、その大部分は回路の設計と C のコーディングに費やした時間です。


»ドキュメント
»GitHub リポジトリ

0
0 924
記事 Hiroshi Sato · 9月 28, 2020 1m read

これはInterSystems FAQ サイトの記事です。
 

%Library.Routine (以降、%Routine)クラスのRoutineListクエリを使用して、プログラムからルーチンの日付やサイズを取得できます。

RoutineListクエリには、引数があり、検索対象となるルーチン名を前方一致や中間一致で指定できます。
(ワイルドカードには、* か ? を指定します。)

以下の例では、*.MAC を引数に指定して、検索をしています。

 SET tStatement ##class(%SQL.Statement).%New()
 DO tStatement.%PrepareClassQuery("%Routine","RoutineList")
 SET rs tStatement.%Execute("*.MAC",,0)
 DO rs.%Display()


ルーチン一覧の他に、クラス定義一覧も取得できます。

0
0 424
記事 Hiroshi Sato · 9月 28, 2020 1m read

これはInterSystems FAQ サイトの記事です。
 

言語バインディングなどのサーバクライアント型で接続した場合、クライアントのマシン名は以下の処理で取得できます。

set client=##CLASS(%SYS.ProcessQuery).Open("P"_$j).ClientNodeName

クライアントのIPアドレスは以下の処理で取得できます。

set ip=##CLASS(%SYS.ProcessQuery).Open("P"_$j).ClientIPAddress

* サーバーとクライアントが同一マシンの場合、上記で取得できるIPアドレスは、127.0.0.1になります。

0
0 734
記事 Megumi Kakechi · 9月 27, 2020 2m read

これはInterSystems FAQ サイトの記事です。
コンソールログ(message.log/cconsole.log)に、以下のようなログが出力される場合があります。

MM/DD/YY-hh:mm:ss:sss (pid) 2 CP: Pausing users because the Write Daemon has not shown
   signs of activity for xxx seconds. Users will resume if Write Daemon completes a
   pass or writes to disk (wdpass=yyyy).


このメッセージは、コントロールプロセスが出力しています。
このプロセスは、ライトデーモン(WriteDaemon)等の主要なシステムプロセスを監視しています。

0
0 420
記事 Tomoko Furuzono · 9月 17, 2020 17m read

Caché 2017以降のSQLエンジンには新しい統計一式が含まれています。 これらの統計は、クエリの実行回数とその実行所要時間を記録します。

これは、多くのSQLステートメントを含むアプリケーションのパフォーマンスを監視する人や最適化を試みる人にとっては宝物のような機能ですが、一部の人々が望むほどデータにアクセスするのは簡単ではありません。

この記事と関連するサンプルコードでは、このような情報の使用方法と、日次統計の概要を定期的に抽出してアプリケーションのSQLパフォーマンス履歴記録を保持する方法について説明します。
※詳細については、下記ドキュメントページもご参考になさってください。

https://docs.intersystems.com/iris20201/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_sqlstmts

記録内容

SQLステートメントが実行されるたびに、所要時間が記録されます。 この処理は非常に軽量であり、オフにすることはできません。 コストを最小限に抑えるため、統計はメモリに保持されてから定期的にディスクに書き込まれます。 このデータには当日にクエリが実行された回数と、その平均所要時間と合計所要時間が含まれます。

0
0 355
記事 Hiroshi Sato · 6月 29, 2020 2m read

Config.Configurationクラス、SYS.Databaseクラスのメソッドを使用して、ネームスペース・データベースの作成及び登録をターミナルから実行することができます。
以下はデータベースファル/CacheDB/AAA/cache.datを作成し、構成ファイル(cache.cpf)にデータベース AAA、及び、ネームスペースAAAの登録を行う一連の実行例です。 *実行は、%SYSネームスペースで行って下さい。*
 

Set Directory="/CacheDB/AAA/"Setx=$ZF(-100, "/shell", "mkdir", Directory)
 Set db=##Class(SYS.Database).%New()
 Set db.Directory=Directory
 Set status=db.%Save()
 Set DBName="AAA"Set status=##class(Config.Configuration).AddDatabase(DBName,Directory)
 Set NSName=DBName
 Set status=##class(Config.Configuration).AddNamespace(NSName,DBName)
3
0 768
記事 Mihoko Iijima · 9月 16, 2020 2m read

これはInterSystems FAQ サイトの記事です。

InterSystemsでは、パフォーマンスの影響や動作不調を避けるために、データベースファイルを含む主要なコンポーネントをウイルススキャンの対象から除外していただくことを推奨しております。

具体的には、アンチウイルスソフトのスキャン対象から、以下のファイルを除外してください。

  • データベースファイル(IRIS.DAT/CACHE.DAT)
  • <インストールディレクトリ>/bin 内の実行可能ファイル(EXE) 
  • ライトイメージジャーナル(WIJ)
  • ジャーナルディレクトリ内のジャーナルファイル

上記ファイルが、アンチウイルスソフトで除外設定されていない場合、「SERIOUS DISK WRITE ERROR...」のようなエラーが発生する場合があります。

このエラーは、実際にハード的なディスク障害が原因であることもありますが、それ以外にアンチウィルスソフトのウィルスチェックなどによって、ディスクへの書き込みが阻止された場合にも起こります。

詳細は、下記ドキュメントページをご参照ください。

インターシステムズ製品と連係して動作するようにサードパーティ・ソフトウェアを構成する方法【IRIS】
インターシステムズ製品と連係して動作するようにサードパーティ・ソフトウェアを構成する方法

0
0 417
記事 Mihoko Iijima · 9月 16, 2020 1m read

これはInterSystems FAQ サイトの記事です。

サーバ側ロジックの記載に使用する ObjectScript でのエラーが発生した場合の対処方法については「ObjectScriptでエラーが発生したら」にまとめています。

ぜひご参照ください!
 

0
0 250
記事 Tomoko Furuzono · 9月 15, 2020 14m read

Cachéデータベースのオブジェクトおよびリレーショナルデータモデルは、標準、ビットマップビットスライスの3種類のインデックスをサポートします。 これら3つのネイティブタイプに加えて、開発者は独自のカスタムタイプのインデックスを宣言し、バージョン2013.1以降の任意のクラスで使用できます。 たとえば、iFindテキストインデックスは、そのメカニズムを使用しています。

カスタムインデックスタイプは、挿入、更新、削除を実行するための%Library.FunctionalIndexインターフェースのメソッドを実装するクラスです。 新しいインデックスを宣言するときに、そのようなクラスをインデックスタイプとして指定できます。

例:

Property A As %String;
Property B As %String;
Index someind On (A,B) As CustomPackage.CustomIndex;

 CustomPackage.CustomIndex クラスは、カスタムインデックスを実装するまさにそのクラスです。

0
0 315
記事 Toshihiko Minamoto · 9月 14, 2020 5m read

この記事は、Caché 以前の歴史に関するかなり個人的な見方を書いたものです。
過去の記事で紹介された Mike Kadow 氏の素晴らしい著書に対抗するつもりはありません。
私たちの歴史的背景は異なるため、この記事は過去に対する別の視点を生み出すことを意図しています。

全体的な話は、1966 年に MGH(マサチューセッツ総合病院)で 8K のメモリ(18 ビットワード)[現在 = 18K バイト] を搭載した PDP-7(シリアル番号#103)
が予備のシステムとして使用されたことから始まります。

「シリアル番号 103 は、
現在 [2014] は MGH のコックスがんセンターの敷地になっている取り壊されたセイヤービルの地下にありました。」
「Octo Barnett の指導の下、Neil Papparlardo と Curt Marble が
このマシンで最初のソフトウェアを開発し、リリースしました。」
 
彼らはこのソフトウェアを MUMPS と名付けました。 (引用元


PDP-7

言語自体は古い形式の Basic にかなり近いものでした。
しかし、他のプログラミング言語に比べて大幅に改善されている点がありました。

  • その画期的なアイデアは、ファイルシステムを処理する必要なしに永続的なデータを格納および取得することでした。
    これは、利用可能なメモリの 30 %以上を容易に消費し、ソートやインデックス付けに対応していない
    他のシステムと比較すると、当時はものすごく先進的なものでした。
  • (ALGOL、FORTRAN のように)強力なデータ型や名前によって強制されるデータ型がない。
    これら(データ型の制約)によって形式上のエラーと変換の原因が無限に存在することになります。
  • 構造が固定されていない動的(疎な)配列とメモリ内に事前に割り当てられた半分空の空間がある。
  • 可変長の構造化インデックス(添え字)で永続的なデータにインデックスを付けることで
    ソート、グループ化、サブグループ化などを簡単にしている。

これを COBOL、FORTRAN、PL/1 の古いコードと比較すると、この革命の規模がいかに大きいかわかるでしょう。
この新しいソフトウェアは PDP-11に至るまでの急速なハードウェアの発展と共に進化し、
最終的には MUMPS 4b として知られるようになりました。

1978 年は注目に値する年でした。
- InterSystems が Terry Ragon によって設立されました。
- DEC が初の VAX-11 クラスタを導入しました(確かカーネギーメロン大学で)。
- DEC が DSM-11(Digital Standard Mumps)を完成させました。
さらに、当時の最新標準規格に従いつつストレージのパフォーマンスを劇的に改善する
新しい Global モジュールがありました。
これは、データベースの名を冠した他のどの製品よりも圧倒的に性能が優れていました。
この Global モジュールの作成者は、国際的な経験を持つ優秀なエンジニアである Terry Ragon でした。
- 私自身も 1978 年に DSM-11 のセールス兼サポートエンジニアとして DEC に
入社し、メイナードでの最初のサポート向けトレーニングで Terry に会いました。

当時の DEC は新しい VAX-11 と VAX-Cluster に完全に舞い上がっていました。
新しい高性能 DB は無視され、その能力は完全に誤解されていました。
新しいハードウェアを有効活用するために DSM を VAX でネイティブに使いたいというソフトウェア開発者の要望はすべて無視されていました。

このように顧客の要望がいつまでも無視され続けていたことが、私が自身の顧客から誘われるきっかけになりました。
対応してもらえないのなら、私たちと一緒にやりましょう!」[このような OS をゼロから作成するオファーはどのくらいありますか?]
私は誘いを断り切れず、ベアボーンの VAX-750 でゼロから顧客と一緒に OS を書き上げました。
この OS は VISOS と名付けられ、サポートされている VAX のモデルが存在する間は生き残っていました。

しばらくして、DEC は DSM を VMS の上にレイヤードプロダクトとして発表しました。
当初、性能は基盤となる RMS によって決まっており、ハードウェアの処理能力の向上は反映されていませんでした。
私は関心を失い、それ以降のことはもう気にしませんでした。
数年後、DEC が愛されていなかった製品である DSM を InterSystems に売却したことは最高の出来事だと思っています。
ほどなくして DEC は自身を売却することになりました。

私は 20 年後に InterSystems に入社したとき、Caché の中で再び自分が実装していた部分をたくさん見つけました。
そのため、私は故郷にいるようなとても温かい気持ちを味わうことができたのです。

現在の Caché はその前身からは大きく変わっていますが、ソースには互換性があります。
Globals の力はまだ残っているのです。 性能面で競合する DB を凌駕できない事例はほとんど
存在しないかもしれません。 数多くの中から私が気に入っている例をご紹介します。
欧州宇宙機関(ESA)が実行している GAIA プロジェクト

この投稿は、技術的な歴史と個人的な物語の一部を間違いなくかなり個人的な視点で執筆したものです。
ご質問がある方や、私の誤りを正す必要があると感じた方は遠慮無くご連絡ください。
私はウィーン(オーストリア)にいるため、遠く離れた天の川の境界から
ケンブリッジ、メイナード、ボストンでの決定を見ているような感覚を常に抱いていました。ウィンク

0
0 406
記事 Tomoko Furuzono · 9月 10, 2020 3m read

日付範囲クエリが極端に遅くなっていませんか?  SQLのパフォーマンスが低下していませんか?

日付範囲のサブクエリをまだご覧になっていない場合は、前回の投稿をご確認ください。
https://jp.community.intersystems.com/post/日付範囲クエリのsqlパフォーマンスを改善する


なぜ、こうも日付クエリに注目しているのでしょうか? それは、日付クエリが重要だからです。 それは報告であり、統計であり、自分の素晴らしい仕事を上司に証明するための数字です(もちろん、あなたが実際にそうしていればの話ですが )。では、前回と同じようなテーブルを見てみましょう。ただし、実際には MAXLEN と MINLEN を次のように適切に定義します。

Class User.DateQ extends %Persistent 
{
Property DateSubmitted as %Date;
Property Data as %String (MAXLEN=200, MINLEN=100);
Index DateIdx on DateSubmitted;
}

では、先月のすべてのデータを取得したい場合を見てみましょう。 次のようなクエリを書き、「良い仕事」を考えます。

0
0 766
記事 Toshihiko Minamoto · 9月 9, 2020 2m read

最近の大規模なベンチマーク活動で、アプリケーションのスケーリングに悪影響を与える過度の %sys CPU 時間が観察されました。

問題

TZ 環境変数が設定されていないため、 localtime() システムコールに多くの時間が費やされていることがわかりました。 観察結果を確認するための単純なテストルーチンが作成されましたが、TZ が設定されている場合と TZ が未設定の場合とでは経過時間と必要な CPUリソースが驚くほど違っていました。 TZ が設定されていない場合、localtime() から /etc/local_time への stat() システムコールの継承使用は非常に負荷が高いことがわかりました。

推奨事項

InterSystems は、x86 または Linux on Power のいずれの Linux インストール環境でも、TZ 環境変数を適切に設定して最適なパフォーマンスを確保することを強く推奨しています。  詳細については、「man tzset」を参照してください。

現在の Caché 2016.1のフィールドテストでは日付および時刻関連の関数に関する最適化が行われており、初期テストでは大幅に改善していることがわかっています。 以下は、TZ が設定されていない場合に PowerPC上のLinuxで新しい内部関数の呼び出しをテストした結果の出力例です。

Caché 2016.1 FT 未満:

real    0m22.60suser  0m1.64ssys   0m20.89s

Caché 2016.1 FT の場合:

real  0m0.40s
user    0m0.37s
sys 0m0.00s

皆さんが TZ 環境変数に関してアプリケーションで経験したことや、TZ 環境変数の設定有無による Caché  2016.1 のフィールドテストへの影響についてコメントを投稿してください。

0
0 241
記事 Tomoko Furuzono · 9月 7, 2020 7m read

アプリケーションに、効率的に検索したいフリーテキストを含むフィールドがありますか?これまで複数の方法を試してみたものの、顧客が要求するパフォーマンスを満たせなかった経験はありませんか?私は変わった手段を使ってあらゆる問題を解決できると思っていませんか。もうご存じですよね。私ができるのは、パフォーマンス低下に対処する優れたソリューションを提供することです。

いつものように、要約版が必要な場合は記事の最後まで飛ばしてください。ただ、それだと私はがっかりしてしまいますが。

最近の(2015.1以降の)バージョンのCaché/Ensemble/HealthShareのSAMPLESネームスペースでSample.Companyのバージョンを開くと、擬似ランダムに生成されたテキストであるMissionフィールドが表示されます。このテキストフィールドを検索してみましょう。 私はこの演習のために約256,246社データを生成しましたが、ご自身で必要な数の会社を生成してから同じ手順に従ってください。例えば、次のクエリを実行するとしましょう。

SELECT * FROM Sample.Company WHERE Mission LIKE ‘% agile %’

0
0 370
記事 Toshihiko Minamoto · 8月 27, 2020 37m read

企業はグローバルコンピューティングインフラストラクチャを迅速かつ効率的に成長させて管理すると同時に、資本コストと費用を最適化して管理する必要があります。 Amazon Web Services(AWS)および Elastic Compute Cloud(EC2)コンピューティングおよびストレージサービスは、非常に堅牢なグローバルコンピューティングインフラストラクチャを提供することにより、最も要求の厳しいCachéベースのアプリケーションのニーズを満たします。 企業は Amazon EC2 インフラストラクチャを利用し、コンピューティング能力を迅速にプロビジョニングしたり、既存のオンプレミスインフラストラクチャをクラウドに迅速かつ柔軟に拡張したりできます。 AWSは、セキュリティ、ネットワーキング、コンピューティング、ストレージのための豊富なサービスセットと堅牢でエンタープライズレベルの仕組みを提供します。

AWS の中核となっているのは、Amazon EC2 です。 さまざまな OS とマシン構成(CPU、RAM、ネットワークなど)をサポートするクラウドコンピューティングインフラストラクチャです。 AWS は、Amazon マシンイメージ(AMI)と呼ばれる構成済みの仮想マシン(VM)イメージを、さまざまな Linux® および Windows ディストリビューションとバージョンを含むゲスト OS と共に提供しています。AWS で実行される仮想化インスタンスの基盤として、追加のソフトウェアが使用される場合もあります。 このような AMI を最初に使用し、追加のソフトウェアやデータなどをインスタンス化してインストールまたは構成し、アプリケーション固有またはワークロード固有の AMI を作成できます。 

あらゆるプラットフォームやデプロイメントモデルと同様に、パフォーマンス、可用性、運用、管理手順などのアプリケーション環境に関わるすべての側面が正しく機能するように注意を払う必要があります。 

このドキュメントでは、次のような各分野の詳細について説明しています。

  • ネットワークのセットアップと構成。 このセクションでは、リファレンスアーキテクチャ内のさまざまなレイヤーとロールの論理サーバーグループをサポートするサブネットなど、AWS 内で Caché ベースのアプリケーションのネットワークをセットアップする方法について説明します。
  • サーバーのセットアップと構成。 このセクションでは、各レイヤーのさまざまなサーバーの設計に関連するサービスとリソースについて説明します。 また、アベイラビリティーゾーン全体で高可用性を実現するためのアーキテクチャも取り上げます。
  • セキュリティ。 このセクションではインスタンスとネットワークのセキュリティを構成し、ソリューション全体やレイヤーとインスタンス間で認可済みアクセスを実現する方法など、AWS のセキュリティメカニズムについて説明します。
  • デプロイと管理。 このセクションでは、パッケージ化、デプロイ、監視、および管理の詳細について説明します。 

# アーキテクチャとデプロイシナリオ

このドキュメントでは、Caché、Ensemble、HealthShare、TrakCare、および DeepSee、iKnow、CSP、Zen、Zen Mojo といった関連する組み込みテクノロジーなどの InterSystems のテクノロジーに基づく堅牢かつパフォーマンスと可用性の高いアプリケーションを提供する AWS 内のいくつかのリファレンスアーキテクチャをサンプルとして提供します。

Caché と関連コンポーネントを AWS でホストする方法を理解するため、まずは典型的な Caché デプロイのアーキテクチャとコンポーネントを確認し、いくつかの一般的なシナリオとトポロジを探っていきましょう。

Caché アーキテクチャのレビュー

InterSystems のデータプラットフォームは絶え間なく進化しており、高度なデータベース管理システムと迅速なアプリケーション開発環境を提供することで、複雑なデータモデルの処理と分析、および Web アプリケーションとモバイルアプリケーションの開発に飛躍的進歩をもたらしています。

これは、複数モードのデータアクセスを提供する新世代のデータベーステクノロジーです。 データは単一の統合データディクショナリに1回だけ記述され、オブジェクトアクセス、高性能 SQL、および強力な多次元アクセスによりすぐに利用できます(これらはすべて同じデータに同時にアクセスできます)。

アクセス可能な Caché の高レベルアーキテクチャコンポーネントの階層とサービスを図1に示します。これらの一般的な階層は、InterSystems TrakCare および HealthShare 製品の両方にも適用されます。

図 1: 高レベルのコンポーネント階層

##一般的なデプロイシナリオ

デプロイにはさまざまな組み合わせが可能ですが、このドキュメントではハイブリッドモデルと完全なクラウドホストモデルの 2 つのシナリオについて説明します。 

###ハイブリッドモデル

このシナリオでは、企業はオンプレミスのエンタープライズリソースと AWS EC2 リソースの両方を、必要に応じて災害復旧、社内メンテナンスでの不測の事態、プラットフォームの変更計画、または短期的あるいは長期的な能力の増強に活用したいと考えています。 このモデルは、オンプレミスのフェイルオーバーミラーメンバーセットに対して高レベルの可用性を提供し、事業継続性と災害復旧を実現できます。  

このシナリオでのこのモデルは、オンプレミスデプロイと AWS アベイラビリティーゾーン間の接続に VPN トンネルを利用し、AWS リソースを企業のデータセンターの拡張先として提供しています。 AWS Direct Connect などの他の接続方法もあります。 ただし、このドキュメントの中では説明していません。  AWS Direct Connect に関する詳細については、こちらを参照してください。

この例の Amazon Virtual Private Cloud(VPC)を設定してオンプレミスのデータセンターの災害復旧に対応する方法については、こちらを参照してください。

図 2: オンプレミスの障害復旧に AWS VPC を使用するハイブリッドモデル

上記の例は、AWS VPC に VPN で接続されたオンプレミスデータセンター内で動作するフェイルオーバーミラーペアを示しています。 図の中の VPC は、特定 AWS リージョンにある 2 つのアベイラビリティーゾーンで複数のサブネットを提供しています。 レジリエンシーを高めるため、2 つの災害復旧(DR)非同期ミラーメンバー(各アベイラビリティーゾーンに 1 つ)があります。   

###クラウドホストモデル

このシナリオでは、データレイヤーとプレゼンテーションレイヤーの両方を含む Caché ベースのアプリケーションは、単一 AWS リージョン内の複数のアベイラビリティーゾーンを使用して AWS クラウド内に完全に維持されています。 同じVPNトンネル、AWS Direct Connect、さらには純粋なインターネット接続モデルも利用できます。

図 3: 完全な本番ワークロードに対応したクラウドホストモデル

上記の図 3 の例は、VPC 内にアプリケーションの本番環境全体をデプロイするデプロイモデルを示しています。 このモデルは、アベイラビリティーゾーン間の同期的なフェイルオーバーミラーリングに対応した 2 つのアベイラビリティーゾーンと共に、ロードバランシングされた Web サーバーと関連するアプリケーションサーバーを ECP クライアントとして活用しています。 それぞれの層は、ネットワークセキュリティ制御のために個別のセキュリティグループに分離されています。 IPアドレスと一定範囲のポートは、アプリケーションのニーズに応じて必要な場合にのみ開かれます。

# ストレージとコンピューティングリソース

###ストレージ

複数のストレージタイプを選択できます。 このリファレンスアーキテクチャでは、Amazon Elastic Block Store(Amazon EBS)と Amazon EC2 Instance Store インスタンスストア(エフェメラルドライブとも呼ばれます)のボリュームについて想定されるいくつかの使用事例を説明します。 さまざまなストレージオプションの詳細は、こちらこちらをご覧ください。

Elastic Block Storage(EBS)

EBS は、Linux または Windows で従来型のファイルシステムとしてフォーマットおよびマウント可能で、Amazon EC2 インスタンス(仮想マシン)で使用する耐久性のあるブロックレベルのストレージを提供します。また、最も重要な事ですが、ボリュームはデータベースにとって重要な単一の Amazon EC2 インスタントの稼働期間とは独立して存続するインスタンス外のストレージになっています。

さらに、Amazon EBS はボリュームのポイントインタイムスナップショットを作成し、Amazon S3 に保存する機能を提供します。 これらのスナップショットは新しい Amazon EBS ボリュームの開始ポイントとして、および長期的な耐久性の確保を目的としてデータを保護するために使用できます。 同じスナップショットを使用して必要な数のボリュームをインスタンス化することができます。 これらのスナップショットは AWS リージョン間でコピーできるため、複数の AWS リージョンを地理的な拡張、データセンターの移行、災害復旧により簡単に活用できるようになっています。 Amazon EBS ボリュームのサイズの範囲は 1 GB から 16 TB の範囲で、1 GB 単位で割り当てられます。

Amazon EBS 内には、マグネティックボリューム、汎用(SSD)、プロビジョンド IOPS(SSD)という 3 種類のタイプがあります。 次のサブセクションでは、それぞれについて簡単に説明します。

マグネティックボリューム

マグネティックボリュームは、中程度または突発的な I/O 要件を持つアプリケーションにコスト効率の高いストレージを提供します。 マグネティックボリュームは平均で毎秒約 100 件の入出力操作(IOPS)を実現するように設計されており、ベストエフォートで数百 IOPS にバーストする機能を備えています。 バースト機能がインスタンスの起動時間を高速にするため、マグネティックボリュームは起動ボリュームとしての使用にも適しています。

汎用(SSD)

汎用(SSD)ボリュームは、幅広いワークロードに適した費用対効果の高いストレージを提供します。 これらのボリュームは、数ミリ秒のレイテンシ、長時間にわたって 3,000 IOPS までバーストする機能、3 IOPS/GB から最大 10,000 IOPS(3,334 GBの場合)までのベースラインパフォーマンスを提供します。 汎用(SSD)ボリュームのサイズは 1 GB から 16 TB の範囲です。

プロビジョンド IOPS(SSD)

プロビジョンド IOPS(SSD)ボリュームは、ランダムアクセス I/O スループットにおけるストレージのパフォーマンスと整合性に影響されやすいデータベースワークロードなど、I/O 集中型のワークロードに予測可能な高パフォーマンスを提供するように設計されています。 ボリュームの作成時に IOPS レートを指定すると、Amazon EBS は 1 年間のうち 99.9% の時間についてプロビジョニングされた IOPS の 10% の範囲内でパフォーマンスを提供します。 プロビジョンド IOPS(SSD)ボリュームのサイズは 4 GB から 16 TB の範囲で、ボリュームごとに最大20,000 IOPS をプロビジョニングできます。 プロビジョニングされた IOPS とリクエストされたボリュームサイズの最大比率は 30 です。例えば、3,000 IOPS のボリュームのサイズは少なくとも 100 GB である必要があります。 プロビジョンド IOPS(SSD)ボリュームのスループットは、プロビジョニングされた IOPS ごとに 256 KB であり、最大で 320 MB/秒となります(1,280 IOPS の場合)。

このドキュメントで説明するアーキテクチャでは EBS ボリュームを使用しています。予測可能な低レイテンシの入出力操作毎秒(IOPS)とスループットが必要な本番ワークロードに適しているためです。 すべての VM タイプが EBS ストレージにアクセスできるわけではないため、特定の EC2 インスタンスのタイプを選択する際には注意が必要です。

注意: Amazon EBS ボリュームはネットワークに接続されたデバイスであるため、Amazon EC2 インスタンスによって実行される他のネットワーク I/O と共有ネットワークの全体的な負荷も、個々の Amazon EBS ボリュームのパフォーマンスに影響を与える可能性があります。 Amazon EC2 インスタンスが Amazon EBS ボリュームでプロビジョンド IOPS を十分に活用できるようにするには、選択した Amazon EC2 インスタンスのタイプを Amazon EBS 最適化インスタンスとして起動してください。 

EBS ボリュームの詳細については、こちらを参照してください。

EC2 インスタンスストレージ(エフェメラルドライブ)

EC2 インスタンスストレージは、稼働中の Amazon EC2 インスタンスをホストしているのと同じ物理サーバー上に構成され、接続されたディスクストレージのブロックで構成されています。 提供されるディスクストレージの容量は、Amazon EC2 インスタンスのタイプによって異なります。 インスタンスストレージを提供する Amazon EC2 インスタンスファミリーでは、インスタンスが大きいほど、インスタンスストアのボリュームが大きくなる傾向があります。

Amazon EC2 インスタンスファミリーにはストレージ最適化(I2)と高密度ストレージ(D2)があり、それぞれが特定の使用事例を対象とした特殊な用途のインスタンスストレージを提供しています。 例えば、I2 インスタンスは 365,000 超のランダム読み取り IOPS と 315,000 書き込み IOPS に対応できる非常に高速な SSD ベースのインスタンスストレージを提供し、コスト的に魅力的な価格モデルを提供しています。

このストレージは EBS ボリュームのように永続的ではなく、インスタンスの存続期間中のみ使用できるものであるため、接続を解除したり別のインスタンスに接続したりすることはできません。 インスタンスストレージは、絶えず変化する情報を一時的に保存するためのものです。 InterSystems のテクノロジーと製品の領域では、エンタープライズサービスバス(ESB)としての Ensemble や Health Connect の使用、エンタープライズキャッシュプロトコル(ECP)を使用するアプリケーションサーバー、または CSP Gateway と Web サーバーの使用などが、このタイプのストレージとストレージ最適化インスタンスタイプに適した使用事例になるでしょう。また、プロビジョニングツールと自動化ツールを使用することで、これらの効率を合理化して柔軟性を高めることができます。

インスタンスストアの詳細については、こちらを参照してください。

コンピューティング

EC2 インスタンス

さまざまな使用事例に最適化された多数のインスタンスタイプを利用できます。 インスタンスタイプは、CPU、メモリ、ストレージ、ネットワーク容量のさまざまな組み合わせで構成されており、それらを無数に組み合わせてアプリケーションのリソース要件を適切なサイズに調整できます。

このドキュメントでは、Amazon EC2 の M4 汎用インスタンスタイプを適切な環境のサイジングを行うために参照しています。また、これらのインスタンスは EBS ボリュームの機能と最適化を提供しています。 アプリケーションの容量要件と価格モデルに基づいて、他のインスタンスを使用することもできます。

M4 インスタンスは最新世代の_汎用_インスタンスです。 このファミリーは、コンピューティング、メモリ、ネットワークリソースをバランスよく提供しているため、多くのアプリケーションに適しています。 仮想 CPU の数は 2 個から 64 個、メモリ容量は 8 GB から 256 GB を提供し、対応する専用 EBS 帯域幅が決まっています。

個々のインスタンスタイプに加えて、専用ホスト、スポットインスタンス、リザーブドインスタンス、専用インスタンスなどの階層化された分類もあり、それぞれ価格、パフォーマンス、分離の程度が異なります。

現在利用可能なインスタンスの可用性と詳細については、こちらを参照してください。

可用性と運用

Web/アプリサーバーの負荷分散

Cachéベースのアプリケーションには、外部および内部の負荷分散されたWebサーバーが必要となる場合があります。 外部のロードバランサーはインターネットまたは WAN(VPN または Direct Connect)経由でのアクセスに使用され、内部のロードバランサーは内部トラフィックに使用される可能性があります。 AWS Elastic Load Balancing は、アプリケーションロードバランサーとクラシックロードバランサーの 2 種類のロードバランサーを提供します。

クラシックロードバランサー

クラシックロードバランサーは、アプリケーションやネットワークレベルの情報に基づいてトラフィックをルーティングし、高可用性、自動スケーリング、堅牢なセキュリティが必要な複数の EC2 インスタンス間でのトラフィックを単純にロードバランシングするのに適しています。仕様の詳細と機能については、こちらを参照してください。

アプリケーションロードバランサー

アプリケーションロードバランサーは Elastic Load Balancing サービスの負荷分散オプションであり、アプリケーションレイヤーで動作し、1 つ以上の Amazon EC2 インスタンスで実行されている複数のサービスやコンテナ全体の内容に応じてルーティングのルールを定義する機能を提供します。 さらに、WebSockets および HTTP/2 に対応しています。仕様の詳細と機能については、こちらを参照してください。

次の例では最高レベルの可用性を提供するため、独立したアベイラビリティーゾーンに 3 つの Web サーバーのセットが定義されています。 Web サーバーのロードバランサーは、ユーザーセッションを Cookie を使用する特定の EC2 インスタンスに固定する機能を提供する_スティッキーセッション_を使用して構成する必要があります。 ユーザーがアプリケーションにアクセスし続けると、トラフィックは同じインスタンスにルーティングされます。 

次の図4では、AWS 内のクラシックロードバランサーの簡単な例を示しています。

図 4: クラシックロードバランサーの例

データベースミラーリング

Caché ベースのアプリケーションを AWS にデプロイする場合、Caché データベースサーバーの高可用性を確保するには、同期的なデータベースミラーリングを使用して特定のプライマリ AWS リージョンで高可用性を確保し、稼働時間サービスレベル契約の要件によっては非同期なデータベースミラーリングを使用して災害復旧用のセカンダリ AWS リージョン内のホットスタンバイにデータを複製する必要があります。

データベースミラーは、フェイルオーバーメンバーと呼ばれる2つのデータベースシステムの論理グループです。これらのメンバーはネットワークでのみ接続された物理的に独立したシステムです。 ミラーは、2 つのシステムを解決した後、自動的に片方をプライマリシステムとするため、もう片方は自動的にバックアップシステムになります。 外部クライアントワークステーションまたはほかのコンピュータは、ミラーリング構成中に指定されたミラーの仮想IP(VIP)を介してミラーに接続します。 ミラーVIPは、ミラーのプライマリシステムのインターフェイスに自動的にバインドされます。 

注意: AWS ではミラー VIP を構成することはできないため、代替ソリューションが考案されています。 ただし、サブネット間のミラーリングはサポートされています。

AWS にデータベースミラーをデプロイするための現在推奨されているのは、3 つの異なるアベイラビリティーゾーンの同じ VPC に 3 つのインスタンス(プライマリ、バックアップ、アービター)を構成することです。 こうすることで、AWS は常に 99.95% の SLA でこれらの VM のうち少なくとも 2 つが外部に接続されていることを保証します。 その結果、データベースのデータ自体の適切な分離と冗長性を提供しています。 AWS EC2 のサービスレベル契約に関する詳細については、こちらを参照してください。

フェイルオーバーメンバー間のネットワーク遅延には厳密な上限はありません。 遅延の増加による影響は、アプリケーションによって異なります。 フェイルオーバーメンバー間のラウンドトリップ時間がディスク書き込みサービス時間と同じである場合、影響はありません。 ただし、アプリケーションがデータの永続化(ジャーナル同期と呼ばれることもあります)を待つ必要がある場合はラウンドトリップ時間が問題になることがあります。 データベースミラーリングとネットワーク遅延に関する詳細については、こちらを参照してください。

### 仮想 IP アドレスと自動フェイルオーバー

ほとんどの IaaS クラウドプロバイダーには、データベースのフェイルオーバー設計で一般的に使用される仮想 IP(VIP)アドレスを提供する機能がありません。 この問題を解決するために ECP クライアントや CSP Gateway などの最も一般的に使用される接続方法が Caché、Ensemble、HealthShare 内で拡張されたため、VIP の機能を使用してこれらをミラー対応にする必要はなくなりました。 

xDBC、TCP/IP ソケットによる直接接続などの接続方法や、その他の直接接続プロトコルについては引き続き VIP を使用する必要があります。 この問題を解決するため、InterSystems のデータベースミラーリングテクノロジーは、AWS の Elastic Load Balancer(ELB)とやり取りして VIP のような機能を実現する API を使用することで、AWS 内でこれらの接続方法に対して自動フェイルオーバーを提供できるようにしています。その結果、AWS 内で完全かつ堅牢な高可用性を実現しています。 

さらに、AWS は最近になってアプリケーションロードバランサーと呼ばれる新しいタイプの ELB を導入しました。 このタイプのロードバランサーはレイヤー 7 で実行され、コンテンツベースのルーティングをサポートし、コンテナで実行されるアプリケーションをサポートしています。 コンテンツベースのルーティングは、パーティション分割されたデータやデータシャーディングを使用するビッグデータタイプのプロジェクトのデプロイで特に役に立ちます。 

仮想 IP の場合と同様に、これは唐突なネットワーク構成の変更であり、フェイルオーバーが発生していることを障害が発生したプライマリミラーメンバーに接続されている既存のクライアントに通知するアプリケーションロジックを必要としないものです。 障害の性質によっては、障害そのもの、アプリケーションのタイムアウトやエラー、新しいプライマリによる古いプライマリインスタンスの強制停止、あるいはクライアントが使用した TCP キープアライブタイマーの失効が原因でこれらの接続が終了する可能性があります。

その結果、ユーザーは再接続してログインする必要になるかもしれません。 この動作はアプリケーションの動作によって決まります。 利用可能なさまざまなタイプの ELB に関する詳細については、こちらをご覧ください。

AWS EC2 インスタンスから AWS Elastic Load Balancer メソッドの呼び出し

このモデルでは、ELB はフェイルオーバーミラーメンバーと潜在的な DR 非同期ミラーメンバーの両方が定義され、現在のプライマリミラーメンバーであるアクティブなエントリが 1 つだけのサーバープールか、アクティブなミラーメンバーのエントリを 1 つ持つサーバープールのみを持つことができます。 

図 5: Elastic Load Balancer と対話する API メソッド(内部)

ミラーメンバーがプライマリミラーメンバーになると、新しいプライマリミラーメンバーの ELB を調整/指示するために API 呼び出しが EC2 インスタンスから AWS ELB に発行されます。 

図 6: ロードバランサーの API を使用したミラーメンバー B へのフェイルオーバー

同様のモデルは、プライマリミラーメンバーとバックアップミラーメンバーの両方が利用できなくなった場合の DR 非同期ミラーメンバーの昇格にも適用されます。

図 7: ロードバランサーの API を使用した DR 非同期ミラーのプライマリへの昇格

標準の推奨される DR 手順のとおり、上記図 6 の DR メンバーの昇格では非同期複製によるデータ損失の可能性があるため、人間による判断が必要になります。 ただし、その操作を実行した後は ELB 上での管理操作は必要ありません。 昇格中に API が呼び出されると、トラフィックが自動的にルーティングされます。

API の詳細

AWS ロードバランサのリソースを呼び出すためのこの API は、具体的には次のようにプロシージャコールの一部として ^ZMIRROR ルーチンで定義されています。  

$$CheckBecomePrimaryOK^ZMIRROR()

このプロシージャの中に、AWS ELB REST API、コマンドラインインターフェイスなどから使用するために選択した API ロジックまたはメソッドを挿入します。 ELB と効果的かつ安全に対話するには、AWS Identity and Access Management(IAM)ロールを使用してください。これにより、長期間有効な認証情報を EC2 インスタンスに配布する必要がなくなります。 IAM ロールは、Caché が AWS ELB と対話するために使用できる一時的な権限を提供します。EC2 インスタンスに割り当てられた IAM ロールの使用に関する詳細は、こちらを参照してください。

AWS Elastic Load Balancer のポーリングメソッド

2017.1で利用可能な CSP Gateway の mirror_status.cxw ページを使用するポーリングメソッドは、ELB サーバープールに追加された各ミラーメンバーに対する ELB ヘルスモニターのポーリングメソッドとして使用できます。 プライマリミラーのみが「SUCCESS」に応答するため、ネットワークトラフィックはアクティブなプライマリミラーメンバーのみに送信されます。 

このメソッドでは、^ZMIRROR にロジックを追加する必要はありません。 ほとんどの負荷分散ネットワークアプライアンスには、ステータスチェックの実行頻度に制限があることに注意してください。 通常、最高頻度は一般的にほとんどの稼働時間サービスレベル契約で許容される 5 秒以上に設定されています。

次のリソースの HTTP リクエストは、ローカルキャッシュ構成のミラーメンバーのステータスをテストします。

/csp/bin/mirror_status.cxw_

それ以外の場合、ミラーステータスリクエストのパスを実際の CSP ページのリクエストに使用されるのと同じ階層構造を使用し、適切なキャッシュサーバーとネームスペースに解決する必要があります。

例: /csp/user/ パスにある構成サービスアプリケーションのミラーステータスをテストする場合は次のようになります。

/csp/user/mirror_status.cxw_

注意: ミラーライセンスチェックを実行しても、CSP ライセンスは消費されません。

ターゲットインスタンスがアクティブなプライマリメンバーであるかどうかに応じて、ゲートウェイは以下の CSP 応答のいずれかを返します。

**** 成功(プライマリメンバーである)**

===============================

   HTTP/1.1 200 OK

   Content-Type: text/plain

   Connection: close

   Content-Length: 7

   SUCCESS

**** 失敗(プライマリメンバーではない)**

===============================

   HTTP/1.1 503 Service Unavailable

   Content-Type: text/plain

   Connection: close

   Content-Length: 6

   FAILED

**** 失敗(キャッシュサーバーが Mirror_Status.cxw のリクエストをサポートしていない)**

===============================

   HTTP/1.1 500 Internal Server Error

   Content-Type: text/plain

   Connection: close

   Content-Length: 6

   FAILED

次の図は、ポーリングメソッドを使用するさまざまなシナリオを表しています。

図 8: すべてのミラーメンバーへのポーリング

上の図 8 のように、すべてのミラーメンバーは動作しており、プライマリミラーメンバーのみがロードバランサーに「SUCCESS」を返しているため、ネットワークトラフィックはこのミラーメンバーにのみ送信されます。

図 9: ポーリングを使用したミラーメンバー B へのフェイルオーバー

次の図は、DR 非同期ミラーメンバーの負荷分散プールへの昇格を表しています。これは通常、同じ負荷分散ネットワークアプライアンスがすべてのミラーメンバーにサービスを提供していることを前提としています(地理的に分割されたシナリオについては、この記事の後半で説明します)。

標準の推奨される DR 手順のとおり、DR メンバーの昇格では非同期複製によるデータ損失の可能性があるため、人間による判断が必要になります。 ただし、その操作を実行した後は ELB 上での管理操作は必要ありません。 新しいプライマリは自動的に検出されます。

図 10: ポーリングを使用した DR 非同期ミラーメンバーのフェイルオーバーと昇格

バックアップと復元

バックアップ操作には、いくつかのオプションがあります。 InterSystems 製品を使用した AWS のデプロイでは、次の 3 つのオプションを実行できます。 最初の 2 つのオプションは、スナップショットを作成する前にデータベースによるディスクへの書き込みを一時停止し、スナップショットに成功したら更新を再開するというスナップショットタイプの手順を使用しています。 いずれかのスナップショット方式を使用してクリーンなバックアップを作成するには、次のおおまかな手順を実行します。

  • データベースのFreeze API呼び出しにより、データベースへの書き込みを一時停止する。
  • OS とデータディスクのスナップショットを作成する。
  • データベースのThaw API呼び出しにより、Cachéの書き込みを再開する。
  • バックアップファシリティはバックアップ場所にアーカイブする。

クリーンで一貫したバックアップを確保するために、整合性チェックなどの追加手順を定期的に追加することができます。

どのオプションを使用するかという決定ポイントは、組織の運用要件とポリシーによって異なります。 さまざまなオプションをさらに詳しく検討するには、InterSystemsにご相談ください。

EBS スナップショット

EBS スナップショットは、高可用性で低コストの Amazon S3 ストレージにポイントインタイムスナップショットを作成するための非常に高速で効率的な方法です。 EBS スナップショットと共に InterSystems External Freeze および Thaw API の機能を使って、実質的に 24 時間 365 日の運用レジリエンシーとクリーンな定期バックアップを実現できます。 Amazon CloudWatch Events などの AWS が提供するサービスか、Cloud Ranger や N2W Software Cloud Protection Manager などの市場で入手可能なサードパーティ製ソリューションを使用してプロセスを自動化する方法は多数存在します。

また、AWS ダイレクト API を呼び出し、独自にカスタマイズしたバックアップソリューションをプログラムで作成することができます。API の活用方法の詳細については、こちらこちらをご覧ください。

注意: InterSystems はこれらのサードパーティ製品を推奨または明示的に検証していません。 テストと検証はお客様の責任で実施してください

論理ボリュームマネージャのスナップショット

別の方法として、市場に出回っている多くのサードパーティ製バックアップツールを使用する場合は、VM そのものにバックアップエージェントを展開し、Linux の論理ボリュームマネージャ(LVM)のスナップショットか Windows のボリュームシャドウコピーサービス(VSS)と組み合わせてファイルレベルのバックアップを活用することができます。

このモデルには、Linux および Windows ベースのインスタンスをファイルレベルで復元できるというメリットがあります。 このソリューションで注意すべき点は、AWS やほとんどの IaaS クラウドプロバイダはテープメディアを提供しないため、すべてのバックアップリポジトリは短期アーカイブ用のディスクベースになっており、長期保管(LTR)を行うには Amazon S3 の低コストストレージ、そして最終的には Amazon Glacier を活用できるということです。 このモデルを使用する場合は、ディスクベースのバックアップリポジトリを最も効率的に使用できるように、重複除去テクノロジーをサポートするバックアップ製品を使用することを強くお勧めします。

こういったクラウド対応のバックアップ製品には、Commvault、EMC Networker、HPE Data Protector、Veritas Netbackup などさまざまな製品があります。 

注意: InterSystems はこれらのサードパーティ製品を推奨または明示的に検証していません。 テストと検証はお客様の責任で実施してください

Caché Online Backup

小規模なデプロイでは、組み込みの Caché Online Backup ファシリティもオプションとして考えられます。 InterSystems のデータベースオンラインバックアップユーティリティは、データベース内のすべてのブロックをキャプチャしてデータベースファイルにデータをバックアップし、出力をシーケンシャルファイルに書き込みます。 この、InterSystems独自のバックアップメカニズムは、本番システムのユーザーにダウンタイムを引き起こさないように設計されています。 

AWS ではオンラインバックアップが完了した後、バックアップ出力ファイルとシステムが使用中のその他すべてのファイルをファイル共有(CIFS/NFS)として機能する EC2 にコピーする必要があります。 このプロセスは、仮想マシン内でスクリプト化して実行しなければなりません。

オンラインバックアップは、バックアップに低コストのソリューションを実装したい小規模サイト向けのエントリーレベルのアプローチですが、 データベースのサイズが大きくなるにつれ、スナップショットテクノロジーを使った外部バックアップがベストプラクティスとして推奨されます。外部ファイルのバックアップ、より高速な復元時間、エンタープライズ全体のデータビューと管理ツールなどのメリットがあります。

災害復旧

AWS に Caché ベースのアプリケーションをデプロイする場合は、ネットワーク、サーバー、ストレージなどの DR リソースを異なる AWS リージョンか、最小限の独立したアベイラビリティーゾーンに配置することをお勧めします。 指定された DR AWS リージョンに必要な容量は、組織のニーズによって異なります。 ほとんどの場合、DRモードでの運用時には本番容量の100%が必要となりますが、弾性モデルとして必要になるまでは、より少ない容量をプロビジョニングできます。 容量が少ないと Web サーバーとアプリケーションサーバーの数が減り、データベースサーバーに使用できる EC2 インスタンスタイプがさらに小さくなる可能性があります。また、昇格時には EBS ボリュームが大きな EC2インスタンスタイプに接続されます。 

非同期データベースミラーリングは、DR AWS リージョンの EC2 インスタンスに対する継続的な複製処理を実行するために使用されます。 ミラーリングは、データベースのトランザクションジャーナルを使用して、プライマリシステムのパフォーマンスへの影響を最小限に抑えながら、TCP/IPネットワーク経由で更新を複製します。 これらの DR 非同期ミラーメンバーには、ジャーナルファイルの圧縮と暗号化を構成することを強くお勧めします。

アプリケーションにアクセスする公開インターネット上のすべての外部クライアントは、追加の DNS サービスである Amazon Route53 経由でルーティングされます。 Amazon Route53 は、トラフィックを現在アクティブなデータセンターに転送するスイッチとして使用されます。Amazon Route53 は、主に次の 3 つの機能を実行します。

  • ドメイン登録 – Amazon Route53では、example.com などのドメイン名を登録できます。
  • ドメインネームシステム(DNS)サービス – Amazon Route53 は、www.example.com などのわかりやすいドメイン名を 192.0.2.1 などの IP アドレスに変換します。 Amazon Route53 は世界中のネットワークに展開された権威 DNS サーバーを使用して DNS クエリに応答し、レイテンシを削減します。
  • ヘルスチェック – Amazon Route53 はインターネット経由でアプリケーションに自動的にリクエストを送信し、到達可能であり、利用可能であり、機能していることを確認しています。

これらの機能に関する詳細は、こちらを参照してください。

このドキュメントでは、DNS フェイルオーバーと Route53 ヘルスチェックについて説明します。ヘルスチェックの監視と DNS フェイルオーバーの詳細は、こちらこちらを参照してください。

Route53 は、各エンドポイントに通常のリクエストを送信してその応答を検証することで機能します。 エンドポイントが有効なレスポンスを提供できない場合、 DNSレスポンスには含まれなくなり、代わりに利用可能な代替エンドポイントを返します。 このようにして、ユーザートラフィックは障害のあるエンドポイントから離れ、利用可能なエンドポイントに向けられます。

上記の方法を使用すると、特定のリージョンと特定のミラーメンバーへのトラフィックのみが許可されます。 これは、この記事で前述した InterSystems CSP Gateway から提示される mirror_status.cxw ページであるエンドポイント定義によって制御されます。 プライマリミラーメンバーのみが、ヘルスチェックからの「SUCCESS」を HTTP 200 として報告します。

次の図は、フェイルオーバールーティングポリシーの概要を表しています。 この方法やその他の詳細については、こちらを参照してください。

図 11: Amazon Route53 のフェイルオーバールーティングポリシー

常に、エンドポイント監視に基づいて、1つのリージョンのみがオンライン状態を報告します。 これにより、トラフィックは常に1つのリージョンにのみ流れるようになります。 エンドポイント監視は、指定されたプライマリ AWS リージョンのアプリケーションがダウン状態であり、セカンダリ AWS リージョンのアプリケーションがライブになっていることを検出するため、リージョン間のフェイルオーバーにさらに手順を追加する必要はありません。 これは、DR 非同期ミラーメンバーが手動でプライマリに昇格されると、CSP Gateway が Elastic Load Balancer のエンドポイント監視に HTTP 200 を報告できるようになるためです。

上記のソリューションの代替案は多くあり、組織の運用要件やサービスレベル契約に基づいてカスタマイズすることができます。

モニタリング

Amazon CloudWatch は、すべての AWS クラウドリソースとアプリケーションに監視サービスを提供できます。 Amazon CloudWatch を使用し、メトリックの収集と追跡、ログファイルの収集と監視、アラームの設定、AWS リソースの変更への自動対応を行うことができます。 Amazon CloudWatch は、Amazon EC2 インスタンス、アプリケーションとサービスによって生成されたカスタム指標、およびアプリケーションが生成したログファイルなどの AWS リソースを監視できます。 Amazon CloudWatch を使用し、システム全体のリソース使用率、アプリケーションパフォーマンス、運用状態を可視化できます。   詳細については、こちらを参照してください。

自動プロビジョニング

現在、Terraform、Cloud Forms、Open Stack、Amazon 独自の CloudFormation など、数多くのツールが市場に出回っています。 これらのツールを使用し、Chef / Puppet / Ansible などのその他のツールと組み合わせることで、DevOps に対応した完全な Infrastructure-as-Code を提供したり、アプリケーションの立ち上げを完全に自動化したりできます。 Amazon CloudFormation の詳細については、こちらを参照してください。

ネットワーク接続

アプリケーションの接続要件に応じて、インターネット、VPN、または Amazon Direct Connect を使用した専用リンクのいずれかを使用して複数の接続モデルを利用することができます。 選択方法は、アプリケーションとユーザーのニーズによって異なります。 これら 3 つの方法の帯域幅使用率はそれぞれ異なるため、AWS 担当者に相談するか Amazon マネジメントコンソールで特定のリージョンで使用可能な接続オプションを確認することをお勧めします。

セキュリティ

パブリック IaaS クラウドプロバイダーにアプリケーションをデプロイするかどうかを決定するには注意が必要です。 組織のセキュリティコンプライアンスを維持するには、組織の標準セキュリティポリシーまたはクラウド専用に作成された新しいポリシーに従う必要があります。 また、組織のデータが国外に保存され、データが存在する国の法律に準拠している場合には関連するデータの主権を把握しておく必要があります。 クラウドデプロイメントには、クライアントデータセンターと物理的なセキュリティ管理の外にデータが置かれるため、付加リスクが伴います。 使用中でないデータ(データベースとジャーナル)と使用中のデータ(ネットワーク通信)には、InterSystemsのデータベースとジャーナル暗号化と合わせて、前者にはAESを、後者にはSSL/TLS暗号化を使用することを強くお勧めします。

すべての暗号化キー管理と同様に、データの安全を確保し、不要なデータアクセスやセキュリティ違反を防止するには、組織のポリシーに基づいて、適切な手順を文書化し、それに従う必要があります。

Amazonは、Caché ベースのアプリケーションに非常に安全な運用環境を提供するための広範なドキュメントと事例を提供しています。こちらで参照できる Identity Access Management(IAM)に関するさまざまなディスカッションのトピックを確認してください。

アーキテクチャ図の例

下の図は、データベースミラーリング(同期フェイルオーバーと DR 非同期)、ECP を使用したアプリケーションサーバー、負荷分散された複数の Web サーバーの構成で高可用性を提供する典型的な Caché のインストール環境を表しています。

TrakCareの例

次の図は、負荷分散された複数の Web サーバー、ECP クライアントとしての 2 台のプリントサーバー、データベースミラーで構成される典型的な TrakCare のデプロイを表しています。 仮想IPアドレスは、ECPまたはCSP Gatewayに関連付けられていない接続にのみ使用されます。 ECPクライアントとCSP Gatewayはミラー対応であり、VIPを必要としません。

Direct Connect を使用している場合、災害復旧シナリオで有効にできる複数の回線やリージョンアクセスなどのオプションがあります。 電気通信事業者に相談し、事業者がサポートする高可用性と災害復旧シナリオを理解することが重要です。

下のサンプルリファレンスアーキテクチャ図には、アクティブまたはプライマリリージョンにおける高可用性と、プライマリ AWS リージョンが利用不可である場合の別の AWS リージョンへの災害復旧が含まれます。 また、この例では、データベースミラーには、1つのミラーセット内にTrakCare DB、TrakCare Analytics、およびIntegrationネームスペースが含まれています。

図 12: TrakCare AWS リファレンスアーキテクチャ図 – 物理アーキテクチャ

さらに、次の図は、関連するインストールされたエンドユーザ向けソフトウェア製品と機能的な目的で、より論理的なアーキテクチャを示しています。

図 13: TrakCare AWS リファレンスアーキテクチャ図 – 論理アーキテクチャ

HealthShare の例

次の図は、負荷分散される複数のWebサーバーと、Information Exchange、Patient Index、Personal Community、Health Insight、Health Connectといった複数のHealthShare製品による典型的なHealthSharaeデプロイメントを示しています。 それぞれの製品は、複数のアベイラビリティーゾーン内に 1 組のデータベースミラーを含めて高可用性を実現しています。 仮想IPアドレスは、ECPまたはCSP Gatewayに関連付けられていない接続にのみ使用されます。 HealthShare製品間のWebサービス通信に使用されるCSP Gatewayはミラー対応であり、VIPを必要としません。

下のサンプルリファレンスアーキテクチャ図には、アクティブまたはプライマリリージョンにおける高可用性と、プライマリリージョンが利用不可である場合の別の AWS リージョンへの災害復旧が含まれます。 

図 14: HealthShare AWS リファレンスアーキテクチャ図 – 物理アーキテクチャ

さらに、次の図は、関連するインストール済みのエンドユーザ向けのソフトウェア製品、接続要件と手法、およびそれぞれの機能的な目的で、より論理的なアーキテクチャを示しています。

図 15: HealthShare AWS リファレンスアーキテクチャ図 – 論理アーキテクチャ

0
0 1008
記事 Shintaro Kaminaka · 8月 26, 2020 23m read

作成者:Daniel Kutac(InterSystems セールスエンジニア)

パート 3. 付録

InterSystems IRIS OAUTH クラスの説明

この連載の前のパートでは、InterSystems IRIS を OAUTH クライアントおよび認可/認証サーバー(OpenID Connect を使用)として機能するように構成する方法について学びました。 この連載の最後のパートでは、InterSystems IRIS OAuth 2.0 フレームワークを実装するクラスについて説明します。 また、一部の API クラスのメソッドの使用例についても説明します。

OAuth 2.0 を実装する API クラスは、目的に応じて 3 種類のグループに分けることができます。 すべてのクラスは %SYS ネームスペースで実装されています。 これらの一部は(% package 経由で)公開されていますが、一部は非公開になっており、開発者が直接呼び出すことはできません。

内部クラス

これらのクラスは OAuth2 パッケージに属しています。

次の表に、対象となるクラスの一部を掲載しています(クラスの完全なリストについては、CachéあるいはIRIS インスタンスのオンラインクラスリファレンスを参照してください)。 以下の表に掲載されているものを除き、これらのクラスをアプリケーション開発者が直接使用することはできません。

  <td>
    説明
  </td>
</tr>

<tr style="height:0px">
  <td>
    OAuth2.AccessToken
  </td>
  
  <td>
    Persistent(永続クラス)

OAuth2.AccessToken は、OAuth 2.0 アクセストークンとその関連情報を格納します。 これは、アクセストークンの OAUTH クライアントコピーです。OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。

<tr style="height:0px">
  <td>
    OAuth2.Client
  </td>
  
  <td>
    Persistent(永続クラス)

OAuth2.Application クラスは OAuth2 クライアントを記述し、RFC 6749 に基づいてアプリケーションを認可するために使用する認可サーバーを参照します。 クライアントシステムは、さまざまなアプリケーションで複数の認可サーバーと共に使用できます。

<tr style="height:0px">
  <td>
    OAuth2.Response
  </td>
  
  <td>
    CSPページ

これは、InterSystems IRIS OAuth 2.0 クライアントコードから使用される OAuth 2.0 認可サーバーからの応答用のランディングページです。 応答はここで処理され、最終的なターゲットにリダイレクトされます。

<tr style="height:0px">
  <td>
    OAuth2.ServerDefinition
  </td>
  
  <td>
    Persistent(永続クラス)

OAUTH クライアント(この InterSystems IRIS インスタンス)が使用する認可サーバーの情報が格納されています。 認可サーバーの定義ごとに複数のクライアント構成を定義できます。

<tr style="height:0px">
  <td>
    OAuth2.Server.AccessToken
  </td>
  
  <td>
    Persistent(永続クラス)

アクセストークンは OAUTH サーバーの OAuth2.Server.AccessToken によって管理されます。 このクラスには、アクセストークンと関連プロパティが格納されます。 このクラスは、認可サーバーのさまざまな要素間の通信手段でもあります。

<tr style="height:0px">
  <td>
    OAuth2.Server.Auth
  </td>
  
  <td>
    CSP ページ

認可サーバーは、RFC 6749 で指定されている認可コードおよびインプリシットグラントタイプの認可制御フローをサポートします。 OAuth2.Server.Auth クラスは認可エンドポイントとして機能し、RFC 6749 に従ってフローを制御する %CSP.Page のサブクラスです。

<tr style="height:0px">
  <td>
    OAuth2.Server.Client
  </td>
  
  <td>
    Persistent(永続クラス)

OAuth2.Server.Configuration は、この認可サーバーに登録したクライアントを記述する永続クラスです。

<tr style="height:0px">
  <td>
    OAuth2.Server.Configuration
  </td>
  
  <td>
    Persistent(永続クラス)

認可サーバーの構成が格納されます。 すべての構成クラスには、ユーザーが構成の詳細を入力するためのシステム管理ポータルページがそれぞれ存在します。

クラス名

OAuth2.Client、OAuth2.ServerDefinition、OAuth2.Server.Client、OAuth2.Configuration の各オブジェクトは UI を使用せずに開き、変更し、作成または変更した構成を保存できます。 これらのクラスを使用し、構成をプログラムで操作できます。

サーバーカスタマイズ用クラス

これらのクラスは %OAuth2 パッケージに属しています。 このパッケージには、一連の内部クラス(ユーティリティ)が含まれています。ここでは、開発者が使用できるクラスについてのみ説明します。 これらのクラスは、OAuth 2.0 Server Configuration(サーバー構成)ページで参照されます。

  <td>
    CSPページ

%OAuth2.Server.Authenticate はデフォルトの Authenticate クラスだけでなく、ユーザーが作成したすべての Authenticate クラスのサブクラスとして機能します。 Authenticate クラスは、ユーザーを認証するために OAuth2.Server.Auth の認可エンドポイントによって使用されます。 このクラスを使用すると、認証プロセスをカスタマイズできます。次のメソッドを OAuth2.Server のデフォルトをオーバーライドするために実装できます。 DirectLogin– ログインページを表示しない場合にのみ使用します。 DisplayLogin – 認可サーバーのログインフォームを実装します。 DisplayPermissions – リクエストされたスコープのリストを使用してフォームを実装します。CSS を変更することで、外観や操作性をさらにカスタマイズできます。 CSS スタイルは DrawStyle メソッドで定義されます。loginForm は DisplayLogin フォーム用です。permissionForm は DisplayPermissions フォーム用です。

<tr style="height:0px">
  <td>
    %OAuth2.Server.Validate
  </td>
  
  <td>
    CSP ページ

これは、サーバーに含まれているデフォルトの Validate User Class(ユーザー検証クラス)です。 デフォルトのクラスは、認証サーバーが配置されている Cache またはIRISインスタンスのユーザーデータベースを使用してユーザーを検証します。 サポートされるプロパティは、issuer(Issuer)、role、sub(Username)です。Validate User Class は Authorization Server Configuration(認可サーバーの構成)で指定されます。 ユーザー名とパスワードの組み合わせを検証し、ユーザーに関連付けられたプロパティ一式を返す ValidateUser メソッドを含める必要があります。

<tr style="height:0px">
  <td>
    %OAuth2.Server.Generate
  </td>
  
  <td>
    Registered Object(登録オブジェクト)

%OAuth2.Server.Generate は、サーバーに含まれているデフォルトのGenerate Token Class(トークン生成クラス)です。 デフォルトのクラスは、ランダムな文字列を opaque アクセストークンとして生成します。Generate Token Class は、Authorization Server Configuration で指定されます。 ValidateUser メソッドによって返されるプロパティの配列に基づいてアクセストークンを生成するために使用される GenerateAccessToken メソッドを含める必要があります。

<tr style="height:0px">
  <td>
    %OAuth2.Server.JWT
  </td>
  
  <td>
    Registered Object(登録オブジェクト)

%OAuth2.Server.JWT は、サーバーに含まれている JSON Web トークンを作成する Generate Token Class です。 Generate Token Class は、Authorization Server Configuration で指定されます。 ValidateUser メソッドによって返されるプロパティの配列に基づいてアクセストークンを生成するために使用される GenerateAccessToken メソッドを含める必要があります。

<tr style="height:0px">
  <td>
    %OAuth2.Utils
  </td>
  
  <td>
    Registered Object(登録オブジェクト)

このクラスは、さまざまなエンティティのログの記録を実装します。 カスタマイズの章のサンプルコードに、可能な使用法を示しています。

%OAuth2.Server.Authenticate

次の画像に、OAuth 2.0 認可サーバー構成の対応するセクションを示します。

OpenID Connect を JWT 形式の識別情報トークン(id_token)と共に使用する場合は、構成内のデフォルトの Generate Token Class である %OAuth2.Server.Generate%OAuth2.Server.JWT に置換してください。それ以外の場合は、デフォルトの Generate クラスのままにしてください。

カスタマイズオプションについては、後で別の章で詳しく説明します。

公開 API クラス

公開 API クラスは、アプリケーション開発者が Web アプリケーションのメッセージフローに正しい値を渡し、アクセストークンの検証やイントロスペクションなどを実行するために使用されます。

これらのクラスは %SYS.OAuth2 パッケージで実装されています。 次の表に、実装されているクラスの一部を掲載しています。

  <td>
    Registered Object(登録オブジェクト)

%SYS.OAuth2.AccessToken クラスは、リソースサーバーへの認証にアクセストークンを使用できるようにするクライアント操作を定義します。基本となるトークンは、CACHESYS データベースの OAuth2.AccessToken に格納されます。 OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。

<tr style="height:0px">
  <td>
    %SYS.OAuth2.Authorization
  </td>
  
  <td>
    Registered Object(登録オブジェクト)

%SYS.OAuth2.Authorization クラスには、アクセストークンを取得してクライアントを認可するために使用される操作が含まれています。基本となるトークンは、CACHESYS データベースの OAuth2.AccessToken に格納されます。 OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。このクラスは CACHELIB にあるため、どこでも使用できることに注意してください。 ただし、トークンストレージは CACHESYS にあるため、ほとんどのコードでは直接使用できません。

<tr style="height:0px">
  <td>
    %SYS.OAuth2.Validation
  </td>
  
  <td>
    Registered Object(登録オブジェクト)

%SYS.OAuth2.Validation クラスは、アクセストークンの検証(または無効化)に使用されるメソッドを定義します。 基本となるトークンは、CACHESYS データベースの OAuth2.AccessToken に格納されます。 OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。

%SYS.OAuth2.AccessToken

このグループのいくつかのメソッドとクラスを詳しく見てみましょう。

アクセストークンを使用するすべてのクライアントアプリケーションクラスは、その有効性をチェックする必要があります。 このチェックは、OnPage メソッド(または ZEN か ZENMojo ページの対応するメソッド)のどこかで実行されます。

こちらがそのコードスニペットです。

 // OAuth2 サーバーからのアクセストークンがあるかどうかをチェックします。
 set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,"scope1,
     scope2",.accessToken,.idtoken,.responseProperties,.error)

 // アクセストークンがあるかどうかをさらにチェックします。
 // 以下は実行可能なすべてのテストであり、あらゆるケースで必要なわけではありません。
 // 各テストで返される JSON オブジェクトが単に表示されています。
 if isAuthorized {
    // 何らかの処理を実行します – リソースサーバーの API を呼び出して目的のデータを取得します。
 }

リソースサーバーの API を呼び出すたびに、アクセストークンを提供する必要があります。 この処理は、%SYS.OAuth2.AccessToken メソッドの AddAccessToken メソッドによって実行されます。こちらがそのコードスニペットです。

 set httpRequest=##class(%Net.HttpRequest).%New()
  // AddAccessToken は現在のアクセストークンをリクエストに追加します。
  set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(
    httpRequest,,
    ..#SSLCONFIG,
    ..#OAUTH2APPNAME)
 if $$$ISOK(sc) {
    set sc=httpRequest.Get(.. Service API url …)
 }

この連載の前のパートで提供したサンプルコードでは、最初のアプリケーションページ(Cache1N)の OnPreHTTP メソッドでこのコードを確認することができました。 このコードは、アプリケーションの最初のページでアクセストークンチェックを実行するのに最適な場所です。

ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
{
 set scope="openid profile scope1 scope2"
    #dim %response as %CSP.Response
 if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,
    scope,.accessToken,.idtoken,.responseProperties,.error) {
      set %response.ServerSideRedirect="Web.OAUTH2.Cache2N.cls"
 }
 quit 1
}

上記のコードにある SYS.OAuth2.AccessToken クラスの IsAuthorized メソッドは有効なアクセストークンが存在するかどうかをチェックし、存在しない場合に認可サーバーの認証フォームを指すログインボタン/リンクを使用してページの内容を表示し、存在する場合に実際にデータ取得処理を実行する 2 番目のページにリダイレクトします。

ただし、このコードは次のように変更できます。

ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
{
 set scope="openid profile scope1 scope2"
 set sc=##class(%SYS.OAuth2.Authorization).GetAccessTokenAuthorizationCode(
    ..#OAUTH2APPNAME,scope,..#OAUTH2CLIENTREDIRECTURI,.properties)
 quit +sc
}

この場合は、結果が異なります。 %SYS.OAuth2.Authorization クラスの GetAccessTokenAuthorizationCode メソッドを使用すると、アプリケーションの最初のページの内容を表示せずに、認可サーバーの認証フォームに直接移動します。

これは、Web アプリケーションがモバイルデバイスのネイティブアプリケーションから呼び出され、一部のユーザー情報がネイティブアプリケーション(ランチャー)によってすでに表示されており、認可サーバーを指すボタンを含む Web ページを表示する必要がない場合に便利です。

署名付き JWT トークンを使用する場合は、その内容を検証する必要があります。 この検証は次のメソッドで実行されます。

 set valid=##class(%SYS.OAuth2.Validation).ValidateJWT(applicationName,accessToken,scope,,.jsonObject,.securityParameters,.sc)

メソッドパラメーターの詳細については、Class Reference ドキュメントをご覧ください。

カスタマイズ

OAUTH が認証 / 承認 UI のカスタマイズ用に提供しているオプションについてもう少し説明します。

勤務先のポリシーで、スコープの付与に関してより限定的な動作が要求されているとします。 たとえば、取引先銀行内のさまざまな金融システムに接続するホームバンキングアプリケーションを実行できるとしましょう。 銀行は、取得対象の実際の銀行口座に関する情報を含むスコープへのアクセスのみを許可します。 銀行は非常に多くの口座を運営しているため、すべての口座に静的なスコープを定義することは不可能です。 代わりに、認可処理中にその場でスコープを生成する処理をカスタム認可ページのコードに組み込むことができます。

デモを行うため、ここではサーバー構成にもう 1 つのスコープを追加する必要があります。次の画像を参照してください。

また、%OAuth2.Server.Authenticate.Bank という名前のカスタム Authenticate クラスへの参照も追加しました。

では、銀行の認証クラスはどのようになるのでしょうか? 次は想定されるクラスの例です。 このクラスは、ユーザーが提供するデータを使用して標準の認証フォームと認可フォームを拡張します。 BeforeAuthenticateDisplayPermissionsAfterAuthenticate の各メソッド間を流れる情報は、%OAuth2.Server.Properties クラスの properties 変数によって渡されます。

Class %OAuth2.Server.Authenticate.Bank Extends %OAuth2.Server.Authenticate
{
/// account(口座)のスコープに CUSTOM BESTBANK のサポートを追加します。
ClassMethod BeforeAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status
{
 // 起動スコープが指定されていない場合は何もしません。
 If 'scope.IsDefined("account") Quit $$$OK
 // 起動クエリパラメーターから起動コンテキストを取得します。
 Set tContext=properties.RequestProperties.GetAt("accno")
 // コンテキストがない場合は何もしません。
 If tContext="" Quit $$$OK
    
 try {
    // ここで BestBank コンテキストを照会する必要があります。
    Set tBankAccountNumber=tContext
    // accno のスコープを追加します。 -> 動的にスコープを変更(account なし:<accno> スコープはサーバー構成に存在します)
    // この特定のスコープは、それが Cookie サポートを使用して account または account:accno によって
    // 以前に選択されていた場合に account 経由で同じ accno にアクセスできるようにするために使用されます。
    Do scope.SetAt("Access data for account "_tBankAccountNumber,"account:"_tBankAccountNumber)
    // 処理が終わった account のスコープはもう必要ありません。
    // これにより、account スコープが存在することで DisplayPermissions が強制的に呼び出されるのを防ぎます。
    Do scope.RemoveAt("account")
    
    // AfterAuthenticate が応答プロパティに変換する accno プロパティを追加します。
    Do properties.CustomProperties.SetAt(tBankAccountNumber,"account_number")
 } catch (e) {
    s ^dk("err",$i(^dk("err")))=e.DisplayString()
 }
 Quit $$$OK
}

/// account のスコープに CUSTOM BESTBANK のサポートを追加します。
/// account_number カスタムプロパティが BeforeAuthenticate(account)または
/// DisplayPermissions(account:accno)によって追加された場合は、必要な応答プロパティを追加します。
ClassMethod AfterAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status
{
 // account_number(account)または accno(account:accno)プロパティが存在しない限り、ここで実行することは何もありません。
 try {
    // カスタムログを記録する例
    If $$$SysLogLevel>=3 {
     Do ##class(%OAuth2.Utils).LogServerScope("log ScopeArray-CUSTOM BESTBANK",%token)
    }
    If properties.CustomProperties.GetAt("account_number")'="" {
     // 応答に accno クエリパラメーターを追加します。
     Do properties.ResponseProperties.SetAt(properties.CustomProperties.GetAt("account_number"),"accno")
    }
 } catch (e) {
    s ^dk("err",$i(^dk("err")))=e.DisplayString()
 }
 Quit $$$OK
}

/// BEST BANK の account のテキストを含むように変更された DisplayPermissions
ClassMethod DisplayPermissions(authorizationCode As %String, scopeArray As %ArrayOfDataTypes, currentScopeArray As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status
{
 Set uilocales = properties.RequestProperties.GetAt("ui_locales")
 Set tLang = ##class(%OAuth2.Utils).SelectLanguage(uilocales,"%OAuth2Login")
 // $$$TextHTML(Text,Domain,Language)
 Set ACCEPTHEADTITLE = $$$TextHTML("OAuth2 Permissions Page","%OAuth2Login",tLang)
 Set USER = $$$TextHTML("User:","%OAuth2Login",tLang)
 Set POLICY = $$$TextHTML("Policy","%OAuth2Login",tLang)
 Set TERM = $$$TextHTML("Terms of service","%OAuth2Login",tLang)
 Set ACCEPTCAPTION = $$$TextHTML("Accept","%OAuth2Login",tLang)
 Set CANCELCAPTION = $$$TextHTML("Cancel","%OAuth2Login",tLang)
 &html<<html>>
 Do ..DrawAcceptHead(ACCEPTHEADTITLE)
 Set divClass = "permissionForm"
 Set logo = properties.ServerProperties.GetAt("logo_uri")
 Set clientName = properties.ServerProperties.GetAt("client_name")
 Set clienturi = properties.ServerProperties.GetAt("client_uri")
 Set policyuri = properties.ServerProperties.GetAt("policy_uri")
 Set tosuri = properties.ServerProperties.GetAt("tos_uri")
 Set user = properties.GetClaimValue("preferred_username")
 If user="" {
    Set user = properties.GetClaimValue("sub")
 }
 &html<<body>>
 &html<<div id="topLabel"></div>>
 &html<<div class="#(divClass)#">>
 If user '= "" {
    &html<
     <div>
     <span id="left" class="userBox">#(USER)#<br>#(##class(%CSP.Page).EscapeHTML(user))#</span>
     >
 }
 If logo '= "" {
    Set espClientName = ##class(%CSP.Page).EscapeHTML(clientName)
   &html<<span class="logoClass"><img src="#(logo)#" alt="#(espClientName)#" title="#(espClientName)#" align="middle"></span>>
 }
 If policyuri '= "" ! (tosuri '= "") {
   &html<<span id="right" class="linkBox">>
    If policyuri '= "" {
     &html<<a href="#(policyuri)#" target="_blank">#(POLICY)#</a><br>>
    }
    If tosuri '= "" {
     &html<<a href="#(tosuri)#" target="_blank">#(TERM)#</a>>
    }
   &html<</span>>
 }
 &html<</div>>
 &html<<form>>
 Write ##class(%CSP.Page).InsertHiddenField("","AuthorizationCode",authorizationCode),!
 &html<<div>>
 If $isobject(scopeArray), scopeArray.Count() > 0 {
    Set tTitle = $$$TextHTML(" is requesting these permissions:","%OAuth2Login",tLang)
   &html<<div class="permissionTitleRequest">>
    If clienturi '= "" {
     &html<<a href="#(clienturi)#" target="_blank">#(##class(%CSP.Page).EscapeHTML(clientName))#</a>>
    } Else {
     &html<#(##class(%CSP.Page).EscapeHTML(clientName))#>
    }
   &html<#(##class(%CSP.Page).EscapeHTML(tTitle))#</div>>
    Set tCount = 0
    Set scope = ""
    For {
     Set display = scopeArray.GetNext(.scope)
     If scope = "" Quit
     Set tCount = tCount + 1
     If display = "" Set display = scope
     Write "<div class='permissionItemRequest'>"_tCount_". "_##class(%CSP.Page).EscapeHTML(display)_"</div>"
    }
 }

 If $isobject(currentScopeArray), currentScopeArray.Count() > 0 {
    Set tTitle = $$$TextHTML(" already has these permissions:","%OAuth2Login",tLang)
   &html<<div>>
   &html<<div class="permissionTitleExisting">>
    If clienturi '= "" {
     &html<<a href="#(clienturi)#" target="_blank">#(##class(%CSP.Page).EscapeHTML(clientName))#</a>>
    } Else {
     &html<#(##class(%CSP.Page).EscapeHTML(clientName))#>
    }
   &html<#(##class(%CSP.Page).EscapeHTML(tTitle))#</div>>
    Set tCount = 0
    Set scope = ""
    For {
     Set display = currentScopeArray.GetNext(.scope)
     If scope = "" Quit
     Set tCount = tCount + 1
     If display = "" Set display = scope
     Write "<div class='permissionItemExisting'>"_tCount_". "_##class(%CSP.Page).EscapeHTML(display)_"</div>"
    }
   &html<</div>>
 }

 /*********************************/
 /*  BEST BANK CUSTOMIZATION      */
 /*********************************/
 try {
    If properties.CustomProperties.GetAt("account_number")'="" {
     // Display the account number obtained from account context.
     Write "<div class='permissionItemRequest'><b>Selected account is "_properties.CustomProperties.GetAt("account_number")_"</b></div>",!

     // or, alternatively, let user add some more information at this stage (e.g. linked account number)
     //Write "<div>Account Number: <input type='text' id='accno' name='p_accno' placeholder='accno' autocomplete='off' ></div>",!
    }
 } catch (e) {
    s ^dk("err",$i(^dk("err")))=e.DisplayString()
 }

 /* original implementation code continues here... */
 &html<
   <div><input type="submit" id="btnAccept" name="Accept" value="#(ACCEPTCAPTION)#"/></div>
   <div><input type="submit" id="btnCancel" name="Cancel" value="#(CANCELCAPTION)#"/></div>
    >
 &html<</form>
 </div>>
 Do ..DrawFooter()
 &html<</body>>
 &html<<html>>
 Quit 1
}

/// CUSTOM BESTBANK の場合、入力された患者を検証する必要があります。
/// ! このメソッドの javascript はユーザーに追加データを DisplayPermissions メソッド内
/// で入力させる場合にのみ必要です !
ClassMethod DrawAcceptHead(ACCEPTHEADTITLE)
{
 &html<<head><title>#(ACCEPTHEADTITLE)#</title>>
 Do ..DrawStyle()
 &html<
 <script type="text/javascript">
 function doAccept()
 {
    var accno = document.getElementById("accno").value;
    var errors = "";
    if (accno !== null) {
     if (accno.length < 1) {
       errors = "Please enter account number name";
     }
    }
    if (errors) {
     alert(errors);
     return false;
    }
    
    // submit the form
    return true;
 }
 </script>
 >
 &html<</head>>
}

}

ご覧のとおり、%OAuth2.Server.Properties クラスにはいくつかの配列が含まれており、渡されています。 具体的には、以下の配列です。

·        RequestProperties – 認可リクエストのパラメーターが含まれています。

·        CustomProperties – 上記のコードの間でデータをやり取りするためのコンテナ。

·        ResponseProperties – トークンリクエストに対する JSON 応答オブジェクトに追加されるプロパティのコンテナ。

·        ServerProperties – 認可サーバーがカスタマイズコードに公開する共有プロパティが含まれます(logo_uri、client_uri など…)

さらに、認可サーバーが返す必要のあるクレームを指定するのに使用されるいくつかの "claims" プロパティが含まれています。

この認証ページを正しく呼び出すため、最初のクライアントページのコードを次のように変更しました。

 set scope="openid profile scope1 scope2 account"
 // このデータはアプリケーションに由来し(フォームデータなど)、リクエストのコンテキストを設定します。
 // ここでは Authenticate クラスをサブクラス化することで、このデータをユーザーに表示することができます。
 // それにより、該当ユーザーはアクセスを許可するかどうかを決めることができます。
 set properties("accno")="75-452152122-5320"
 set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(
   ..#OAUTH2APPNAME,
    scope,
   ..#OAUTH2CLIENTREDIRECTURI,
    .properties,
   .isAuthorized,
    .sc)
 if $$$ISERR(sc) {
    write "GetAuthorizationCodeEndpoint Error="
   write ..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!
 }

ご覧のとおり、ここではアプリケーションのさまざまな部分から発生する可能性のあるコンテキスト値を使用して account のスコープとプロパティ配列ノード “accno” を追加しました。 この値はアクセストークンの内部でリソースサーバーに渡され、さらに処理されます。

上記のロジックは、電子的な患者記録を交換するための FHIR 標準で実際に使用されています。

デバッグ

OAUTH フレームワークには、デバッグ機能が組み込まれています。 クライアントとサーバー間の通信はすべて暗号化されているため、これは非常に便利です。 デバッグ機能を使用すると、API クラスによって生成されたトラフィックデータをネットワーク経由で送信する前にキャプチャできます。 コードをデバッグするため、以下のコードに従って単純なルーチンまたはクラスを実装できます。 InterSystems IRIS インスタンスのすべての通信でこのコードを実装する必要があることに注意してください! その場合、OAUTH フローのプロセス内での役割を示す名前をファイル名に指定することをお勧めします。 (以下のサンプルコードは rr.mac ルーチンとして保存されていますが、どんな名前を付けるかはあなた次第です。)

 // d start^rr()
start() public {
 new $namespace
 set $namespace="%sys"
 kill ^%ISCLOG
 set ^%ISCLOG=5
 set ^%ISCLOG("Category","OAuth2")=5
 set ^%ISCLOG("Category","OAuth2Server")=5
 quit
}

 // d stop^rr()
stop() public {
 new $namespace
 set $namespace="%sys"
 set ^%ISCLOG=0
 set ^%ISCLOG("Category","OAuth2")=0
 set ^%ISCLOG("Category","OAuth2Server")=0
 quit

}

 // display^rr()
display() public {
 new $namespace
 set $namespace="%sys"
 do ##class(%OAuth2.Utils).DisplayLog("c:\temp\oauth2_auth_server.log")
 quit
}

次に、テストを開始する前にターミナルを開き、すべての InterSystems IRIS ノード(クライアント、認可サーバー、リソースサーバー)で d start^rr() を呼び出してください。 完了後、d stop^rr() d display^rr() を実行してログファイルを読み込んでください。

最後に

この連載記事では、InterSystems IRIS OAuth 2.0 の実装を使用する方法を学びました。 パート1では簡単なクライアントアプリケーションのデモを行い、パート2では複雑な例を説明しました。 最後に、OAuth 2.0 の実装で最も重要なクラスについて説明し、ユーザーアプリケーション内でそれらを呼び出す必要がある場合について説明しました。

時々私が投げかけるくだらない質問に我慢強く回答し、この連載をレビューしてくれた Marvin Tener に心から感謝の意を表します。

 

0
0 374
記事 Shintaro Kaminaka · 8月 20, 2020 23m read

作成者:Daniel Kutac(InterSystems セールスエンジニア) 注意: 使用されている URL に戸惑っている方のために。*元の連載記事では、dk-gs2016 と呼ばれるマシンの画面を使用していました。 新しいスクリーンショットは別のマシンから取得されています。 *WIN-U9J96QBJSAG という URL は dk-gs2016 であると見なしても構いません。

パート2. 認可サーバー、OpenID Connect サーバー

この短い連載の前のパートでは、OAUTH[1] クライアントとして機能する単純な使用事例について学びました。 今回は私たちの経験をまったく新しいレベルに引き上げましょう。 InterSystems IRIS がすべての OAUTH の役割を果たす、より複雑な環境を構築します。 クライアントの作成方法はすでに分かっていますので、認可サーバーだけでなく、OpenID Connect[2] プロバイダーにも注意を向けましょう。 前のパートと同様に、環境を準備する必要があります。 今回はより多くの変動要素があるため、より注意を要します。

具体例を見る前に、OpenID Connect について少し説明する必要があります。 前のパートの内容を覚えていらっしゃるかと思いますが、Google から認可してもらうため、まずは自身がGoogle で認証を受けることを求められていました。 認証は OAUTH フレームワークには含まれていません。 実際、OAUTH に依存しない多くの認証フレームワークがあります。 そのうちの1つに OpenID と呼ばれるものがあります。 当初は単独の構想で開始されましたが、最近では OAUTH フレームワークによって提供されるインフラストラクチャ、つまり通信とデータ構造が活用されています。 その結果、OpenID Connect が誕生しました。 事実、多くの人がこれを OAUTH の強化版と呼んでいます。 実際、OpenID Connect を使用すると、認可するだけでなく、OAUTH フレームワークのよく知られたインターフェースを使用して認証することもできます。

複雑な OpenID Connect のデモ

ここでは、パート 1 のクライアントコードの多くを活用します。 そうすることで多くの手間が省けるため、環境のセットアップに集中できます。

前提条件

今回は、SSL が有効になっている既存の Web サーバーに PKI インフラストラクチャを追加する必要があります。 OpenID Connect の要件である暗号化が必要です。 ユーザー認証が必要な場合は、第三者がエージェント(クライアント、認証サーバーなど)になりすますし、ネットワーク経由でユーザーの機密データを送信できないようにする必要があります。 そこで X.509 ベースの暗号化の出番です。

注意 : Cache 2017.1 以降では、X.509 証明書を使用して JWT / JWKS(JSON Web Key Set)を生成する必要はありません。 ここでは下位互換性と単純化を図るためにこのオプションを使用しています。
### PKI 厳密に言えば、Caché PKI インフラストラクチャを使用する必要はまったくありません。ただし、openssl などのツールを直接使用してすべての証明書を生成するよりは楽です。 詳細は InterSystems IRIS の[ドキュメント](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_pki#GCAS_C157792)や他の場所でも確認できますので、ここでは証明書の生成に関する詳細は触れません。 証明書を生成した結果、3 つの公開鍵/秘密鍵のペアと関連する証明書が作成されます。 これらを次のように呼びます。
  • 発行元認証局の root_ca(root_ca.cer)

  • 認可サーバーおよび OpenID サーバー用の auth(auth.cer および auth.key)

  • クライアントアプリケーションサーバー用の client(client.cer および client.key)

X.509 資格情報

デモ中に交換された JSON Web Token(JWT)に署名して検証できるよう、個々のサーバーで X.509 資格情報を定義する必要があります。

認可サーバーと認証サーバーの構成

ここでは X.509 資格情報の定義方法については詳しく説明せず、AUTHSERVER インスタンスの資格情報のスクリーンショットを示します。 画像のように、AUTHSERVER にはその秘密鍵と証明書がありますが、CLIENT に関しては公開鍵を含む証明書しかありません。

クライアントサーバーの構成

同様に、CLIENT インスタンスで資格情報が定義されています。 ここで、CLIENT には秘密鍵と証明書がありますが、AUTHSERVER に関しては公開鍵を含む証明書しかありません。

リソースサーバーの構成

このセットアップの例では、RESSERVER インスタンスで X509 資格情報を定義する必要はありません。

OAUTH の構成

この連載のパート 1 で説明した構成と同様に、サーバーを OAUTH 用に構成する必要があります。 まずは、OAUTH 構成全体の中心的なコンポーネントである AUTHSERVER インスタンスから始めましょう。

AUTHSERVER

System Management Portal で、System Administration > Security > OAuth 2.0 > Server Configuration を開きます。 メニューのリンクをクリックし、次のフォーム項目に入力します。

  • Host name(ホスト名)

  • Port(ポート、省略可)

  • Prefix(プレフィックス、省略可) – これら 3 つのフィールドは Issuer endpoint(発行者エンドポイント)を構成します。

  • 更新トークンを返す条件を指定します。

  • Supported grant types(サポートされているグラント種別)をチェックします。このデモでは 4 つの種別すべてにチェックします。 ただし、認可コードのみが使用されます。

  • 必要に応じて Audience required(オーディエンスを要求)をチェックします。これにより、aud プロパティが認可コードと暗黙的な許可(Implicit)のリクエストに追加されます。

  • 必要に応じて Support user session(ユーザーセッションをサポート)をチェックします。これにより、認可サーバーが現在このブラウザを使用しているユーザーのログイン状態を維持するために httpOnly Cookie が使用されます。 2 回目以降のアクセストークンの要求では、ユーザー名とパスワードの入力は求められません。

  • Endpoint intervals(エンドポイントの間隔)を指定します。

  • このサーバーでサポートされるスコープ(Supportted scopes)を定義します。

  • Customization Options(カスタマイズオプション)のデフォルト値を受け入れるか、カスタム値を入力します。**注意: **JWT が単なる opaque トークンではなくアクセストークンとして使用されるよう、Generate token class(生成トークンクラス)の値を %OAuth2.Server.Generate から %OAuth2.Server.JWT に変更してください。

  • OAuth 2.0 の要求に従い、HTTP 上の SSL を確立するための登録済み SSL 構成の名前を入力します。

  • JSON Web Token(JWT)の設定を入力します。

以下はサンプル構成のスクリーンショットです。 サーバー構成を定義したら、サーバークライアント構成を入力する必要があります。 サーバー構成フォームのページ内で、「Client Configurations」(クライアント構成)ボタンをクリックし、CLIENT インスタンスおよび RESSERVER インスタンスの「Create New Configuration」(新しい構成の作成)をクリックします。 (IRISを使用して構成している場合は、「クライアントディスクリプション」の構成ページから以下の設定を行います。) 以下の画像は CLIENT の構成を示しています。 JWT Token(JWT トークン)タブはデフォルト値である空のままにします。 ご覧のとおり、実際のアプリケーションの場合とは異なり、フィールドには無意味なデータが入力されています。 同様に、RESSERVER の構成を示します。 ご覧のとおり、リソースサーバーに必要なのは非常に基本的な情報だけです。具体的には、Client type(クライアント種別)をリソースサーバーに設定する必要があります。 CLIENT では、より詳細な情報とクライアント種別を入力する必要があります(クライアントはクライアントシークレットをサーバーで維持し、クライアントエージェントには送信しない Web アプリケーションとして動作するため、機密(confidential)を選択します)。

CLIENT

SMP で、System Administration > Security > OAuth 2.0 > Client Configurations を開きます。 「Create Server Configuration」(サーバー構成の作成)ボタンをクリックし、フォームに入力して保存します。 Issuer Endpoint(発行者エンドポイント)が、AUTHSERVER インスタンスで前に定義した値に対応していることを必ず確認してください! また、認可サーバーのエンドポイントを Web サーバーの構成に従って変更する必要があります。 この場合は各入力フィールドに「authserver」を埋め込んだだけです。 次に、新しく作成された発行者エンドポイントの横にある「Client Configurations」(クライアント構成)リンクをクリックし、「Create Client Configuration」(クライアント構成の作成)ボタンをクリックします。 以上です! ここまでの手順で CLIENT と AUTHSERVER の両方を構成しました。 多くの使用事例ではこれだけで十分です。リソースサーバーは単なる AUTHSERVER のネームスペースにすぎず、結果的にすでに保護されている場合があるからです。 しかし、外部の医師が内部の臨床システムからデータを取得しようとしている使用事例に対応する場合を考えてみましょう。 このような医師がデータを取得できるようにするため、この医師のアカウント情報を監査目的と法医学的な理由でリソースサーバー内に確実に保存したいと思います。 この場合は、続けて RESSERVER を定義する必要があります。

RESSERVER

SMP で、System Administration > Security > OAuth 2.0 > Client Configurations を開きます。 「Create Server Configuration」(サーバー構成の作成)ボタンをクリックし、フォームに入力して保存します。 ここでは、Cache 2017.1 に実装された新機能である検出機能を使用しました。 ご覧のように、この構成は CLIENT インスタンスの対応する構成と同じデータを使用しています。 次に、新しく作成された発行者エンドポイントの横にある「Client Configurations」(クライアント構成)リンクをクリックし、「Create Client Configuration」(クライアント構成の作成)ボタンをクリックします。 X.509 資格情報から JWT を作成することはお勧めしませんが、ここでは互換性のために使用しました。 CLIENTとほとんど同じ手順でしたが、必要なものでした。 しかし、これで先に進んでコーディングできるようになりました!

クライアントアプリケーション

話をできる限り簡単にするため、パート1で説明した Google の例から多くのコードをリサイクルします。 クライアントアプリケーションは /csp/myclient で実行されるわずか 2 つの CSP ページからなるアプリケーションです。セキュリティは強制せず、未認証ユーザーとして実行されます。

ページ 1

Class Web.OAUTH2.Cache1N Extends %CSP.Page
{

Parameter OAUTH2CLIENTREDIRECTURI = "https://dk-gs2016/client/csp/myclient/Web.OAUTH2.Cache2N.cls";

Parameter OAUTH2APPNAME = "demo client";

ClassMethod OnPage() As %Status
{
  &html<<html>

<body>
  <h1>Cache&acute; OAuth2 プロバイダーに対する認証と認可</h1>
  <p>このページのデモでは、OAuth2 の認可を使用して Cache&acute; API 関数を呼び出す方法を示しています。
  <p>ここでは Cache&acute; の認証および認可サーバーを呼び出し、アプリケーションに別の Cache&acute; サーバーに保存されているデータへのアクセスを許可します。
 >

  // 適切なリダイレクトとスコープを持つ認証エンドポイントの URL を取得します。
  // 返された URL は下のボタンで使用されます。

  // DK: 'dankut' アカウントを使用して認証します!
  set scope="openid profile scope1 scope2"
  set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(
    ..#OAUTH2APPNAME,
    scope,
    ..#OAUTH2CLIENTREDIRECTURI,
    .properties,
    .isAuthorized,
    .sc)
  if $$$ISERR(sc) {
    write "GetAuthorizationCodeEndpoint Error="
    write ..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!
  } 

  &html<
  <div class="portalLogoBox"><a class="portalLogo" href="#(url)#">Authorize for <b>ISC</b></a></div>
  </body></html>>
  Quit $$$OK
}

ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
{
  #dim %response as %CSP.Response
  set scope="openid profile scope1 scope2"
  if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error) {
    set %response.ServerSideRedirect="Web.OAUTH2.Cache2N.cls"
  }
  quit 1
}

}

ページ 2

Class Web.OAUTH2.Cache2N Extends %CSP.Page
{

Parameter OAUTH2APPNAME = "demo client";

Parameter OAUTH2ROOT = "https://dk-gs2016/resserver";

Parameter SSLCONFIG = "SSL4CLIENT";

ClassMethod OnPage() As %Status
{
    &html<<html>
  
<body>>
    
    // OAuth2 サーバーからのアクセストークンがあるかどうかをチェックします。
    set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,"scope1 scope2",.accessToken,.idtoken,.responseProperties,.error)
    
    // アクセストークンがあるかどうかをさらにチェックします。
    // 以下は実行可能なすべてのテストであり、あらゆるケースで必要なわけではありません。
    // 各テストで返される JSON オブジェクトが単に表示されています。
    if isAuthorized {
        write "<h3>Authorized!</h3>",!
        
        
        // JWT の場合、検証してからアクセストークンから詳細を取得します。
        set valid=##class(%SYS.OAuth2.Validation).ValidateJWT(..#OAUTH2APPNAME,accessToken,"scope1 scope2",,.jsonObject,.securityParameters,.sc)
        if $$$ISOK(sc) {
            if valid {
                write "Valid JWT"_"<br>",!    
            } else {
                write "Invalid JWT"_"<br>",!    
            }
            write "Access token="
            do jsonObject.%ToJSON()
            write "<br>",!
        } else {
            write "JWT Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
        }
        write "<br>",!

        // イントロスペクションエンドポイントを呼び出して結果を表示します。RFC 7662 を参照してください。
        set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection(..#OAUTH2APPNAME,accessToken,.jsonObject)
        if $$$ISOK(sc) {
            write "Introspection="
            do jsonObject.%ToJSON()
            write "<br>",!
        } else {
            write "Introspection Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
        }
        write "<br>",!
        
        if idtoken'="" {
            // ID トークンの検証と表示。OpenID Connect Core の仕様を参照してください。
            set valid=##class(%SYS.OAuth2.Validation).ValidateIDToken(
                ..#OAUTH2APPNAME,
                idtoken,
                accessToken,,,
                .jsonObject,
                .securityParameters,
                .sc)
            if $$$ISOK(sc) {
                if valid {
                    write "Valid IDToken"_"<br>",!    
                } else {
                    write "Invalid IDToken"_"<br>",!    
                }
                write "IDToken="
                do jsonObject.%ToJSON()
                write "<br>",!
            } else {
                write "IDToken Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
            }
        } else {
            write "No IDToken returned"_"<br>",!
        }
        write "<br>",!
    
        // アプリケーションロジックには不要ですが、委任認証に渡すことができるユーザーに関する情報を提供します。
    
        // Userinfo エンドポイントを呼び出して結果を表示します。OpenID Connect Core の仕様を参照してください。
        set sc=##class(%SYS.OAuth2.AccessToken).GetUserinfo(
            ..#OAUTH2APPNAME,
            accessToken,,
            .jsonObject)
        if $$$ISOK(sc) {
            write "Userinfo="
            do jsonObject.%ToJSON()
            write "<br>",!
        } else {
            write "Userinfo Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
        }
        write "<p>",!

        /***************************************************
        *                                                  *
        *   リソースサーバーを呼び出し、結果を表示します。   *
        *                                                  *
        ***************************************************/
                
        // オプション 1 - リソースサーバー - 定義によれば認可サーバーからのデータを信頼します。
        //     そのため、リソースサーバーに渡されたアクセストークンが有効である限り
        //  要求元を問わずデータを提供します。
        
        // オプション 2 - または委任認証(OpenID Connect)を使用して 
        //  (委任認証を保護して)別の CSP アプリケーションを呼び出すこともできます。
        //  - これはまさにこのデモで実施する内容です。
        
        
        write "<h4>リソースサーバーの呼び出し(委任認証)","</h4>",!
        set httpRequest=##class(%Net.HttpRequest).%New()
        // AddAccessToken は現在のアクセストークンをリクエストに追加します。
        set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(
            httpRequest,,
            ..#SSLCONFIG,
            ..#OAUTH2APPNAME)
        if $$$ISOK(sc) {
            set sc=httpRequest.Get(..#OAUTH2ROOT_"/csp/portfolio/oauth2test.demoResource.cls")
        }
        if $$$ISOK(sc) {
            set body=httpRequest.HttpResponse.Data
            if $isobject(body) {
                do body.Rewind()
                set body=body.Read()
            }
            write body,"<br>",!
        }
        if $$$ISERR(sc) {
            write "Resource Server Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
        }
        write "<br>",!
    
        write "<h4>Call resource server - no auth, just token validity check","</h4>",!
        set httpRequest=##class(%Net.HttpRequest).%New()
        // AddAccessTokenは現在のアクセストークンをリクエストに追加します。
        set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(
            httpRequest,,
            ..#SSLCONFIG,
            ..#OAUTH2APPNAME)
        if $$$ISOK(sc) {
            set sc=httpRequest.Get(..#OAUTH2ROOT_"/csp/portfolio2/oauth2test.demoResource.cls")
        }
        if $$$ISOK(sc) {
            set body=httpRequest.HttpResponse.Data
            if $isobject(body) {
                do body.Rewind()
                set body=body.Read()
            }
            write body,"<br>",!
        }
        if $$$ISERR(sc) {
            write "Resource Server Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
        }
        write "<br>",!
    } else {
        write "Not Authorized!<p>",!
        write "<a href='Web.OAUTH2.Cache1N.cls'>Authorize me</a>"
    }    
    &html<</body></html>>
    Quit $$$OK
}

}

次のスクリーンショットで処理を説明します。

AUTHSERVER インスタンスの認可 / OpenID Connect 認証サーバーのログインページ

AUTHSERVER のユーザー同意ページ

その後、最終的に結果ページが表示されます。

ご覧のとおり、実際にコードを読んでもパート 1 で示したクライアントのコードとほとんど違いはありません。 ページ 2 には違いがあります。 これはデバッグ情報であり、JWT の有効性をチェックしています。 返ってきた JWT を検証した後、AUTHSERVER からのユーザー識別情報に関するデータに対してイントロスペクションを実行できました。 ここではこの情報をページに出力しただけですが、それ以上のこともできます。 上記で説明した外部の医師の使用事例と同様に、必要に応じて識別情報を使用し、それを認証目的でリソースサーバーに渡すことができます。 または、この情報をパラメーターとしてリソースサーバーへの API 呼び出しに渡すこともできます。

次の段落では、ユーザー識別情報の使用方法について詳しく説明します。

リソースアプリケーション

リソースサーバーは認可 / 認証サーバーと同じサーバーにすることができ、多くの場合はそうなります。 しかし、このデモでは 2 つのサーバーを独立した InterSystems IRIS インスタンスにしました。

したがって、リソースサーバーでセキュリティコンテキストを使用する方法には 2 つあります。

方法 1 – 認証なし

これは最も単純なケースです。 認可 / 認証サーバーはまったく同じ Caché インスタンスです。 この場合、単一の目的のために特別に作成された csp アプリケーションにアクセストークンを渡すだけで、OAUTH を使用するクライアントアプリケーションにデータを提供し、データを要求することを認可できます。

リソース csp アプリケーションの構成(ここでは /csp/portfolio2 と呼びました)は、以下のスクリーンショットのようになります。

最小限のセキュリティをアプリケーション定義に組み込み、特定の CSP ページのみを実行できるようにします。

または、リソースサーバーは従来の Web ページの代わりに REST API を提供できます。 実際のシナリオでは、セキュリティコンテキストを微調整するのはユーザー次第です。

ソースコードの例:

Class oauth2test.demoResource Extends %CSP.Page
{

ClassMethod OnPage() As %Status
{
    set accessToken=##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.sc)
    if $$$ISOK(sc) {
        set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection("RESSERVER resource",accessToken,.jsonObject)
        if $$$ISOK(sc) {        
            // 必要に応じて jsonObject のフィールドを検証します

            w "<p><h3>Hello from Cach&eacute; server: <i>/csp/portfolio2</i> application!</h3>"
            w "<p>running code as <b>$username = "_$username_"</b> with following <b>$roles = "_$roles_"</b> at node <b>"_$p($zu(86),"*",2)_"</b>."
        }
    } else {
        w "<h3>NOT AUTHORIZED!</h3>"    
        w "<pre>"
        w
        i $d(%objlasterror) d $system.OBJ.DisplayError()
        w "</pre>"
    }
    Quit $$$OK
}

}

方法 2 – 委任認証

これはもう 1 つの極端なケースです。ユーザーがリソースサーバーの内部ユーザーと同等のセキュリティコンテキストで作業しているかのように、リソースサーバーでユーザーの識別情報を可能な限り最大限に活用したいと考えています。

解決方法の 1 つは、委任認証を使用することです。

この方法を実行するには、さらにいくつかの手順を実行してリソースサーバーを構成する必要があります。

·        委任認証を有効にする

·        ZAUTHENTICATE ルーチンを提供する

·        Web アプリケーションを構成する(この場合、/csp/portfolio で呼び出しました)

ここではユーザー識別情報とそのスコープ(セキュリティプロファイル)を提供した AUTHSERVER を信頼しているため、ZAUTHENTICATE ルーチンの実装は非常に単純で簡単です。そのため、ここではいかなるユーザー名も受け入れ、それをスコープと共にリソースサーバーのユーザーデータベースに渡しています(OAUTH スコープと InterSystems IRIS のロール間で必要な変換を行ったうえで)。 それだけです。 残りの処理は InterSystems IRIS によってシームレスに行われます。

これは ZAUTHENTICATE ルーチンの例です。

#include %occErrors
#include %occInclude

ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC
{
    set tRes=$SYSTEM.Status.OK()
    try {        
        set Properties("FullName")="OAuth account "_Username
        //set Properties("Roles")=Credentials("scope")
        set Properties("Username")=Username
        //set Properties("Password")=Password
        // Credentials 配列を GetCredentials() メソッドから渡せないため、一時的に書き換えます。
        set Properties("Password")="xxx"    // OAuth2 アカウントのパスワードは気にしません。
        set Properties("Roles")=Password
    } catch (ex) {
        set tRes=$SYSTEM.Status.Error($$$AccessDenied)
    }
    quit tRes
}

GetCredentials(ServiceName,Namespace,Username,Password,Credentials) Public 
{
    s ts=$zts
    set tRes=$SYSTEM.Status.Error($$$AccessDenied)        

     try {
         If ServiceName="%Service_CSP" {
            set accessToken=##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.sc)
            if $$$ISOK(sc) {
                set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection("RESSERVER resource",accessToken,.jsonObject)
                if $$$ISOK(sc) {
                    // ToDo: 標準アカウントと委任されたアカウント(OpenID)が競合する可能性があるため、注意してください!
                    set Username=jsonObject.username
                    set Credentials("scope")=$p(jsonObject.scope,"openid profile ",2)
                    set Credentials("namespace")=Namespace
                    // temporary hack
                    //set Password="xxx"
                    set Password=$tr(Credentials("scope")," ",",")
                    set tRes=$SYSTEM.Status.OK()
                } else {
                    set tRes=$SYSTEM.Status.Error($$$GetCredentialsFailed) 
                }
            }    
        } else {
            set tRes=$SYSTEM.Status.Error($$$AccessDenied)        
        }
     } catch (ex) {
         set tRes=$SYSTEM.Status.Error($$$GetCredentialsFailed)
    }
    Quit tRes
}

CSP ページ自体は非常にシンプルになります。

Class oauth2test.demoResource Extends %CSP.Page
{

ClassMethod OnPage() As %Status
{
    // アクセストークン認証は委任認証によって実行されます!
    // もう一度ここで行う必要はありません。

    // これはリクエストからアクセストークンを取得し、イントロスペクションエンドポイントを
    // 使用してアクセストークンの有効性を確認するダミーのリソースサーバーです。
    // 通常、応答はセキュリティに関連しませんが、リクエストパラメーターに基づく
    // 興味深いデータが含まれている可能性があります。
    w "<p><h3>Hello from Cach&eacute; server: <i>/csp/portfolio</i> application!</h3>"
    w "<p>running code as <b>$username = "_$username_"</b> with following <b>$roles = "_$roles_"</b> at node <b>"_$p($zu(86),"*",2)_"</b>."
    Quit $$$OK
}

}

そして最後に、/csp/portfolio の Web アプリケーション構成を示します。

あなたが本当に心配であったなら、最初のバリエーションで行ったように _Permitted クラス_を設定できたかもしれません。 または、REST API を使用していたかもしれません。 しかし、これらの設定に関してはこの記事の中では説明しません。

次回は、InterSystems IRIS OAUTH フレームワークによって導入される個々のクラスについて説明します。 それらの API、およびそれらを呼び出すタイミングと場所について説明します。

 

[1] この記事で OAUTH について言及する場合、常に RFC 6749(https://tools.ietf.org/html/rfc6749)で規定されている OAuth 2.0 を指しています。 ここでは簡略化のために短縮形の OAUTH を使用しています。

[2] OpenID Connect は OpenID Foundation(http://openid.net/connect)によって管理されています。

0
0 446
記事 Toshihiko Minamoto · 8月 17, 2020 25m read

この記事では、スナップショットを使用したソリューションとの統合の例を使って、_外部バックアップ_による Caché のバックアップ方法を紹介します。 このところ私が目にするソリューションの大半は、Linux の VMware にデプロイされているため、この記事の大半では、例として、ソリューションが VMware スナップショットテクノロジーをどのように統合しているかを説明しています。

Caché バックアップ - すぐ使えますか?

Caché をインストールすると、Caché データベースを中断せずにバックアップできる Caché オンラインバックアップが含まれています。 しかし、システムがスケールアップするにつれ、より効率的なバックアップソリューションを検討する必要があります。 Caché データベースを含み、システムをバックアップするには、スナップショットテクノロジーに統合された_外部バックアップ_をお勧めします。

外部バックアップに関して特別な考慮事項はありますか?

詳しい内容は外部バックアップのオンラインドキュメンテーションに説明されていますが、 主な考慮事項は次のとおりです。

「スナップショットの整合性を確保するために、スナップショットが作成される間、Caché はデータベースへの書き込みをfreezeする方法を提供しています。 スナップショットの作成中は、データベースファイルへの物理的な書き込みのみがfreezeされるため、ユーザーは中断されることなくメモリ内で更新を実行し続けることができます。」

また、仮想化されたシステム上でのスナップショットプロセスの一部によって、バックアップされている VM にスタンタイムと呼ばれる短い無応答状態が発生することにも注意してください。 通常は 1 秒未満であるため、ユーザーが気付いたり、システムの動作に影響を与えたりすることはありませんが、状況によってはこの無応答状態が長引くことがあります。 無応答状態が Caché データベースミラーリングの QoS タイムアウトより長引く場合、バックアップノードはプライマリに障害が発生したとみなし、フェイルオーバーします。 この記事の後の方で、ミラーリング QoS タイムアウトを変更する必要がある場合に、スタンタイムをどのように確認できるかについて説明します。


[InterSystems データプラットフォームとパフォーマンスに関する他の連載記事のリストはこちらにあります。](https://jp.community.intersystems.com/node/477596/)

この記事では、Caché オンラインドキュメンテーション『Backup and Restore Guide(バックアップと復元ガイド)』も確認する必要があります。


バックアップの選択肢

最小限のバックアップソリューション - Caché Online Backup

ほかのソリューションがない場合は、ダウンタイム無しでバックアップを行えるこのソリューションが InterSystems データプラットフォームに同梱されています。 _Caché オンラインバックアップ_は、Caché データベースファイルのみをバックアップするもので、データに割り当てられたデータベースのすべてのブロックをキャプチャし、出力をシーケンシャルファイルに書き込みます。 Caché Online Backup は累積バックアップと増分バックアップをサポートしています。

VMware の文脈では、Caché Online Backup はゲスト内で発生するバックアップソリューションです。 ほかのゲスト内ソリューションと同様に、Caché Online Backup の動作は、アプリケーションが仮想化されていようが、ホストで直接実行していようが、基本的に変わりません。 Caché Online Backup は、システムバックアップと連携しながら Caché のオンラインバックアップ出力ファイルを、アプリケーションが使用しているその他すべてのファイルシステムとともに、バックアップメディアにコピーします。 最小限のシステムバックアップには、インストールディレクトリ、ジャーナルおよび代替ジャーナルディレクトリ、アプリケーションファイル、およびアプリケーションが使用する外部ファイルを含むすべてのディレクトリを含める必要があります。

Caché Online Backup は、Caché データベースやアドホックバックアップのみをバックアップする低コストのソリューションを実装したいと考えている小規模サイト向けのエントリレベルのアプローチとして捉えてください。ミラーリングをセットアップする際のバックアップなどに役立てられます。 しかし、データベースのサイズが増加したり、Caché が顧客のデータランドスケープの一部でしかない場合は、_外部バックアップ_にスナップショットテクノロジーとサードパーティ製ユーティリティを合わせたソリューションがベストプラクティスとして推奨されます。非データベースファイルのバックアップ、より高速な復旧時間、エンタープライズ全体のデータビュー、より優れたカタログと管理用ツールなど、さまざまなメリットが得られます。


推奨されるバックアップソリューション - 外部バックアップ

VMware を例として使用します。VMware で仮想化すると、VM 全体を保護するための追加機能と選択肢が追加されます。 ソリューションの仮想化が完了した時点で、オペレーティングシステム、アプリケーション、データを含むシステムを .vmdk(とその他の)ファイル内に効果的にカプセル化したことになります。 これらのファイルの管理は非常に簡単で、必要となった場合にはシステム全体の復旧に使用できるため、オペレーティングシステム、ドライバ、サードパーティ製アプリケーション、データベースとデータベースファイルなどのコンポーネントを個別に復旧して構成する必要のある物理システムで同じような状況が発生した場合とは大きく異なります。


# VMware のスナップショット

VMware の vSphere Data Protection(VDP)や、Veeam や Commvault などのサードパーティ製バックアップソリューションでは、VMware 仮想マシンスナップショットを使用してバックアップを作成しています。 VMware スナップショットの概要を説明しますが、詳細については、VMware ドキュメンテーションを参照してください。

スナップショットは VM 全体に適用されるものであり、オペレーティングシステムとすべてのアプリケーションまたはデータベースエンジンはスナップショットが発生していることを認識しないということに注意してください。 また、次のことにも注意してください。

VMware スナップショット自体はバックアップではありません!

スナップショットは、バックアップソフトウェアによるバックアップの作成を_可能にします_が、スナップショット自体はバックアップではありません。

VDP とサードパーティ製バックアップソリューションは、バックアップアプリケーションとともに VMware スナップショットプロセスを使用して、スナップショットの作成と、非常に重要となるその削除を管理します。 おおまかに見ると、VMware スナップショットを使用した外部バックアップのイベントのプロセスと順序は次のとおりです。

  • サードパーティ製バックアップソフトウェアは ESXi ホストに対し、VMware スナップショットをトリガーするように要求します。
  • VM の .vmdk ファイルは読み取り専用状態となり、各 VM の .vmdk ファイルに子 vmdk 差分ファイルが作成されます。
  • 書き込み時コピーを使用して、VM へのすべての変更が差分ファイルに書き込まれます。 すべての読み取りは、最初に差分ファイルから行われます。
  • バックアップソフトウェアは、読み取り専用の親 .vmdk ファイルからバックアップターゲットへのコピーを管理します。
  • バックアップが完了すると、スナップショットがコミットされます(VM ディスクは、親に書き込まれた差分ファイルのブロックの書き込みと更新を再開します)。
  • VMware スナップショットが削除されます。

バックアップソリューションは、変更ブロック追跡(CBT)などのほかの機能も使用し、増分または累積バックアップを可能にして速度と効率性(容量節約において特に重要)を実現します。また、一般的に、データの重複解除や圧縮、スケジューリング、整合性チェックなどで IP アドレスが更新された VM のマウント、VM とファイルレベルの完全復元、およびカタログ管理などの重要な機能性も追加します。

適切に管理されていない、または長期間実行されたままの VMware スナップショットは、ストレージを過剰に消費し(データの変更量が増えるにつれ、差分ファイルが増加し続けるため)、VM の速度を低下させてしまう可能性があります。

本番インスタンスで手動スナップショットを実行する前に、十分に検討する必要があります。 なぜ実行しようとしているのか、 スナップショットが作成された時間に戻すとどうなるか、 作成とロールバック間のすべてのアプリケーショントランザクションはどうなるか、といったことを検討してください。

バックアップソフトウェアがスナップショットを作成し、削除しても問題ありません。 スナップショットの存続期間は、短期間であるものなのです。 また、バックアップ戦略においては、ユーザーやパフォーマンスへの影響をさらに最小限に抑えられるように、システムの使用率が低い時間を選ぶことも重要です。

スナップショットに関する Caché データベースの考慮事項

スナップショットを作成する前に、保留中のすべての書き込みがコミットされ、データベースが一貫した状態になるように データベースを静止させる必要があります。 Caché は、スナップショットが作成される間、データベースへの書き込みをコミットして短時間フリーズ(停止)するメソッドと API を提供しています。 こうすることで、スナップショットの作成中は、データベースファイルへの物理的な書き込みのみがフリーズされるため、ユーザーは中断されることなくメモリ内で更新を実行し続けることができます。 スナップショットがトリガーされると、データベースの書き込みが再開し、バックアップはバックアップメディアへのデータのコピーを続行します。 フリーズからの復旧は、非常に短時間(数秒)で発生します。

書き込みを一時停止するのに加え、Caché Freeze は、ジャーナルファイルの切り替えとジャーナルへのバックアップマーカーの書き込みも処理します。 ジャーナルファイルは、物理データベースの書き込みがフリーズしている間、通常通りに書き込みを続けます。 物理データベースの書き込みがフリーズしている間にシステムがクラッシュした場合、データは、起動時に、通常通りジャーナルから復元されます。

以下の図は、整合性のあるデータベースイメージを使用してバックアップを作成するための、VMware スナップショットを使用した Freeze と Thaw の手順を示しています。


![](https://community.intersystems.com/sites/default/files/inline/images/snapshot-timeline.png "スナップショットのタイムライン")

Freeze から Thaw の時間が短いところに注目してください。読み取り専用の親をバックアップターゲットにコピーする時間でなく、スナップショットを作成する時間のみです。


Caché の Freeze と Thaw の統合

vSphere では、スナップショット作成のいずれか側で自動的にスクリプトを呼び出すことができ、この時に、Caché Freeze と Thaw が呼び出されます。 注意: この機能が正しく機能するために、ESXi ホストは _VMware ツール_を介してゲストオペレーティングシステムにディスクを静止するように要求します。

VMware ツールは、ゲストオペレーティングシステムにインストールされている必要があります。

スクリプトは、厳密な命名規則と場所のルールに準じる必要があります。 ファイルの権限も設定されていなければなりません。 以下は、Linux の VMware の場合のスクリプト名です。

# /usr/sbin/pre-freeze-script
# /usr/sbin/post-thaw-script

以下は、InterSystems のチームが内部テストラボインスタンスに対して、Veeam バックアップで使用する Freeze と Thaw のスクリプト例ですが、ほかのソリューションでも動作するはずです。 これらの例は、vSphere 6 と Red Hat 7 で検証され、使用されています。

これらのスクリプトは例として使用でき、方法を示すものではありますが、各自の環境で必ず検証してください!

Freeze 前スクリプトの例:

#!/bin/sh
#
# Script called by VMWare immediately prior to snapshot for backup.
# Tested on Red Hat 7.2
#

LOGDIR=/var/log
SNAPLOG=$LOGDIR/snapshot.log

echo >> $SNAPLOG
echo "`date`: Pre freeze script started" >> $SNAPLOG
exit_code=0

# Only for running instances
for INST in `ccontrol qall 2>/dev/null | tail -n +3 | grep '^up' | cut -c5-  | awk '{print $1}'`; do

    echo "`date`: Attempting to freeze $INST" >> $SNAPLOG

    # Detailed instances specific log    
    LOGFILE=$LOGDIR/$INST-pre_post.log

    # Freeze
    csession $INST -U '%SYS' "##Class(Backup.General).ExternalFreeze(\"$LOGFILE\",,,,,,1800)" >> $SNAPLOG $
    status=$?

    case $status in
        5) echo "`date`:   $INST IS FROZEN" >> $SNAPLOG
           ;;
        3) echo "`date`:   $INST FREEZE FAILED" >> $SNAPLOG
           logger -p user.err "freeze of $INST failed"
           exit_code=1
           ;;
        *) echo "`date`:   ERROR: Unknown status code: $status" >> $SNAPLOG
           logger -p user.err "ERROR when freezing $INST"
           exit_code=1
           ;;
    esac
    echo "`date`:   Completed freeze of $INST" >> $SNAPLOG
done

echo "`date`: Pre freeze script finished" >> $SNAPLOG
exit $exit_code

Thaw スクリプトの例:

#!/bin/sh
#
# Script called by VMWare immediately after backup snapshot has been created
# Tested on Red Hat 7.2
#

LOGDIR=/var/log
SNAPLOG=$LOGDIR/snapshot.log

echo >> $SNAPLOG
echo "`date`: Post thaw script started" >> $SNAPLOG
exit_code=0

if [ -d "$LOGDIR" ]; then

    # Only for running instances    
    for INST in `ccontrol qall 2>/dev/null | tail -n +3 | grep '^up' | cut -c5-  | awk '{print $1}'`; do

        echo "`date`: Attempting to thaw $INST" >> $SNAPLOG

        # Detailed instances specific log
        LOGFILE=$LOGDIR/$INST-pre_post.log

        # Thaw
        csession $INST -U%SYS "##Class(Backup.General).ExternalThaw(\"$LOGFILE\")" >> $SNAPLOG 2>&1
        status=$?

        case $status in
            5) echo "`date`:   $INST IS THAWED" >> $SNAPLOG
               csession $INST -U%SYS "##Class(Backup.General).ExternalSetHistory(\"$LOGFILE\")" >> $SNAPLOG$
               ;;
            3) echo "`date`:   $INST THAW FAILED" >> $SNAPLOG
               logger -p user.err "thaw of $INST failed"
               exit_code=1
               ;;
            *) echo "`date`:   ERROR: Unknown status code: $status" >> $SNAPLOG
               logger -p user.err "ERROR when thawing $INST"
               exit_code=1
               ;;
        esac
        echo "`date`:   Completed thaw of $INST" >> $SNAPLOG
    done
fi

echo "`date`: Post thaw script finished" >> $SNAPLOG
exit $exit_code

権限の設定を必ず行ってください:

# sudo chown root.root /usr/sbin/pre-freeze-script /usr/sbin/post-thaw-script
# sudo chmod 0700 /usr/sbin/pre-freeze-script /usr/sbin/post-thaw-script

Freeze と Thaw のテスト

スクリプトが正しく動作することをテストするには、VM でスナップショットを手動で実行し、スクリプトの出力を確認することができます。 以下のスクリーンショットは、[Take VM Snapshot(VM スナップショットの作成)]ダイアログとオプションを示します。

選択解除- [Snapshot the virtual machine's memory(仮想マシンのメモリをスナップショットする)]

選択 - [Quiesce guest file system (Needs VMware Tools installed)(ゲストファイルシステムを静止する: VMware ツールのインストールが必要)]チェックボックス。スナップショットを作成する際にゲストオペレーティングシステムで実行中のプロセスを一時停止し、ファイルシステムのコンテンツが既知の整合性のある状態を保つようにします。

重要! テストの後、スナップショットを必ず削除してください!!!!

スナップショットが作成される際に、静止フラグが真であり、仮想マシンがオンである場合、VMware ツールによって仮想マシンのファイルシステムが静止されます。 ファイルシステムの静止は、ディスク上のデータをバックアップに適した状態にするプロセスです。 このプロセスには、オペレーティングシステムのメモリ内キャッシュからディスクへのダーティバッファのフラッシュといった操作が含まれる場合があります。

以下の出力は、操作の一環としてスナップショットを含むバックアップを実行した後に、上記の Freeze/Thaw スクリプトの例で設定された $SNAPSHOT ログファイルのコンテンツを示しています。

Wed Jan  4 16:30:35 EST 2017: Pre freeze script started
Wed Jan  4 16:30:35 EST 2017: Attempting to freeze H20152
Wed Jan  4 16:30:36 EST 2017:   H20152 IS FROZEN
Wed Jan  4 16:30:36 EST 2017:   Completed freeze of H20152
Wed Jan  4 16:30:36 EST 2017: Pre freeze script finished

Wed Jan  4 16:30:41 EST 2017: Post thaw script started
Wed Jan  4 16:30:41 EST 2017: Attempting to thaw H20152
Wed Jan  4 16:30:42 EST 2017:   H20152 IS THAWED
Wed Jan  4 16:30:42 EST 2017:   Completed thaw of H20152
Wed Jan  4 16:30:42 EST 2017: Post thaw script finished

この例では、Freeze から Thaw までに 6 秒経過していることがわかります(16:30:36~16:30:42)。 この間、ユーザー操作は中断されていません。 _独自のシステムからメトリックを収集する必要があります_が、背景としては、この例は、IO ボトルネックがなく、平均 200 万 Glorefs/秒、17 万 Gloupds/秒、および毎秒あたり平均 1,100 個の物理読み取りと書き込みデーモン 1 サイクル当たりの書き込み 3,000 個の BM でアプリケーションベンチマークを実行しているシステムから得たものです。

メモリはスナップショットの一部ではないため、再起動すると、VM は再起動して復元されることに注意してください。 データベースファイルは整合性のある状態です。 バックアップを「再開」するのではなく、ファイルを既知の時点の状態にする必要があります。 その後で、ファイルが復元されたら、ジャーナルと、アプリケーションとトランザクションの整合性に必要なその他の復元手順をロールフォワードできます。

その他のデータ保護を追加するには、ジャーナルの切り替えを単独で実行することもでき、ジャーナルは、たとえば 1 時間ごとに別の場所にバックアップまたは複製されます。

以下は、上記の Freeze と Thaw スクリプトの $LOGFILE の出力で、スナップショットのジャーナルの詳細を示しています。

01/04/2017 16:30:35: Backup.General.ExternalFreeze: Suspending system

Journal file switched to:
/trak/jnl/jrnpri/h20152/H20152_20170104.011
01/04/2017 16:30:35: Backup.General.ExternalFreeze: Start a journal restore for this backup with journal file: /trak/jnl/jrnpri/h20152/H20152_20170104.011

Journal marker set at
offset 197192 of /trak/jnl/jrnpri/h20152/H20152_20170104.011
01/04/2017 16:30:36: Backup.General.ExternalFreeze: System suspended
01/04/2017 16:30:41: Backup.General.ExternalThaw: Resuming system
01/04/2017 16:30:42: Backup.General.ExternalThaw: System resumed

VM スタンタイム

VM スナップショットの作成時、バックアップが完了してスナップショットがコミットされた後、VM を短時間フリーズする必要があります。 この短時間のフリーズは、VM のスタンと呼ばれることがよくあります。 スタンタイムについてよく説明されたブログ記事は、こちらをご覧ください。 以下に要約した内容を示し、Caché データベースの考慮事項に照らして説明します。

スタンタイムに関する記事より: 「VM スナップショットを作成するには、(i)デバイスの状態をディスクにシリアル化し、(ii)現在実行中のディスクを閉じてスナップショットポイントを作成するために、VM は「スタン」されます(無応答状態になります)。… 統合時、VM は、ディスクを閉じて統合に最適な状態にするために、「スタン」されます。」

スタンタイムは、通常数百ミリ秒です。ただし、コミットフェーズ中にディスクの書き込みアクティビティが非常に高い場合には、スタンタイムが数秒に長引くことがあります。

VM が、Caché データベースミラーリングに参加するプライマリまたはバックアップメンバーであり、スタンタイムがミラーのサービス品質(QoS)タイムアウトより長引く場合、ミラーはプライマリ VM に失敗としてレポートし、ミラーのテイクオーバーを開始します。

2018 年 3 月更新: 同僚の Pter Greskoff から指摘されました。バックアップミラーメンバーは、VM スタン中またはプライマリミラーメンバーが利用不可である場合には、QoS タイムアウトの半分超という短時間でフェイルオーバーを開始することがあります。

QoS の考慮事項とフェイルオーバーのシナリオの詳細については、「Quality of Service Timeout Guide for Mirroring(ミラーリングのサービス品質タイムアウトに関するガイド)」という素晴らしい記事を参照できますが、以下に、VM スタンタイムと QoS に関する要約を示します。

バックアップミラーが、QoS タイムアウトの半分の時間内にプライマリミラーからメッセージを受信しない場合、ミラーはプライマリが動作しているかどうかを確認するメッセージを送信します。 それからバックアップはさらに QoS タイムアウトの半分の時間、プライマリマシンからの応答を待機します。 プライマリからの応答がない場合、プライマリは停止しているとみなされ、バックアップがテイクオーバーします。

ビジー状態のシステムでは、プライマリからバックアップミラーにジャーナルが継続的に送信されるため、バックアップはプライマリが動作しているかどうかを確認する必要はありません。 ただし、バックアップが発生している可能性が高い静止時間中、アプリケーションがアイドル状態である場合、QoS タイムアウトの半分以上の時間、プライマリとバックアップミラーの間でメッセージのやり取りがない可能性があります。

これは Peter が提示した例ですが、このタイムフレームを、QoS タイムアウトが :08 秒で VM スタンタイムが :07 秒のアイドル状態にあるシステムで考えてみましょう。

  • :00 プライマリは キープライブでアービターに ping し、アービターは即座に応答
  • :01 バックアップメンバーはプライマリにキープアライブを送信し、プライマリは即座に応答
  • :02
  • :03 VM スタンの開始
  • :04 プライマリはアービターにキープアライブを送信しようとするが、スタンが完了するまで到達しない
  • :05 QoS の半分の時間が経過したため、バックアップメンバーはプライマリに ping を送信
  • :06
  • :07
  • :08 QoS タイムアウトが過ぎてもアービターはプライマリからの応答を受信しないため、接続を閉じる
  • :09 バックアップはプライマリからの応答を受信していないため、接続が失われたことをアービターに確認し、テイクオーバーを開始
  • :10 VM スタンが終了するが、間に合わない!!

上記のリンク先の記事にある「Pitfalls and Concerns when Configuring your Quality of Service Timeout(サービス品質タイムアウトを構成する際の落とし穴と考慮事項)」のセクションもお読みください。QoS を必要な期間だけ確保する際のバランスが説明されています。 QoS が長すぎる場合、特に 30 秒を超える場合も問題を引き起こす可能性があります。

2018 年 3 月更新の終了:

ミラーリングの QoS の詳細については、ドキュメンテーションを参照してください。

スタンタイムを最小限に抑える戦略には、データベースアクティビティが低い場合にバックアップを実行することと、ストレージを十分にセットアップすることが挙げられます。

上記で述べたように、スナップショットの作成時に指定できるオプションがいくつかあります。その 1 つは、スナップショットにメモリ状態を含めることです。_Caché データベースのバックアップにはメモリ状態は不要である_ことに注意してください。 メモリフラグが設定されている場合、仮想マシンの内部状態のダンプがスナップショットに含められます。 メモリスナップショットの作成には、もっと長い時間がかかります。 メモリスナップショットは、実行中の仮想マシンの状態を、スナップショットが作成された時の状態に戻すために使用されます。 これは、データベースファイルのバックアップには必要ありません。

メモリスナップショットを作成する場合、仮想マシンの状態全体が停止しますが、スタンタイムはさまざまです。

前述のとおり、バックアップでは、整合性のある使用可能なバックアップを保証するために、手動スナップショットで、またはバックアップソフトウェアによって、静止フラグを true に設定する必要があります。

VMware ログでスタンタイムを確認

ESXi 5.0 より、スナップショットのスタンタイムは、以下のようなメッセージで仮想マシンのログファイル(vmware.log)に記録されます。

2017-01-04T22:15:58.846Z| vcpu-0| I125: Checkpoint_Unstun: vm stopped for 38123 us

スタンタイムはミリ秒単位であるため、上記の例にある 38123 us は 38123/1,000,000 秒または 0.038 秒となります。

スタンタイムが許容範囲内であることを確認するため、またはスタンタイムが長引くせいで問題が発生していると思われる場合にトラブルシューティングを実施するため、関心のある VM のフォルダから vmware.log ファイルをダウンロードして確認することができます。 ダウンロードしたら、以下の Linux コマンドの例を使うなどして、ログを抽出しソートできます。

vmware.log ファイルのダウンロード例

vSphere 管理コンソールまたは ESXi ホストコマンドラインから VMware サポートバンドルを作成するなど、サポートログのダウンロードにはいくつかの方法があります。 全詳細については VMware ドキュメンテーションを参照できますが、以下は、スタンタイムを確認できるように、vmware.log ファイルを含む非常に小さなサポートバンドルを作成して収集する簡単な方法です。

VM ファイルが配置されているディレクトリの正式な名称が必要となります。 ssh を使用してデータベース VM が実行している ESXi ホストにログオンし、コマンド vim-cmd vmsvc/getallvms を使用して vmx ファイルとそれに関連付けられた一意の正式名称を一覧表示します。

たとえば、この記事で使用されているデータベース VM の正式な名称は次のように出力されます。 26 vsan-tc2016-db1 [vsanDatastore] e2fe4e58-dbd1-5e79-e3e2-246e9613a6f0/vsan-tc2016-db1.vmx rhel7_64Guest vmx-11

次に、ログファイルのみを収集してバンドルするコマンドを実行します。
vm-support -a VirtualMachines:logs

コマンドによって、以下の例のように、サポートバンドルの場所が表示されます。 To see the files collected, check '/vmfs/volumes/datastore1 (3)/esx-esxvsan4.iscinternal.com-2016-12-30--07.19-9235879.tgz'.

これで、sftp を使用してファイルをホストから転送し、さらに処理して確認できるようになりました。

この例では、サポートバンドルを解凍した後に、データベース VM の正式名称に対応するパスに移動します。 この場合は、 <bundle name>/vmfs/volumes/<host long name>/e2fe4e58-dbd1-5e79-e3e2-246e9613a6f0 というパスになります。

そこで番号付きの複数のログファイルが表示されます。最新のログファイルには番号は付きません。つまり、vmware.log と示されます。 ログはわずか数百 KB ですが、たくさんの情報が記載されています。ただし、注目するのは stun/unstun の時間のみで、grep を使うと簡単に見つけ出すことができます。 次は、その記載例です。

$ grep Unstun vmware.log
2017-01-04T21:30:19.662Z| vcpu-0| I125: Checkpoint_Unstun: vm stopped for 1091706 us
--- 
2017-01-04T22:15:58.846Z| vcpu-0| I125: Checkpoint_Unstun: vm stopped for 38123 us
2017-01-04T22:15:59.573Z| vcpu-0| I125: Checkpoint_Unstun: vm stopped for 298346 us
2017-01-04T22:16:03.672Z| vcpu-0| I125: Checkpoint_Unstun: vm stopped for 301099 us
2017-01-04T22:16:06.471Z| vcpu-0| I125: Checkpoint_Unstun: vm stopped for 341616 us
2017-01-04T22:16:24.813Z| vcpu-0| I125: Checkpoint_Unstun: vm stopped for 264392 us
2017-01-04T22:16:30.921Z| vcpu-0| I125: Checkpoint_Unstun: vm stopped for 221633 us

この例では、2つのグループのスタンタイムを確認できます。1 つはスナップショップ作成時、そしてもう 1 つは、各ディスクのスナップショットが削除/統合された 45 分後(バックアップソフトウェアが読み取り専用 vmx ファイルのコピーを完了したとき)のものです。 上記の例では、最初のスタンタイムは 1 秒をわずかに上回っていますが、ほとんどのスタンタイムは 1 秒未満であることがわかります。

短時間のスタンタイムは、エンドユーザーが気付くものではありませんが、 Caché データベースミラーリングなどのシステムプロセスは、インスタンスが「アライブ」であるかどうかを継続的に監視しています。 スタンタイムがミラーリング QoS タイムアウトを超える場合、ノードは接続不可能であり「停止」状態とみなされ、フェイルオーバーがトリガーされます。

ヒント: すべてのログを確認するか、トラブルシューティングを実行する場合には、grep コマンドが役立ちます。すべての vmware*.log ファイルに対して grep を使用し、異常値、またはスタンタイムが QoS タイムアウトに達しているインスタンスを探してください。 以下のコマンドは、出力をパイプ処理し、awk で書式設定しています。

grep Unstun vmware* | awk '{ printf ("%'"'"'d", $8)} {print " ---" $0}' | sort -nr


最後に

通常稼働時に定期的にシステムを監視し、スタンタイムと、ミラーリングなどの HA 向けの QoS タイムアウトへの影響を理解しておく必要があります。 前述のとおり、スタンタイムまたはスタン解除タイムを最小限に抑える戦略には、データベースとストレージのアクティビティが低い場合にバックアップを実行することと、ストレージを十分にセットアップすることが挙げられます。 常時監視の場合、ログは、VMware ログのインサイトなどのツールを使用して処理できるかもしれません。

InterSystems データプラットフォームのバックアップと復元操作について、今後の記事で再度取り上げる予定です。 それまでは、システムのワークフローに基づいたコメントや提案がある場合は、以下のコメントセクションに投稿してください。

0
0 714
記事 Shintaro Kaminaka · 8月 12, 2020 6m read

最初の記事では、RESTForms(永続クラス用のREST API)について説明をしました。 基本的な機能についてはすでに説明しましたが、ここではクエリ機能を中心とする高度な機能について説明します。

* 基本クエリ * クエリ引数 * カスタムクエリ ### クエリ

クエリを使用すると、任意の条件に基づいてデータの一部を取得できます。 RESTFormsには、2種類のクエリがあります。

* 基本クエリは一度定義すればすべてのRESTFormsクラスに対して機能します。異なっているのはフィールドリストのみです。 * カスタムクエリはそれが指定され、使用できるクラスに対してのみ機能しますが、開発者はクエリの本文に完全にアクセスできます。 ### 基本クエリ

一度定義すると、すべてのクラスか一部のクラスですぐに使用できます。 基本クエリはシステムによって定義されているものもありますが、開発者が追加することもできます。これらのクエリはすべて、SELECTのフィールドリストのみを定義します。 その他すべて(絞り込み、ページネーションなど)はRESTFormsによって行われます。 form/objects/:class/:query を呼び出すと、単純なクエリを実行できます。 2番目の :query パラメーターはクエリ名(クエリの SELECT と FROM の間の内容)を定義します。 デフォルトのクエリタイプは次のとおりです。

クエリ説明
allすべての情報
infodisplayName と id
infoclassdisplayName、id、class
count行数

例えば、Form.Test.Personオブジェクトに関する基本的な情報を取得するには、infoclassクエリを実行できます。 form/objects/Form.Test.Person/infoclass

 {"children": [
    {"_id":"1", "displayName":"Alice",   "_class":"Form.Test.Person"},
    {"_id":"2", "displayName":"Charlie", "_class":"Form.Test.Person"},
    {"_id":"3", "displayName":"William", "_class":"Form.Test.Person"}
]}

RESTFormsは次の場所で myq という名前のクエリを探します(最初にヒットするまで)。

  1. フォームクラス内のqueryMYQクラスメソッド
  2. クエリクラス内のMYQパラメーター
  3. クエリクラス内のqueryMYQクラスメソッド
  4. Form.REST.Objectsクラス内のMYQパラメーター
  5. Form.REST.Objectsクラス内のqueryMYQクラスメソッド

独自のクエリクラスを定義できます(上記リストの項目2、3用)。このクエリクラスは、すべてのクラスで使用可能なクエリの定義を保持する特別なクラスです。 そのクラスで myq という名前の独自クエリを定義する手順は以下のとおりです。

  1. (1回だけ)YourClassName クラスを定義します。
  2. そのクラスで MYQ パラメーターか queryMYQ クラスメソッドを定義します。 パラメーターはメソッドよりも優先されます。
  3. メソッドまたはパラメーターは、SQLクエリのSELECTとFROMの間の部分を返す必要があります。
  4. (1回だけ)ターミナルで以下を実行します。 Do ##class(For.Settings).setSetting("queryclass", YourClassName)

メソッドの署名は以下のとおりです。

ClassMethod queryMYQ(class As %String) As %String

クラス固有のクエリを定義することもできます。 myq という名前の独自クラスクエリを定義する手順は以下のとおりです。

  1. フォームクラスで queryMYQ クラスメソッドを定義します。
  2. メソッドの署名は以下のとおりです。ClassMethod queryMYQ() As %String
  3. メソッドは、SQLクエリのSELECTとFROMの間の部分を返す必要があります。

URL 引数

フィルターやその他のパラメーターをURLで指定できます。 すべての引数は省略可能です。
引数サンプル値説明
size2ページサイズ
page1ページ番号
filter値+contains+WWHERE句
orderby値+descORDER BY句
collationUPPERCOLLATION句
nocount1レコード数を削除(クエリを高速化します)

これらの引数に関するいくつかの情報を次に示します。

ORDER BY句

結果の順序を変更します。 値は、カラム名またはカラム名+desc です。 カラム名は、SQLテーブルのカラム名またはカラム番号です。

WHERE句

絞り込み条件の書式は、カラム名+条件+値です。 カラム名+条件+値+カラム名2+条件2+値2のように、複数の条件を指定できます。 矢印構文とシリアルオブジェクトもサポートされています: Column_ColumnField+条件+値 Valueに空白が含まれている場合、サーバーに送信する前にタブに置き換えます。

URLSQL
neq!=
eq=
gte>=
gt>
lte<=
lt<
startswith%STARTSWITH
contains[
doesnotcontain'[
inIN
likeLIKE

リクエストの例:

form/objects/Form.Test.Simple/info?size=2&page=1&orderby=text
form/objects/Form.Test.Simple/all?orderby=text+desc
form/objects/Form.Test.Simple/all?filter=text+eq+Hello
form/objects/Form.Test.Person/infoclass?filter=company_name+contains+a
form/objects/Form.Test.Simple/all?filter=text+in+A9044~B5920

SQLアクセスには、ユーザーに適切なSQL権限(フォームテーブルに対するSELECT)を付与する必要があることに注意してください。

COLLATION句

書式は、collation=UPPER または collation=EXACT です。 指定した照合順序をWHERE句で強制します。 省略した場合、デフォルトの照合順序が使用されます。

ページネーション

ページネーションは、デフォルトで1ページあたり25レコードになります。 ページサイズと現在のページを変更するには、size 引数と page 引数を(1を基準として)指定します。

カスタムクエリ

form/objects/:class/custom/:query を呼び出すと、カスタムクエリを実行できます。 カスタムクエリを使用すると、開発者はクエリの内容全体を決めることができます。 size および page 以外のURLパラメーターは指定できません。 メソッドは他のすべてのURLパラメーターを解析する必要があります(またはForm.JSON.SQLからデフォルトのパーサーを呼び出す必要があります)。 myq という名前のカスタムクエリを定義する手順は以下のとおりです。

  1. フォームクラスで customqueryMYQ クラスメソッドを定義します。
  2. メソッドの署名は以下のとおりです。ClassMethod customqueryMYQ() As %String
  3. メソッドは有効なSQLクエリを返す必要があります。

デモ

[現在デモ環境はお試しいただくことができません。] こちらでRESTFormsをオンラインで試すことができます(ユーザー名:Demo、パスワード:Demo)。 また、RESTFormsUIアプリケーション(RESTFormsデータエディタ)もあります。こちらをご確認ください(ユーザー名:Demo、パスワード:Demo)。 クラスリストのスクリーンショットを以下に掲載しています。

まとめ

RESTFormsは、幅広くカスタマイズ可能なクエリ機能を提供します。

次の内容

次の記事では、いくつかの高度な機能について説明します。

  • メタデータの解釈
  • セキュリティと権限
  • オブジェクト名

リンク

  • RESTForms UI GitHubリポジトリ
  • 0
    0 144
    記事 Shintaro Kaminaka · 7月 30, 2020 11m read

    この記事では、RESTFormsプロジェクト(モダンなWebアプリケーション用の汎用REST APIバックエンド)を紹介します。

    プロジェクトの背後にあるアイデアは単純です。私はいくつかのREST APIを書いた後、REST APIが一般的に次の2つの部分で構成されていることに気付きました。

    • 永続クラスの操作
    • カスタムビジネスロジック

    また、独自のカスタムビジネスロジックを書く必要はありますが、RESTFormsには永続クラスの操作に関連するすべての機能を提供しています。

    使用例

    • Cachéにすでにデータモデルがあり、REST API形式で情報の一部(またはすべて)を公開したい
    • 新しいCachéアプリケーションを開発しており、REST APIを提供したい

    クライアントサイド

    このプロジェクトはWebアプリケーションのバックエンドとして開発されているため、JSだけで事足ります。 形式の変換は必要ありません。

    補足:CRUD

    オブジェクトまたはコレクションに対し、次の4つの操作を実行できます。

    • Create(作成)
    • Read(読み込み)
    • Update(更新)
    • Delete(削除)

    機能

    RESTFormsを使用して以下を実行できます。

    • 公開されたクラスに対するCRUD - クラスのメタデータを取得し、クラスのプロパティを作成 / 更新 / 削除できます。
    • オブジェクトに対するCRUD - オブジェクトを取得 / 作成 / 更新 / 削除できます。
    • オブジェクトコレクションに対するRead(SQL経由) - SQLインジェクションから保護します。
    • 自己検出 – 最初に使用可能なクラスのリストを取得し、その後でクラスのメタデータを取得し、そのメタデータを基にしてオブジェクトに対するCRUDを実行できます。

    パス

    以下の表には、主なパスとRESTFormsを使用して実行できる操作を掲載しています。

    <td>
      説明
    </td>
    
    <td>
      利用可能なすべてのクラスを一覧表示します
    </td>
    
    <td>
      すべてのクラスのメタデータを取得します
    </td>
    
    <td>
      クラスのメタデータ
    </td>
    
    <td>
      プロパティをクラスに追加します
    </td>
    
    <td>
      クラスのプロパティを変更します
    </td>
    
    <td>
      クラスのプロパティを削除します
    </td>
    
    <td>
      オブジェクトを取得します
    </td>
    
    <td>
      オブジェクトの1つのプロパティを取得します
    </td>
    
    <td>
      オブジェクトを作成します
    </td>
    
    <td>
      動的オブジェクトからオブジェクトを更新します
    </td>
    
    <td>
      オブジェクトからオブジェクトを更新します
    </td>
    
    <td>
      オブジェクトを削除します
    </td>
    
    <td>
      (SQL)クエリでクラスのオブジェクトを取得します
    </td>
    
    <td>
      (SQL)カスタムクエリでクラスのオブジェクトを取得します
    </td>
    
    URL
    info
    info/all
    info/:class
    field/:class
    field/:class
    field/:class/:property
    object/:class/:id
    object/:class/:id/:property
    object/:class
    object/:class/:id
    object/:class
    object/:class/:id
    objects/:class/:query
    objects/:class/custom/:query

    **RESTFormsを使い始めるには?**
    1. GitHubからプロジェクトをインポートします(お勧めの方法は独自リポジトリにサブモジュールとして追加する方法ですが、単にリリースをダウンロードしても良いです)。
    2. RESTFormsを介して公開したい各クラスについて以下を実施します。
    • アダプタクラスから継承する
    • 権限を指定します(一部のクラスを読み取り専用として公開する場合などに実施)。
    • オブジェクトの表示値として使用されるプロパティを指定します。
    • 表示したいプロパティの表示名を指定します。

    セットアップ

    1. [リリースページ](https://github.com/intersystems-ru/RESTForms/releases/tag/v1.0)で最新リリースである20161.xml( Caché 2016.1用)または201162.xml(Caché 2016.2以降用)をダウンロードして任意のネームスペースにインポートします。
    2. 新しいWebアプリケーション /forms をDispatchクラス Form.REST.Main を使用して作成します。
    3. http://localhost:57772/forms/test?Debug をブラウザで開き、インストールを検証します({"Status": "OK"} が出力され、場合によってはパスワードの入力が求められます)。
    4. テストデータが必要な場合は、次を呼び出します:

    do ##class(Form.Util.Init).populateTestForms()

    最初に、利用可能なクラスを知る必要があります。 この情報を取得するには、次を呼び出します。

    http://localhost:57772/forms/form/info
    

    次のような応答が返されます。

    [
       { "name":"Company",     "class":"Form.Test.Company" },
       { "name":"Person",      "class":"Form.Test.Person"  },
       { "name":"Simple form", "class":"Form.Test.Simple"  }
    ]

    現在3つのサンプルクラス(RESTFormで提供)があります。Person(Form.Test.Personクラス)のメタデータを見てみましょう。 この情報を取得するには、次を呼び出します。

    http://localhost:57772/forms/form/info/Form.Test.Person
    

    次のように、クラスのメタデータが応答として返されます。

    {  
       "name":"Person",
       "class":"Form.Test.Person",
       "displayProperty":"name",
       "objpermissions":"CRUD",
       "fields":[  
          { "name":"name",     "type":"%Library.String",    "collection":"", "displayName":"Name",          "required":0, "category":"datatype" },
          { "name":"dob",      "type":"%Library.Date",      "collection":"", "displayName":"Date of Birth", "required":0, "category":"datatype" },
          { "name":"ts",       "type":"%Library.TimeStamp", "collection":"", "displayName":"Timestamp",     "required":0, "category":"datatype" },
          { "name":"num",      "type":"%Library.Numeric",   "collection":"", "displayName":"Number",        "required":0, "category":"datatype" },
          { "name":"аge",      "type":"%Library.Integer",   "collection":"", "displayName":"Age",           "required":0, "category":"datatype" },
          { "name":"relative", "type":"Form.Test.Person",   "collection":"", "displayName":"Relative",      "required":0, "category":"form"     },
          { "name":"Home",     "type":"Form.Test.Address",  "collection":"", "displayName":"House",         "required":0, "category":"serial"   },
          { "name":"company",  "type":"Form.Test.Company",  "collection":"", "displayName":"Company",       "required":0, "category":"form"     }
       ]
    }

    これらの情報は次のような意味を持ちます。

    クラスのメタデータ:

    • name - クラスの表示名。
    • class - 基本となる永続クラス。
    • displayProperty - オブジェクトを表示するときに使用するオブジェクトのプロパティ。
    • objpermissions - ユーザーがオブジェクトを使用して実行できる操作。 この例では、ユーザーは新しいオブジェクトを作成し、既存のオブジェクトを変更し、既存のオブジェクトを削除し、次を取得できます。

    プロパティのメタデータ:

    • name - プロパティ名 - クラスの定義と同じです。
  • type - プロパティのクラス。
  • * コレクション - リスト/配列のコレクションです。 * displayName - 表示プロパティ名。 * required - このプロパティが必須であるかどうか。 * category - プロパティのタイプクラスのカテゴリ。 RESTForms対応のすべてのクラスが「form」として表示されることを除き、通常のCachéクラスのカテゴリに従います。

    クラス定義では次のようになります。

    /// テストフォーム: Person
    Class Form.Test.Person Extends (%Persistent, Form.Adaptor, %Populate)
    {
    
    /// フォーム名。グローバルキーではないため、何でもかまいません。
    /// クラスをフォームとして持たないようにするには(ここのように)空の文字列に設定します。 
    Parameter FORMNAME = "Person";
    
    /// デフォルトの権限
    /// このフォームのオブジェクトは、作成、読み取り、更新、削除できます。
    /// すべてのユーザーの権限を変更するには、このパラメーターを再定義します。
    /// このクラスのcheckPermissionメソッドを再定義します(Form.Securityを参照してください)。
    /// ユーザーやロールなどに基づいて独自のセキュリティを追加します。
    Parameter OBJPERMISSIONS As %String = "CRUD";
    
    /// オブジェクトの基本情報に使用されるプロパティ
    /// デフォルトでは、getObjectDisplayNameメソッドはここから値を取得します。
    Parameter DISPLAYPROPERTY As %String = "name";
    
    /// このパラメーターの値をSQLでORDER BY句の値として使用します。 
    Parameter FORMORDERBY As %String = "dob";
    
    /// Personの名前。
    Property name As %String(COLLATION = "TRUNCATE(250)", DISPLAYNAME = "Name", MAXLEN = 2000);
    
    /// Personの生年月日。
    Property dob As %Date(DISPLAYNAME = "Date of Birth", POPSPEC = "Date()");
    
    Property ts As %TimeStamp(DISPLAYNAME = "Timestamp") [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)} ];
    
    Property num As %Numeric(DISPLAYNAME = "Number") [ InitialExpression = "2.15" ];
    
    /// Personの年齢。<br>
    /// これは、 <property>DOB</property> から派生した値を持つ計算されたフィールドです。
    Property аge As %Integer(DISPLAYNAME = "Age") [ Calculated, SqlComputeCode = { set {*}=##class(Form.Test.Person).currentAge({dob})}, SqlComputed, SqlComputeOnChange = dob ];
    
    /// このクラスメソッドは、誕生日 <var>date</var> が与えられた場合に現在の年齢を計算します。
    ClassMethod currentAge(date As %Date = "") As %Integer [ CodeMode = expression ]
    {
    $Select(date="":"",1:($ZD($H,8)-$ZD(date,8)\10000))
    }
    
    /// Personの配偶者。
    /// これは別の永続オブジェクトへの参照です。
    Property relative As Form.Test.Person(DISPLAYNAME = "Relative");
    
    /// Personの自宅住所。 埋め込みオブジェクトを使用します。
    Property Home As Form.Test.Address(DISPLAYNAME = "House");
    
    /// このPersonが働いている会社。
    Relationship company As Form.Test.Company(DISPLAYNAME = "Company") [ Cardinality = one, Inverse = employees ];
    }

    クラスでRESTFormsを有効にする

    そして、このクラスでRESTFormsを有効にするため、通常の永続クラスから始めて次のことを行いました。

    1. Form.Adaptor から拡張しました。
    2. 値を含むパラメーター FORMNAME(クラス名)を追加しました。
    3. OBJPERMISSIONS パラメーター(すべての権限のCRUD)を追加しました。
    4. DISPLAYPROPERTY パラメーター(オブジェクト名の表示に使用されるプロパティ名)を追加しました。
    5. FORMORDERBY パラメーター(RESTFormsを使用するクエリでソートするデフォルトのプロパティ)を追加しました。
    6. メタデータで確認したいプロパティごとに DISPLAYNAME プロパティのパラメーターを追加しました。

    以上です。 コンパイル後、RESTFormsを含むクラスを使用できるようになります。

    いくつかのテストデータを生成しましたので(インストールのステップ4を参照)、IDが1のPersonを取得してみましょう。 オブジェクトを取得するには、次を呼び出します。

    http://localhost:57772/forms/form/object/Form.Test.Person/1

    その応答は以下のとおりです(生成されるデータは異なる場合があります)。

    {
       "_class":"Form.Test.Person",
       "_id":1,
       "name":"Klingman,Rhonda H.",
       "dob":"1996-10-18",
       "ts":"2016-09-20T10:51:31.375Z",
       "num":2.15,
       "аge":20,
       "relative":null,
       "Home":{
          "_class":"Form.Test.Address",
          "House":430,
          "Street":"5337 Second Place",
          "City":"Jackson"
       },
       "company":{
          "_class":"Form.Test.Company",
          "_id":60,
          "name":"XenaSys.com",
          "employees":[
             null
          ]
       }
    }

    オブジェクト(具体的にはnumプロパティ)を変更するには、次を呼び出します。

    PUT http://localhost:57772/forms/form/object/Form.Test.Person

    このボディを使用します。

    {
       "_class":"Form.Test.Person",
       "_id":1,
       "num":3.15
    }

    速度を上げるには、_class_id、および変更対象のプロパティのみをリクエストのボディに含める必要があります。

    では、新しいオブジェクトを作成しましょう。 以下を呼び出します。

    POST http://localhost:57772/forms/form/object/Form.Test.Person

    このボディを使用します。

    {
       "_class":"Form.Test.Person",
        "name":"Test person",
        "dob":"2000-01-18",
        "ts":"2016-09-20T10:51:31.375Z",
        "num":2.15,
        "company":{ "_class":"Form.Test.Company", "_id":1 }
    }

    オブジェクトの作成が成功した場合、RESTFormsは以下のようにIDを返します。

    {"Id": "101"}

    成功しなかった場合、エラーがJSON形式で返されます。 すべての永続オブジェクトのプロパティは、 _class および _id プロパティによってのみ参照する必要があります。

    そして最後に、新しいオブジェクトを削除しましょう。 以下を呼び出します。

    DELETE http://localhost:57772/forms/form/object/Form.Test.Person/101

    これがForm.Test.Personクラスに対する完全なCRUDです。

    デモ

    [現在デモ環境はお試しいただくことができません。] こちらでRESTFormsをオンラインで試すことができます(ユーザー名:Demo、パスワード:Demo)。

    また、RESTFormsUIアプリケーション(RESTFormsデータエディタ)もあります。こちらをご確認ください(ユーザー名:Demo、パスワード:Demo)。 クラスリストのスクリーンショットを以下に掲載しています。

    まとめ

    RESTFormsは永続クラスに関する、REST APIから要求されるほとんどの機能を提供します。

    次の内容

    この記事では、RESTFormsの機能について説明しました。 次回の記事では、いくつかの高度な機能(クライアントからSQLインジェクションのリスクを冒さずにデータの一部を安全に取得できるクエリなど)についてお話ししたいと思います。 この記事のパート2でクエリに関する情報をお読みください

    RESTFormsUI(RESTFormsデータエディタ)もあります。

    リンク

    0
    0 344
    記事 Mihoko Iijima · 7月 21, 2020 2m read

    IRIS で REST サーバを作成する際に準備する REST ディスパッチクラスを API ファーストの手順で作成する方法を解説します。
    (OpenAPI 2.0に基づいて作成したアプリケーション定義を使用してディスパッチクラスを作成する手順を解説します)

    このビデオには、以下の関連ビデオがあります。

    もくじ

    最初~ 復習ビデオ/関連ビデオについて など

    2:36~  作成するディスパッチクラスの内容

    4:15~ RESTディスパッチクラス:APIファーストで作成する方法(手順説明)

    5:55~ アプリケーションの仕様を定義する (例)

    6:19~ IRISにアプリケーション仕様を登録する(説明)など

    7:40~ POST要求の実行 (例)

    8:24~ 実演

     ↓実演で使用したURL↓
     http://localhost:52773/api/mgmnt/v2/user/crud2

    10:32~ (POST要求の)実行結果

    11:50~ ベースURLの設定(管理ポータルでの設定)+ 実演

    0
    0 358
    記事 Mihoko Iijima · 7月 20, 2020 1m read

    IRIS で作成する REST サーバの仕組みを解説します。


    このビデオには、以下の関連ビデオがあります。

    もくじ

    0:55~ 今回の説明内容解説と関連ビデオについて

    3:00~  RESTとは?

    3:52~  動作の仕組み

    6:23~ RESTディスパッチクラスとは

    8:04~ RESTディスパッチクラスの実装方法

    8:34~ RESTディスパッチクラス:全て手動で作成する方法 概要

    9:30~ RESTディスパッチクラス:APIファーストで作成する方法 概要

    11:12~ 共通:ベースURLの設定(管理ポータルでの設定)

    11:37~最後まで 確認できたこと

    ※YouTubeでご覧いただくと、もくじの秒数にジャンプできます。

    0
    0 764
    記事 Tomohiro Iwamoto · 7月 16, 2020 25m read

    この記事ではVMware ESXi 5.5以降の環境にCaché 2015以降を導入する場合の構成、システムのサイジング、およびキャパシティ計画のガイドラインを示します。 

    ここでは、皆さんがVMware vSphere仮想化プラットフォームについてすでに理解していることを前提としています。 このガイドの推奨事項は特定のハードウェアやサイト固有の実装に特化したものではなく、vSphereの導入を計画して構成するための完全なガイドとして意図されたものでもありません。これは、皆さんが選択可能なベストプラクティス構成をチェックリストにしたものです。 これらの推奨事項は、皆さんの熟練したVMware実装チームが特定のサイトのために評価することを想定しています。 

    InterSystems データプラットフォームとパフォーマンスに関する他の連載記事のリストはこちらにあります。 

    注意: 本番データベースインスタンス用のVMメモリを予約し、Cachéに確実にメモリを使用させてデータベースのパフォーマンスに悪影響を与えるスワップやバルーニングの発生を防ぐ必要があることを強調するため、この記事を2017年1月3日に更新しています。 詳細については、以下のメモリセクションを参照してください。 

    参考情報 

    0
    0 636
    記事 Toshihiko Minamoto · 7月 8, 2020 5m read

    ** 2018年2月12日改訂 

    この記事はInterSystems IRISに関するものですが、Caché、Ensemble、およびHealthShareのディストリビューションにも適用されます。 

    概要

    メモリはページ単位で管理されます。 Linuxシステムでは、デフォルトのページサイズは4KBです。 Red Hat Enterprise Linux 6、SUSE Linux Enterprise Server 11、およびOracle Linux 6では、HugePageと呼ばれるシステム構成に応じて、ページサイズを2MBまたは1GBに増やす方法が導入されました。 

    第一に、HugePagesは起動時に割り当てる必要があり、適切に管理や計算が行われていない場合はリソースが浪費される可能性があります。 結果的に、さまざまなLinuxディストリビューションでTransparent HugePagesがデフォルトで有効になっているカーネル2.6.38が導入されました。 これは、HugePagesの作成、管理、使用を自動化することを意図したものでした。 旧バージョンのカーネルもこの機能を備えている可能性がありますが、[always] に指定されておらず、 [madvise] に設定されている可能性があります。   

    0
    0 1686
    記事 Toshihiko Minamoto · 7月 6, 2020 24m read

    この記事では、InterSystems Technologiesに基づく、堅牢なパフォーマンスと可用性の高いアプリケーションを提供するリファレンスアーキテクチャをサンプルとして提供します。Caché、Ensemble、HealthShare、TrakCare、およびDeepSee、iKnow、Zen、Zen Mojoといった関連する組み込みテクノロジーに適用可能です。 

    Azureには、リソースを作成して操作するための、Azure ClassicとAzure Resource Managerという2つのデプロイメントモデルがあります。 この記事で説明する情報は、Azure Resource Managerモデル(ARM)に基づきます。 

    要約 

    Microsoft Azureクラウドプラットフォームは、InterSystems製品すべてを完全にサポートできるクラウドサービスとして、IaaS(サービスとしてのインフラストラクチャ)向けの機能の豊富な環境を提供しています。 あらゆるプラットフォームやデプロイメントモデルと同様に、パフォーマンス、可用性、運用、管理手順などの環境に関わるすべての側面が正しく機能するように注意を払う必要があります。 この記事では、こういった各分野の詳細について説明しています。 

    パフォーマンス 

    0
    0 753
    記事 Mihoko Iijima · 7月 6, 2020 18m read

    InterSystemsのテクノロジースタックを使用して独自のアプリを開発し、顧客側で複数のデプロイを実行したいとします。 開発プロセスでは、クラスをインポートするだけでなく、必要に応じて環境を微調整する必要があるため、アプリケーションの詳細なインストールガイドを作成しました。この特定のタスクに対処するために、インターシステムズは、%Installer(Caché/Ensemble)という特別なツールを作成しました 。 続きを読んでその使用方法を学んでください。

    %Installer

    このツールを使用すると、インストール手順ではなく、目的のCaché構成を記述するインストールマニフェストを定義できます。作成したい Caché 構成を記述します。必要な内容を記述するだけで、環境を変更するために必要なコードが自動的に生成されます。
    したがって、マニフェストのみを配布する必要がありますが、インストール・コードはすべてコンパイル時に特定の Caché サーバ用に生成されます。

    0
    0 551