検索

クリアフィルター
記事
Toshihiko Minamoto · 2021年3月17日

Kubernetesにおけるミラーリングを使用しない高可用性IRISデプロイ

この記事では、従来のIRISミラーリング構成の代わりに、Kubernetesの Deploymentと分散永続ストレージを使って高可用性IRIS構成を構築します。 このデプロイでは、ノード、ストレージ、アベイラビリティーゾーンといったインフラストラクチャ関連の障害に耐えることが可能です。 以下に説明する方法を使用することで、RTOがわずかに延長されますが、デプロイの複雑さが大幅に軽減されます。図1 - 従来のミラーリング構成と分散ストレージを使ったKubernetesの比較 この記事に記載されているすべてのソースコードは、https://github.com/antonum/ha-iris-k8s より入手できます。要約 3ノードクラスタを実行しており、Kubernetesにいくらか詳しい方は、このまま以下のコードを使用してください。 kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr.yaml 上記の2行の意味がよくわからない方、またはこれらを実行できるシステムがない場合は、「高可用性要件」のセクションにお進みください。 説明しながら進めていきます。 最初の行は、Longhornというオープンソースの分散Kubernetesストレージをインストールしています。 2行目は、DurableSYS用にLonghornベースのボリュームを使って、InterSystems IRISデプロイをインストールしています。 すべてのポッドが実行状態になるまで待ちます。 kubectl get pods -A 上記の手順を済ませると、http://<IRIS Service Public IP>:52773/csp/sys/%25CSP.Portal.Home.zenにあるIRIS管理ポータル(デフォルトのパスワードは「SYS」)にアクセスできるようになります。また、次のようにしてIRISコマンドラインも使用できるようになります。 kubectl exec -it iris-podName-xxxx -- iris session iris 障害をシミュレートする では、実際に操作してみましょう。 ただし、操作の前に、データベースにデータを追加して、IRISがオンラインになったときに存在することを確認してください。 kubectl exec -it iris-6d8896d584-8lzn5 -- iris session iris USER>set ^k8stest($i(^k8stest))=$zdt($h)_" running on "_$system.INetInfo.LocalHostName() USER>zw ^k8stest ^k8stest=1 ^k8stest(1)="01/14/2021 14:13:19 running on iris-6d8896d584-8lzn5" ここからが「カオスエンジニアリング」の始まりです。 # IRISを停止 - コンテナは自動的に再開します kubectl exec -it iris-6d8896d584-8lzn5 -- iris stop iris quietly # ポッドを削除 - ポッドが再作成されます kubectl delete pod iris-6d8896d584-8lzn5 # irisポッドの配信によりノードを「強制ドレイン」 - ポッドは別のノードで再作成されます kubectl drain aks-agentpool-29845772-vmss000001 --delete-local-data --ignore-daemonsets --force # ノードを削除 - ポッドは別のノードで再作成されます # ただし、kubectlでは削除できないので、 そのインスタンスまたはVMを見つけて、強制終了します。 マシンにアクセスできる場合は、電源を切るかネットワークケーブルを外します。 冗談ではありません! 高可用性要件 以下の障害に耐えられるシステムを構築しています。 コンテナ/VM内のIRISインスタンス。 IRIS - レベル障害 ポッド/コンテナの障害 個々のクラスタノードの一時的な使用不能。 アベイラビリティーゾーンが一時的にオフラインになる場合などが挙げられます。 個々のクラスタノードまたはディスクの永久的障害。 基本的に、「障害をシミュレートする」セクションで試したシナリオです。 上記のいずれかの障害が発生すると、システムは人間が手をいれなくてもオンラインになり、データも失われません。 データの永続性が保証する範囲には一応制限がありますが、 ジャーナルサイクルとアプリケーション内のトランザクションの使用状況に応じて、IRISで実現されます(https://docs.intersystems.com/irisforhealthlatestj/csp/docbook/Doc.View.cls?KEY=GCDI_journal#GCDI_journal_writecycle)。いずれにしても、RPO(目標復旧ポイント)は2秒未満です。 システムの他のコンポーネント(Kubernetes APIサービス、etcdデータベース、ロードバランサーサービス、DNSなど)はスコープ外であり、通常、Azure AKSやAWS EKSなどのマネージドKubernetesサービスで管理されます。 別の言い方をすれば、個別の計算コンポーネントやストレージコンポーネントの障害はユーザーが処理するものであり、その他のコンポーネントの障害はインフラストラクチャ/クラウドプロバイダーが対処するものと言えます。 アーキテクチャ InterSystems IRISの高可用性について言えば、これまでミラーリングの使用が勧められてきました。 ミラーリングでは、データは、常時オンライン状態にある2つのIRISインスタンスが同期的に複製されます。 各ノードにはデータベースの完全なコピーが維持され、プライマリノードがオフラインになると、ユーザーはバックアップノードに接続し直すことができます。 基本的に、ミラーリング手法では、計算とストレージの両方の冗長性は、IRISが管理するものです。 ミラーをさまざまなアベイラビリティーゾーンにデプロイしたミラーリングにより、計算処理とストレージの障害に備えた必要な冗長性を実現し、わずか数秒という優れたRTO(目標復旧時間または障害後にシステムがオンラインに復旧するまでにかかる時間)を達成することができます。 AWSクラウドでミラーリングされたIRISのデプロイテンプレートは、https://jp.community.intersystems.com/node/486206から入手できます。 一方で、ミラーリングには、設定やバックアップ/復旧手順が複雑で、セキュリティ設定とローカルのデータベース以外のファイルの複製機能が不足しているという欠点があります。 コンテナオーケストレータ―にはKubernetesなど(今や2021年。ほかにオーケストレーターってありますか?!)がありますが、これらは、障害時に、Deploymentオブジェクトが障害のあるIRISポッド/コンテナを自動的に再起動することで、計算冗長性を実現しています。 Kubernetesアーキテクチャ図に、実行中のIRISノードが1つしかないのはこのためです。 2つ目のIRISノードを常時実行し続ける代わりに、計算可用性をKubernetesにアウトソースしています。 Kubernetesは、元のIRISポッドが何らかの理由でエラーとなった場合に、IRISポッドの再作成を確保します。 図2 フェイルオーバーのシナリオ ここまでよろしいでしょうか。 IRISノードに障害が発生すると、Kubernetesは単に新しいノードを作成します。 クラスタによって異なりますが、計算障害が発生してからIRISがオンラインになるまでには、10~90秒かかります。 ミラーリングではわずか2秒で復旧されるわけですから、これはダウングレードとなりますが、万が一サービス停止となった場合に許容できる範囲であれば、複雑さが緩和されることは大きなメリットと言えます。 ミラーリングの構成は不要です。 セキュリティ設定やファイル複製を気にする必要もありません。 率直に言うと、KubernetesでIRISを実行し、コンテナにログインしても、高可用性環境で実行していることには気づくこともないでしょう。 単一インスタンスのIRISデプロイと全く同じように見え、何ら感触にも変化はありません。 では、ストレージはどうでしょうか。 データベースを扱っているわけですから気になります。 どのようなフェイルオーバーのシナリオであっても、システムはデータの永続性も処理する必要があります。 ミラーリングは、IRISノードのローカルである計算ノードに依存しているため、 ノードが停止するか、一時的に使用不可になってしまえば、そのノードのストレージも停止してしまいます。 ミラーリング構成では、IRISがIRISレベルでデータベースを複製するのはこれに対処するためです。 コンテナの再起動時に元の状態を維持したデータベースを使用できるだけでなく、ノードやネットワークのセグメント全体(アベイラビリティーゾーン)が停止するといったイベントに備えて、冗長性を提供できるストレージが必要です。 ほんの数年前、これを解決できる簡単な答えは存在しませんでした。 上の図から推測できるように、今ではその答えを得ています。 分散コンテナストレージです。 分散ストレージは、基盤のホストボリュームを抽象化し、k8sクラスタのすべてのノードが使用できる共同の1つのクラスターとして提示します。 この記事では、インストールが非常に簡単なLonghorn(https://longhorn.io)という無料のオープンソースのストレージを使用しますが、 OpenEBS、Porworx、StorageOSといったほかのストレージも利用できます。同じ機能が提供されています。 CNCF IncubatingプロジェクトであるRook Cephも検討する価値があるでしょう。 この種のハイエンドとしては、NetAppやPureStorageといったエンタープライズ級のストレージソリューションがあります。 手順 「要約」セクションでは、1回にすべてをインストールしました。 インストールと検証の手順の説明は、付録Bをご覧ください。 Kubernetesストレージ まずは、コンテナとストレージ全般について、またIRISがどこに適合するのかについて説明しましょう。 デフォルトでは、コンテナ内のすべてのデータは揮発性であるため、 コンテナが停止すればデータは消失します。 Dockerでは、ボリュームの概念を使用することができるため、 基本的に、ホストOSのディレクトリをコンテナに公開することができます。 docker run --detach --publish 52773:52773 --volume /data/dur:/dur --env ISC_DATA_DIRECTORY=/dur/iconfig --name iris21 --init intersystems/iris:2020.3.0.221.0 上記の例では、IRISコンテナを起動し、host-localの「/data/dur」ディレクトリを、「/dur」マウントポイントのコンテナからアクセスできるようにしています。 つまり、コンテナがこのディレクトリに何かを格納している場合、それは保持され、コンテナの次回起動時に使用できるようになります。 IRIS側では、ISC_DATA_DIRECTORYを指定することで、IRISに、コンテナの再起動時に損失してはいけないすべてのデータを特定のディレクトリに格納するように指示することができます。 ドキュメントの「Durable SYS」というIRISの機能をご覧ください( https://docs.intersystems.com/irisforhealthlatestj/csp/docbook/Doc.View.cls?KEY=ADOCK#ADOCK_iris_durable_running)。 Kubernetesでの構文は異なりますが、概念は同じです。 IRISの基本的なKubernetes Deploymentは以下のようになります。 apiVersion: apps/v1 kind: Deployment metadata: name: iris spec: selector: matchLabels: app: iris strategy: type: Recreate replicas: 1 template: metadata: labels: app: iris spec: containers: - image: store/intersystems/iris-community:2020.4.0.524.0 name: iris env: - name: ISC_DATA_DIRECTORY value: /external/iris ports: - containerPort: 52773 name: smp-http volumeMounts: - name: iris-external-sys mountPath: /external volumes: - name: iris-external-sys persistentVolumeClaim: claimName: iris-pvc 上記のデプロイ仕様では、「volumes」の部分にストレージボリュームがリストされています。 このボリュームには、「iris-pvc」などのpersistentVolumeClaimを介して、コンテナの外部からアクセスできます。 volumeMountsは、このボリュームをコンテナ内に公開します。 「iris-external-sys」は、ボリュームマウントを特定のボリュームに関連付ける識別子です。 実際には複数のボリュームが存在する可能性があり、ほかのボリュームと区別するためにこの名前が使用されるわけですから、 「steve」と名付けることも可能です。 すでにお馴染みのISC_DATA_DIRECTORYは、IRISが特定のマウントポイントを使用して、コンテナの再起動後も存続する必要のあるすべてのデータを格納するように指示する環境変数です。 では、persistentVolumeClaimのiris-pvcを見てみましょう。 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: iris-pvc spec: storageClassName: longhorn accessModes: - ReadWriteOnce resources: requests: storage: 10Gi かなりわかりやすいと思います。 「longhorn」ストレージクラスを使って、1つのノードでのみ読み取り/書き込みとしてマウント可能な、10ギガバイトを要求しています。 ここで重要なのは、storageClassName: longhornです。 AKSクラスタで利用できるストレージクラスを確認してみましょう。 kubectl get StorageClass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE azurefile kubernetes.io/azure-file Delete Immediate true 10d azurefile-premium kubernetes.io/azure-file Delete Immediate true 10d default (default) kubernetes.io/azure-disk Delete Immediate true 10d longhorn driver.longhorn.io Delete Immediate true 10d managed-premium kubernetes.io/azure-disk Delete Immediate true 10d Azureのストレージクラスはほとんどありませんが、これらは、デフォルトでインストールされたクラスと、以下の一番最初のコマンドの一部でインストールしたLonghornのクラスです。 kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml 永続ボリュームクレームの定義に含まれる#storageClassName: longhornをコメントアウトすると、現在「default」としてマークされているストレージクラスが使用されます。これは、通常のAzureディスクです。 分散ストレージが必要な理由を説明するために、この記事の始めに説明した、longhornストレージを使用しない「カオスエンジニアリング」実験をもう一度行ってみましょう。 最初の2つのシナリオ(IRISの停止とポッドの削除)は正常に完了し、システムは稼働状態に回復します。 ノードをドレインまたは強制終了しようとすると、システムは障害状態になります。 #forcefully drain the node kubectl drain aks-agentpool-71521505-vmss000001 --delete-local-data --ignore-daemonsets kubectl describe pods ... Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 57s (x9 over 2m41s) default-scheduler 0/3 nodes are available: 1 node(s) were unschedulable, 2 node(s) had volume node affinity conflict. 基本的に、KubernetesはクラスタのIRISポッドを再起動しようとしますが、最初に起動されていたノードは利用できないため、ほかの2つのノードに「ボリュームノードアフィニティの競合」が発生しています。 このストレージタイプでは、基本的にボリュームはノードホストで使用可能なディスクに関連付けられているため、最初に作成されたノードでしか使用できないのです。 ストレージクラスにlonghornを使用すると、「強制ドレイン」と「ノードの強制終了」の2つの実験は正常に完了し、間もなくしてIRISポッドの動作が再開します。 これを実現するために、Longhornは3ノードクラスタをで使用可能なストレージを制御し、3つのすべてのノードにデータを複製しています。 ノードの1つが永久的に使用不可になると、Longhornは迅速にクラスタストレージを修復します。 「ノードの強制終了」シナリオでは、IRISポッドはほかの2つのボリュームレプリカを使ってすぐに別のノードで再起動されます。 するとAKSは失われたノードに置き換わる新しいノードをプロビジョニングし、準備ができたら、Longhorn がそのノードに必要なデータを再構築します。 すべては自動的に行われるため、ユーザーが手を入れる必要はありません。 図3 置換されたノードにボリュームレプリカを再構築するLonghorn k8sデプロイについて デプロイの他の側面をいくつか見てみましょう。 apiVersion: apps/v1 kind: Deployment metadata: name: iris spec: selector: matchLabels: app: iris strategy: type: Recreate replicas: 1 template: metadata: labels: app: iris spec: containers: - image: store/intersystems/iris-community:2020.4.0.524.0 name: iris env: - name: ISC_DATA_DIRECTORY value: /external/iris - name: ISC_CPF_MERGE_FILE value: /external/merge/merge.cpf ports: - containerPort: 52773 name: smp-http volumeMounts: - name: iris-external-sys mountPath: /external - name: cpf-merge mountPath: /external/merge livenessProbe: initialDelaySeconds: 25 periodSeconds: 10 exec: command: - /bin/sh - -c - "iris qlist iris | grep running" volumes: - name: iris-external-sys persistentVolumeClaim: claimName: iris-pvc - name: cpf-merge configMap: name: iris-cpf-merge strategy: Recreateとreplicas: 1では、Kubernetesに、常にIRISポッドの1つのインスタンスのみを実行し続けることを指示しています。 これが「ポッドの削除」シナリオを処理する部分です。 livenessProbeのセクションでは、IRISが常時、コンテナ内で稼働し、「IRIS停止」シナリオを処理できるようにしています。 initialDelaySecondsは、IRISが起動するまでの猶予期間です。 IRISがデプロイを起動するのにかなりの時間が掛かっている場合は、これを増やすと良いでしょう。 IRISのCPF MERGE機能を使用すると、コンテナの起動時に、iris.cpf構成ファイルのコンテンツを変更することができます。 関連するドキュメントについては、 https://docs.intersystems.com/irisforhealthlatestj/csp/docbook/DocBook.UI.Page.cls?KEY=RACS_cpf#RACS_cpf_edit_mergeをご覧ください。 この例では、Kubernetesの構成図を使って、マージファイルのコンテンツを管理しています( https://github.com/antonum/ha-iris-k8s/blob/main/iris-cpf-merge.yaml)。ここでは、IRISインスタンスが使用するグローバルバッファとgmheapの値を調整していますが、iris.cpfファイルにあるものはすべて調整できます。 デフォルトのIRISパスワードでさえも、CPFマージファイルの「PasswordHash」フィールドで変更可能です。 詳細については、https://docs.intersystems.com/irisforhealthlatestj/csp/docbook/Doc.View.cls?KEY=ADOCK#ADOCK_iris_images_password_authをご覧ください。 永続ボリュームクレーム(https://github.com/antonum/ha-iris-k8s/blob/main/iris-pvc.yaml)デプロイ(https://github.com/antonum/ha-iris-k8s/blob/main/iris-deployment.yaml)とCPFマージコンテンツを使った構成図(https://github.com/antonum/ha-iris-k8s/blob/main/iris-cpf-merge.yaml)のほかに、デプロイには、IRISデプロイをパブリックインターネットに公開するサービスが必要です(https://github.com/antonum/ha-iris-k8s/blob/main/iris-svc.yaml)。 kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE iris-svc LoadBalancer 10.0.18.169 40.88.123.45 52773:31589/TCP 3d1h kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 10d iris-svcの外部IPは、http://40.88.123.45:52773/csp/sys/%25CSP.Portal.Home.zenを介してIRIS管理ポータルにアクセスするために使用できます。 デフォルトのパスワードは「SYS」です。 ストレージのバックアップ/復元とスケーリング Longhornには、ボリュームの構成と管理を行えるウェブベースのUIがあります。 kubectlを使用して、ポッドや実行中のlonghorn-uiコンポーネントを特定し、ポート転送を確立できます。 kubectl -n longhorn-system get pods # longhorn-uiのポッドIDに注意してください。 kubectl port-forward longhorn-ui-df95bdf85-gpnjv 9000:8000 -n longhorn-system Longhorn UIは、http://localhost:9000で利用できるようになります。 図4 LonghornのUI Kubernetesコンテナストレージのほとんどのソリューションでは、高可用性のほか、バックアップ、スナップショット、および復元のための便利なオプションが用意されています。 詳細は実装によって異なりますが、一般的には、バックアップをVolumeSnapshotに関連付ける方法です。 この方法はLonghornでも利用できます。 使用しているKubernetesのバージョンとプロバイダーによっては、ボリュームスナップショット機能( https://github.com/kubernetes-csi/external-snapshotter)もインストールする必要があります。 そのようなボリュームショットの例には、「iris-volume-snapshot.yaml」が挙げられます。 使用する前に、バックアップを、LonghornのS3バケットまたはNFSボリュームに構成する必要があります。 https://longhorn.io/docs/1.0.1/snapshots-and-backups/backup-and-restore/set-backup-target/ # IRISボリュームのクラッシュコンシステントなバックアップを取得する kubectl apply -f iris-volume-snapshot.yaml IRISでは、バックアップ/スナップショットを取得する前にExternal Freezeを実行し、後でThawを実行することをお勧めします。 詳細については、https://docs.intersystems.com/irisforhealthlatestj/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=Backup.General#ExternalFreezeをご覧ください。 IRISボリュームのサイズを増やすには、IRISが使用する、永続ボリュームクレーム(iris-pvc.yamlファイル)のストレージリクエストを調整します。 ... resources: requests: storage: 10Gi #change this value to required そして、pvcの仕様を再適用します。 Longhornは、実行中のポッドにボリュームが接続されている場合、この変更を実際に適用することはできません。 そのため、ボリュームサイズが増えるように、デプロイでレプリカ数を一時的にゼロに変更します。 高可用性 - 概要 この記事の冒頭で、高可用性の基準を設定しました。 このアーキテクチャでは、次のようにそれを実現します。 障害の領域 自動的に緩和処置を行う機能 コンテナ/VM内のIRISインスタンス。 IRIS - レベル障害 IRISが機能停止となった場合、デプロイのLiveness Probeによってコンテナが再起動されます。 ポッド/コンテナの障害 デプロイによってポッドが再作成されます。 個々のクラスタノードの一時的な使用不能。 アベイラビリティーゾーンがオフラインになる場合などが挙げられます。 デプロイによって別のノードにポッドが再作成されます。 Longhornによって、別のノードでデータが使用できるようになります。 個々のクラスタノードまたはディスクの永久的障害。 上記と同様かつ、k8sクラスタオートスケーラーによって、破損したノードが新しいノードに置き換えられます。 Longhornによって、新しいノードにデータが再構築されます。 ゾンビやその他の考慮事項 DockerコンテナでのIRISの実行を理解している方であれば、「--init」フラグを使用したことがあるでしょう。 docker run --rm -p 52773:52773 --init store/intersystems/iris-community:2020.4.0.524.0 このフラグは、「ゾンビプロセス」の形成を防止することを目的としています。 Kubernetesでは、「shareProcessNamespace: true」を使用する(セキュリティの考慮事項が適用されます)か、コンテナで「tini」を使用することができます。 以下に、tiniを使用したDockerfileの例を示します。 FROM iris-community:2020.4.0.524.0 ... # Tiniを追加します USER root ENV TINI_VERSION v0.19.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini USER irisowner ENTRYPOINT ["/tini", "--", "/iris-main"] 2021年以降、すべてのInterSystemsが提供するコンテナイメージには、デフォルトでtiniが含まれています。 いくつかのパラメーターを調整することで、「ノードの強制ドレイン/ノードの強制終了」シナリオのフェイルオーバー時間をさらに短縮することができます。 Longhornのポッド削除ポリシー(https://longhorn.io/docs/1.1.0/references/settings/#pod-deletion-policy-when-node-is-down)およびkubernetes taintベースのエビクション機能(https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/#taint-based-evictions)をご覧ください。 免責事項 InterSystemsに勤務する者として、この内容を記載しておく必要があります。この記事では、分散型Kubernetesブロックストレージの一例としてLonghornを使用しています。 InterSystemsは、個々のストレージソリューションや製品を検証ないしは公式なサポート声明を発行していません。 特定のストレージソリューションがニーズに適合しているかどうかは、ユーザー自身がテストして検証する必要があります。 分散ストレージには、ノードローカルストレージとは大きく異なるパフォーマンス特性も備わっています。 特に書き込み操作の場合、データが永続化された状態とみなされるには、データを複数の場所に書き込む必要があります。 必ずワークロードをテストし、CSIドライバが提供する特定の動作とオプションを理解するようにしてください。 InterSystemsでは基本的に、Longhornなどの特定のストレージjソリューションを検証あるいは承認していません。これは、個々のHDDブランドやサーバーハードウェアメーカーを検証しないのと同じです。 個人的には、Longhornは扱いやすいと感じています。プロジェクトのGitHubページ(https://github.com/longhorn/longhorn)では、その開発チームは積極的に応答し、よく助けていただいています。 まとめ Kubernetesエコシステムは、過去数年間で大きな進化を遂げました。今日では、分散ブロックストレージソリューションを使用することで、IRISインスタンスやクラスタノード、さらにはアベイラビリティーゾーンの障害を維持できる高可用性構成を構築できるようにもなっています。 計算とストレージの高可用性をKubernetesコンポーネントにアウトソースできるため、従来のIRISミラーリングに比べ、システムの構成と管理が大幅に単純化しています。 ただしこの構成では、ミラーリング構成ほどのRTOとストレージレベルのパフォーマンスは得られないことがあります。 この記事では、Azure AKSをマネージドKubernetesとLonghorn分散ストレージシステムとして使用し、可用性の高いIRIS構成を構築しました。 ほかにも、AWS EKS、マネージドK8s向けのGoogle Kubernetes Engine、StorageOS、Portworx、OpenEBSなどの様々な分散コンテナストレージを探ってみると良いでしょう。エンタープライズ級のストレージソリューションには、NetApp、PureStorage、Dell EMCなどもあります。 付録A: クラウドでのKubernetesクラスタの作成 パブリッククラウドプロバイダーが提供するマネージドKubernetesサービスを使うと、このセットアップに必要なk8sクラスタを簡単に作成できます。 この記事で説明したデプロイには、AzureのAKSデフォルト構成をそのまますぐに使用することができます。 3ノードで新しいAKSクラスタを作成します。 それ以外はすべてデフォルトのままにします。 図5 AKSクラスタの作成 ローカルコンピュータにkubectlをインストールします。https://kubernetes.io/docs/tasks/tools/install-kubectl/ ローカルkubectlにASKクラスタを登録します。 図6 kubectlにAKSクラスタを登録 登録が済んだら、この記事の最初に戻って、longhornとIRISデプロイをインストールします。 AWS EKSでのインストールは、もう少し複雑です。 ノードグループのすべてのインスタンスにopen-iscsiがインストトールされていることを確認する必要があります。 sudo yum install iscsi-initiator-utils -y GKEでのLonghornのインストールには追加の手順があります。https://longhorn.io/docs/1.0.1/advanced-resources/os-distro-specific/csi-on-gke/をご覧ください。 付録B: インストール手順 手順1 - Kubernetesクラスタとkubectl 3ノードk8sクラスタが必要です。 Azureでのクラスタの構成方法は付録Aをご覧ください。 $ kubectl get nodes NAME STATUS ROLES AGE VERSION aks-agentpool-29845772-vmss000000 Ready agent 10d v1.18.10 aks-agentpool-29845772-vmss000001 Ready agent 10d v1.18.10 aks-agentpool-29845772-vmss000002 Ready agent 10d v1.18.10 手順2 - Longhornをインストールする kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml 「longhorn-system」ネームスペースのすべてのポッドが実行状態であることを確認してください。 これには数分かかる場合があります。 $ kubectl get pods -n longhorn-system NAME READY STATUS RESTARTS AGE csi-attacher-74db7cf6d9-jgdxq 1/1 Running 0 10d csi-attacher-74db7cf6d9-l99fs 1/1 Running 1 11d ... longhorn-manager-flljf 1/1 Running 2 11d longhorn-manager-x76n2 1/1 Running 1 11d longhorn-ui-df95bdf85-gpnjv 1/1 Running 0 11d 詳細とトラブルシューティングについては、Longhornインストールガイド(https://longhorn.io/docs/1.1.0/deploy/install/install-with-kubectl)をご覧ください。 手順3 - GitHubリポジトリのクローンを作成する $ git clone https://github.com/antonum/ha-iris-k8s.git $ cd ha-iris-k8s $ ls LICENSE iris-deployment.yaml iris-volume-snapshot.yaml README.md iris-pvc.yaml longhorn-aws-secret.yaml iris-cpf-merge.yaml iris-svc.yaml tldr.yaml 手順4 - コンポーネントを1つずつデプロイして検証する tldr.yamlファイルには、デプロイに必要なすべてのコンポーンネントが1つのバンドルとして含まれています。 ここでは、コンポーネントを1つずつインストールし、それぞれのセットアップを個別に検証します。 # 以前にtldr.yamlを適用したことがある場合は、削除します。 $ kubectl delete -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr.yaml # 永続ボリュームクレームを作成します $ kubectl apply -f iris-pvc.yaml persistentvolumeclaim/iris-pvc created $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE iris-pvc Bound pvc-fbfaf5cf-7a75-4073-862e-09f8fd190e49 10Gi RWO longhorn 10s # 構成図を作成します $ kubectl apply -f iris-cpf-merge.yaml $ kubectl describe cm iris-cpf-merge Name: iris-cpf-merge Namespace: default Labels: <none> Annotations: <none> Data ==== merge.cpf: ---- [config] globals=0,0,800,0,0,0 gmheap=256000 Events: <none> # irisデプロイを作成します $ kubectl apply -f iris-deployment.yaml deployment.apps/iris created $ kubectl get pods NAME READY STATUS RESTARTS AGE iris-65dcfd9f97-v2rwn 0/1 ContainerCreating 0 11s # ポッド名を書き留めます。 この名前は、次のコマンドでポッドに接続する際に使用します。 $ kubectl exec -it iris-65dcfd9f97-v2rwn -- bash irisowner@iris-65dcfd9f97-v2rwn:~$ iris session iris Node: iris-65dcfd9f97-v2rwn, Instance: IRIS USER>w $zv IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2020.4 (Build 524U) Thu Oct 22 2020 13:04:25 EDT # h<enter>でIRISシェルを終了 # exit<enter>でポッドを終了 # IRISコンテナのログにアクセスします $ kubectl logs iris-65dcfd9f97-v2rwn ... [INFO] ...started InterSystems IRIS instance IRIS 01/18/21-23:09:11:312 (1173) 0 [Utility.Event] Private webserver started on 52773 01/18/21-23:09:11:312 (1173) 0 [Utility.Event] Processing Shadows section (this system as shadow) 01/18/21-23:09:11:321 (1173) 0 [Utility.Event] Processing Monitor section 01/18/21-23:09:11:381 (1323) 0 [Utility.Event] Starting TASKMGR 01/18/21-23:09:11:392 (1324) 0 [Utility.Event] [SYSTEM MONITOR] System Monitor started in %SYS 01/18/21-23:09:11:399 (1173) 0 [Utility.Event] Shard license: 0 01/18/21-23:09:11:778 (1162) 0 [Database.SparseDBExpansion] Expanding capacity of sparse database /external/iris/mgr/iristemp/ by 10 MB. # irisサービスを作成します $ kubectl apply -f iris-svc.yaml service/iris-svc created $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE iris-svc LoadBalancer 10.0.214.236 20.62.241.89 52773:30128/TCP 15s 手順5 - 管理ポータルにアクセスする 最後に、サービスの外部IP(http://20.62.241.89:52773/csp/sys/%25CSP.Portal.Home.zen)を使って、IRISの管理ポータルに接続します。ユーザー名は「_SYSTEM」、パスワードは「SYS」です。 初回ログイン時にパスワードの変更が求められます。
記事
Toshihiko Minamoto · 2023年5月23日

Embedded Python による Web スクレイピングの基礎 - Python のお仕事を抽出しよう

## **Web スクレイピングとは:** 簡単に言えば、**Web スクレイピング**、**Web ハーベスティング**、または **Web データ抽出**とは、Web サイトから大量のデータ(非構造化)を収集する自動プロセスです。 ユーザーは特定のサイトのすべてのデータまたは要件に従う特定のデータを抽出できます。 収集されたデータは、さらに分析するために、構造化された形式で保存することができます。 ## **Web スクレイピングの手順:** 1. スクレイピングする Web ページの URL を見つけます。 2. 検査により、特定の要素を選択します。 3. 選択した要素のコンテンツを取得するコードを記述します。 4. 必要な形式でデータを保存します。 たったそれだけです!! ## **Web スクレイピングに使用される一般的なライブラリ/ツール** * Selenium - Web アプリケーションをテストするためのフレームワーク * BeautifulSoup – HTML、XML、およびその他のマークアップ言語からデータを取得するための Python ライブラリ * Pandas - データ操作と分析用の Python ライブラリ ## Beauthiful Soup とは? Beautiful Soup は、Web サイトから構造化データを抽出するための純粋な Python ライブラリです。 HTML と XML ファイルからデータを解析できます。 これはヘルパーモジュールとして機能し、利用できる他の開発者ツールを使って Web ページを操作する方法と同じ方法かより優れた方法で HTML と対話します。 * `lxml` や `html5lib` などの使い慣れたパーサーと連携して、有機的な Python の方法で、解析ツリーを移動操作、検索、および変更できるようにするため、通常、プログラマーは数時間または数日間に及ぶ作業を節約できます。 * Beautiful Soup のもう 1 つの強力で便利な機能は、フェッチされるドキュメントを Unicode に変換し、送信されるドキュメントを UTF-8 に変換するインテリジェンスです。 ドキュメント自体にエンコーディングが指定されていないか、Beautiful Soup がエンコーディングを検出できない場合を除き、開発者がその操作に注意する必要はありません。 * 他の一般的な解析またはスクレイピング手法と比較した場合も**高速**と見なされています。 ## **今日の記事では、Embedded Python と Object Script を使用して、ae.indeed.com にある Python の求人情報と企業をスクレイピングします。** ### ステップ 1 - スクレイピングする Web ページの URL を見つけます。 Url = https://ae.indeed.com/jobs?q=python&l=Dubai&start=0 スクレイピングするデータのある Web ページは以下のようになります。 ![](/sites/default/files/inline/images/images/image(4275).png)   **単純化と学習の目的で、"Job Title"(役職)と "Company"(会社)を抽出します。出力は以下のスクリーンショットのようになります。** ![](/sites/default/files/inline/images/irisani_0.gif)   **以下の 2 つの Python ライブラリを使用します。** * **requests** Requests は、Python プログラミング言語の HTTP ライブラリです。 プロジェクトの目標は、HTTP リクエストを単純化し、人間が読みやすくすることです。   * **bs4 for BeautifulSoup** Beautiful Soup は、HTML と XML ドキュメントを解析するための Python パッケージです。 HTML からデータを抽出するために使用できる解析済みページの解析ツリーを作成します。Web スクレイピングに役立ちます。 **以下の Python パッケージをインストールしましょう(Windows)。** irispip install --target C:\InterSystems\IRISHealth\mgr\python bs4 irispip install --target C:\InterSystems\IRISHealth\mgr\python requests ![](/sites/default/files/inline/images/images/image(4277).png) **Python ライブラリを ObjectScript にインポートしましょう**   Class PythonTesting.WebScraper Extends %Persistent { // pUrl = https://ae.indeed.com/jobs?q=python&l=Dubai&start= // pPage = 0 ClassMethod ScrapeWebPage(pUrl, pPage) { // imports the requests python library set requests = ##class(%SYS.Python).Import("requests") // import the bs4 python library set soup = ##class(%SYS.Python).Import("bs4") // import builtins package which contains all of the built-in identifiers set builtins = ##class(%SYS.Python).Import("builtins") } **Requests を使って HTML データを収集しましょう。** _注意: 「my user agent」でグーグル検索し取得した、ユーザーエージェント_ _URL は "https://ae.indeed.com/jobs?q=python&l=Dubai&start=" で、pPage はページ番号です。_ Requests を使って URL に HTTP GET リクエストを行い、そのレスポンスを "req" に格納します。 set headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"} set url = "https://ae.indeed.com/jobs?q=python&l=Dubai&start="_pPage set req = requests.get(url,"headers="_headers) req オブジェクトには、Web ページから返された HTML が含まれます。 **これを BeautifulSoup HTML パーサーで実行し、求人データを抽出できるようにします。** set soupData = soup.BeautifulSoup(req.content, "html.parser") set title = soupData.title.text W !,title **タイトルは以下のように表示されます** ![](/sites/default/files/inline/images/images/image(4278).png) ### ステップ 2 - 検査し、必要な要素を選択します。 **このシナリオでは、通常 <div> タグに含まれる求人リストに注目しています。ブラウザ内で要素を検査すると、その div クラスが見つかります。** ここでは、必要な情報は、「 <div class="cardOutline tapItem ... </div>」 に格納されています。 ### ステップ 3 - 選択した要素のコンテンツを取得するコードを記述します。 **BeautifulSoup の find_all 機能を使用して、クラス名 "cardOutline" を含むすべての <div> タグを検索します。** //parameters to python would be sent as a python dictionary set divClass = {"class":"cardOutline"} set divsArr = soupData."find_all"("div",divClass...) これによりリストが返されます。これをループ処理すると、Job Titles と Company を抽出できます。 ###ステップ 4 - 必要なフォーマットでデータを保存/表示します。 以下の例では、データをターミナルに書き出します。 set len = builtins.len(divsArr) W !, "Job Title",$C(9)_" --- "_$C(9),"Company" for i = 1:1:len { Set item = divsArr."__getitem__"(i - 1) set title = $ZSTRIP(item.find("a").text,"W") set companyClass = {"class_":"companyName"} set company = $ZSTRIP(item.find("span", companyClass...).text,"W") W !,title,$C(9)," --- ",$C(9),company } builtins.len() を使用して、divsArr リストの長さを取得していることに注意してください。 識別子名: ObjectScript と Python の識別子の命名規則は異なります。 たとえば、Python のメソッド名ではアンダースコア(_)を使用でき、\__getitem\__ や \__class\__ のようにいわゆる「ダンダー」といわれる特殊なメソッドや属性で実際に広く使用されています(「ダンダー」は「double underscore = 二重アンダースコア」の略です)。 このような識別子を ObjectScript で使用するには、二重引用符で囲みます: 識別子名に関する InterSystems ドキュメント ## クラスメソッドの例 ClassMethod ScrapeWebPage(pUrl, pPage) // pUrl = https://ae.indeed.com/jobs?q=python&l=Dubai&start= // pPage = 0 ClassMethod ScrapeWebPage(pUrl, pPage) { set requests = ##class(%SYS.Python).Import("requests") set soup = ##class(%SYS.Python).Import("bs4") set builtins = ##class(%SYS.Python).Builtins() set headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"} set url = pUrl_pPage set req = requests.get(url,"headers="_headers) set soupData = soup.BeautifulSoup(req.content, "html.parser") set title = soupData.title.text W !,title set divClass = {"class_":"cardOutline"} set divsArr = soupData."find_all"("div",divClass...) set len = builtins.len(divsArr) W !, "Job Title",$C(9)_" --- "_$C(9),"Company" for i = 1:1:len { Set item = divsArr."__getitem__"(i - 1) set title = $ZSTRIP(item.find("a").text,"W") set companyClass = {"class_":"companyName"} set company = $ZSTRIP(item.find("span", companyClass...).text,"W") W !,title,$C(9)," --- ",$C(9),company } } ## 今後の内容.. ObjectScript とEmbedded Python と数行のコードを使用して、いつも使用する求人サイトのデータをスクレイピングし、求人タイトル、会社、給料、職務内容、メールアドレス/リンクを簡単に収集できます。 たとえば、ページが複数ある場合、ページを使用して簡単にそれらをトラバースできます。 このデータを Pandas データフレームに追加して重複を削除したら、関心のある特定のキーワードに基づいてフィルターを適用できます。 このデータを NumPy で実行してラインチャートを取得します。 または、One-Hot エンコーディングをデータに実行し、ML モデルを作成/トレーニングします。興味のある特定の求人情報がある場合は、自分に通知を送信するようにします。 😉 それではコーディングをお楽しみください!!! 「いいね」ボタンも忘れずに押してください 😃
記事
Toshihiko Minamoto · 2020年10月22日

JSONの機能強化

InterSystems IRIS 2019.1は公開されてからしばらく経ちますが、気づかれていない可能性のある、JSONの処理の強化機能について説明したいと思います。 最新のアプリケーションを構築する際、特にRESTエンドポイントを操作する際は、JSONをシリアル化形式として扱うことが重要です。 ## JSONの書式 まず、JSONに書式設定を適用すると、人の目で読みやすくなります。 コードをデバックする際に、特定のサイズでJSONコンテンツを確認する場合に非常に役立ちます。 構造が単純化されていれば、ざっと目を通すことが容易にはなりますが、ネストされている複数の要素に遭遇すると、あっという間に読みづらくなります。 以下に簡単な例を示します。 {"name":"Gobi","type":"desert","location":{"continent":"Asia","countries":["China","Mongolia"]},"dimensions":{"length":1500,"length_unit":"km","width":800,"width_unit":"km"}} 人の目でより読みやすい形式を適用すると、コンテンツの構造を調べやすくなります。 適切な改行やインデントを使用した同一のJSON構造を見てみましょう。 { "name":"Gobi", "type":"desert", "location":{ "continent":"Asia", "countries":[ "China", "Mongolia" ] }, "dimensions":{ "length":1500, "length_unit":"km", "width":800, "width_unit":"km" } } この単純な例だけでも、出力がかなり大きくなるため、多くのシステムでこの書式がデフォルト設定となっていない理由は明確でしょう。 ただし、この詳細な書式により、基礎構造を簡単に読み取れるようになり、何かが間違っているかどうかを見つけやすくなります。 InterSystems IRIS 2019.1では、%JSONという名前のパッケージが導入されました。 パッケージには、上記で示したとおり、動的なオブジェクトと配列、そしてJSON文字列をより読みやすくできる整形ツールなど、いくつかの便利なユーティリティがあります。 %JSON.Formatterは非常に単純なインターフェースを持つクラスです。 すべてのメソッドはインスタンスメソッドであるため、必ずインスタンスを取得するところから始めます。 USER>set formatter = ##class(%JSON.Formatter).%New() この選択の背景には、インデント(空白またはタブなど)特定の文字や行末記号の特定の文字を使えるように整形ツールを1回構成し、それ以降、必要な個所に使用できるようになるという理由があります。 Format()メソッドは、動的なオブジェクト化配列またはJSON文字列を取ります。 では、動的なオブジェクトを使用した簡単な例を見てみましょう。 USER>do formatter.Format({"type":"string"}) { "type":"string" } そして、以下は、同じJSONコンテンツでJSON文字列を使った例です。 USER>do formatter.Format("{""type"":""string""}") { "type":"string" } Format()メソッドは、整形された文字列を現在のデバイスに出力しますが、直接変数に出力する場合のFormatToString()とFormatToStream() も表示されます。 ## 変速を変える 上記は素晴らしい機能ですが、それだけでは記事にする価値はないかもしれません。 InterSystems IRIS 2019.1では、永続オブジェクトと一時オブジェクトをJSONとの間でシリアル化する便利な方法も導入されています。 ここで調べるクラスは %JSON.Adaptorです。 概念が%XML.Adaptorに非常に似ているため、その名前が付けられています。 JSONとの間でシリアル化するクラスは、%JSON.Adaptorをサブクラス化する必要があります。 クラスは、いくつかの便利なメソッドを継承しますが、中でも%JSONImport()と%JSONExport()の継承は非常に役立ちます。 これについては例で示すのが一番良いでしょう。 次のクラスがあったとします。 Class Model.Event Extends (%Persistent, %JSON.Adaptor) {  Property Name As %String;  Property Location As Model.Location; } および Class Model.Location Extends (%Persistent, %JSON.Adaptor) {  Property City As %String;  Property Country As %String; } ご覧の通り、永続的なイベントクラスがあり、ロケーションにリンクしています。 両方のクラスは%JSON.Adaptorから継承されています。 このため、オブジェクトグラフを作成し、それをJSON文字列として直接エクスポートすることができます。 USER>set event = ##class(Model.Event).%New()   USER>set event.Name = "Global Summit"   USER>set location = ##class(Model.Location).%New()   USER>set location.City = "Boston"   USER>set location.Country = "United States of America"   USER>set event.Location = location   USER>do event.%JSONExport() {"Name":"Global Summit","Location":{"City":"Boston","Country":"United States of America"}} もちろん、%JSONImport()を使用して、逆の方向にエクスポートすることもできます。 USER>set jsonEvent = {"Name":"Global Summit","Location":{"City":"Boston","Country":"United States of America"}}   USER>set event = ##class(Model.Event).%New()   USER>do event.%JSONImport(jsonEvent)   USER>write event.Name Global Summit USER>write event.Location.City Boston インポートメソッドとエクスポートメソッドは、任意のネストされた構造で機能します。 %XML.Adaptorと同様に、対応するパラメーターを設定して、個々のプロパティのマッピングロジックを指定できます。 Model.Eventクラスを次の定義に変更してみましょう。 Class Model.Event Extends (%Persistent, %JSON.Adaptor) {  Property Name As %String(%JSONFIELDNAME = "eventName");  Property Location As Model.Location(%JSONINCLUDE = "INPUTONLY"); } 上記の例とオブジェクト構造が変数eventに割り当てられていると仮定した場合、%JSONExport()を呼び出すと、次の結果が返されます。 USER>do event.%JSONExport() {"eventName":"Global Summit"} Nameプロパティは、eventNameフィールド名にマッピングされ、Locationプロパティは %JSONExport()呼び出しから除外されますが、存在する場合は%JSONImport()呼び出し中にJSONコンテンツに入力されます。 マッピングを調整するには、次のようなパラメーターを使用できます。 * %JSONFIELDNAME: JSONコンテンツのフィールド名に対応します。 * %JSONIGNORENULL: 開発者が文字列プロパティの空の文字列のデフォルト処理をオーバーライドできるようにします。 * %JSONINCLUDE : このプロパティがJSON出力/入力に含まれるかどうかを制御します。 * %JSONNULL: これがtrue(=1)である場合、未指定のプロパティはnull値としてエクスポートされます。 そうでない場合は、プロパティに対応するフィールドはエクスポート中に省略されます。 * %JSONREFERENCE: オブジェクト参照の処理方法を指定します。 デフォルトは「OBJECT」で、参照先クラスのプロパティが参照先オブジェクトを表すために使用されることを示します。 その他のオプションは「ID」、「OID」、および「GUID」です。 これにより高度な制御が可能になり、非常に便利です。 オブジェクトを手動でJSONにマッピングする時代は終わりました。 ## あともう一つ マッピングパラメーターをプロパティレベルで設定する代わりに、XDataブロックにJSONマッピングを定義することも可能です。 次に示すOnlyLowercaseTopLevelという名前のXDataブロックには、上記のeventクラスと同じ設定が行われています。 Class Model.Event Extends (%Persistent, %JSON.Adaptor) {  Property Name As %String;  Property Location As Model.Location;  XData OnlyLowercaseTopLevel  {  <Mapping xmlns="http://www.intersystems.com/jsonmapping">   <Property Name="Name" FieldName="eventName"/>   <Property Name="Location" Include="INPUTONLY"/>  </Mapping>  } } 重要な違いが1つあります。それは、XDataブロックのJSONマッピングはデフォルトの動作を変更しないが、対応する%JSONImport()と%JSONExport()の呼び出しで最後の引数として参照する必要があるということです。例を示します。 USER>do event.%JSONExport("OnlyLowercaseTopLevel") {"eventName":"Global Summit"} 指定された名前のXDataブロックが存在しない場合、デフォルトのマッピングが使用されます。 このアプローチを使用すると、複数のマッピングを構成し、各呼び出しに必要なマッピングを個別に参照することができます。そのため、マッピングをより柔軟で再利用可能にしながら、より高い制御性を得ることができます。 これらの機能強化によって作業が楽になることを願っています。皆さんからのフィードバックを楽しみしています。 ぜひコメントを残してください。
記事
Toshihiko Minamoto · 2021年9月28日

DSTIMEでデータを同期する

Cachéでのデータ同期については、オブジェクトとテーブルを同期させるさまざまな方法があります。 データベースレベルでは、[シャドーイング](https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCDI_shadow)または[ミラーリング](https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror)を使用できます。  これは非常によく機能し、データの一部分だけを同期する必要がある場合には、 [グローバルマッピング](https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GORIENT_devtasks_mappings_global)を使用してより小さなピースにデータを分割することができます。 または、クラス/テーブルレベルで双方向の同期が必要な場合には、[オブジェクト同期機能](https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GOBJ_objsync)を使用することができます。 これらすべての優れた機能には次のような制限があります。Caché/IRISからCaché/IRISにしか機能しません。 外部のデータベースにデータを同期する必要がある場合には、ほかのソリューションが必要です。 このソリューションは、かなり以前からCaché/IRISに提供されており、非常に良く機能します。 [^OBJ.DSTIME](https://docs.intersystems.com/ens20181j/csp/docbook/DocBook.UI.Page.cls?KEY=D2IMP_ch_current#D2IMP_current_cube_sync_and_mirroring)で解決です。 これは、Deep Seeとのデータ同期を可能にするために構築されました。 Modified,New,Deleted をシグナルすることで、オブジェクト/テーブルの変更に関する非常に単純なジャーナルを維持します。 これはDeep Seeだけでなく、あらゆる種類のデータ同期でも有用です。 グローバル ^OBJ.DSTIMEにはこのほかに2つの機能があります。 * 永続クラスの[%SYSTEM.DSTIME](https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.DSTIME)にラップされているため、通常のSQLテーブルとして変更を選択するために使用することもできます。   * 同期されたジャンクの制御を可能にするバージョンID(DSTIMEという名前)を維持します。            - 最後のバージョンをフェッチする            - バージョンを増加する            - フェッチされたバージョンに基づいて、必要な場所に変更をアップロードする 純粋なSQLで同期を行う場合、SQLを理解するあらゆるデータベースをターゲットとすることができます。 クラス %SYSTEM.DSTIME を拡張して、[こちらに例](https://github.com/rcemper/Sync-Data-with-DSTIME-ZPM)を置きました。SAMPLESで試すことができます。   これはCaché 2018.1.3とIRIS 2020.2で機能するコーディングの例です。 新しいバージョンと同期されません。 また、InterSystemsのサポートによるサービスはありません!  
記事
Megumi Kakechi · 2021年12月9日

管理ポータル/スタジオ/ターミナルにパスワード認証を設定する方法

これは、InterSystems FAQサイトの記事です。 【 管理ポータルへのパスワード認証設定方法 】 管理ポータルの、 [ホーム] > [システム管理] > [セキュリティ] > [アプリケーション] > [ウェブ・アプリケーション]で /csp/sys、および、/csp/sys/ 以下の各アプリケーション(/csp/sys/expなど) の編集画面を開き、“許可された認証方法” の、“パスワード” をチェックして保存します。 また、これと同様のことを、ターミナルから、^SECURITYルーチンを使用して実行することも可能です。以下は/csp/sys/アプリケーションに対する実行例です。*実行は%SYSネームスペースで行って下さい。* ※入力箇所は青字で記載%SYS>do ^SECURITY 1) User setup2) Role setup3) Service setup4) Resource setup5) Application setup6) Auditing setup8) SSL configuration setup9) Mobile phone service provider setup10) OpenAM Identity Services setup11) Encryption key setup12) System parameter setup13) X509 User setup14) KMIP server setup15) Exit Option? 5 1) Web applications2) Doc DB applications3) Privileged Routine applications4) Client applications5) Exit Option? 1 1) Create application2) Edit application3) List applications4) Detailed list applications5) Delete application6) Export applications7) Import applications8) Exit Option? 2 Application to edit? /csp/sys // ? 入力でアプリケーション一覧を表示Description? システム管理ポータル =>Enabled? Yes => Yes // そのままEnterCSP/ZEN Enabled? Yes => YesBusiness Intelligence Enabled? No => NoiKnow Enabled? No => NoInbound Web Services Enabled? Yes => YesRequire resource to use application? No => NoRole to always add to application?New match role to add to application?Do you want to go back and re-edit any match roles or target roles? No => NoAllow 認証なし access? Yes => No // 認証なしログインを不可設定Allow パスワード authentication? No => Yes // パスワードログインのみ可能:(このあと、メニューが終わるまでデフォルトのままEnter) ※ 認証方法を変更後、アクセスに必要なユーザ名/パスワードが分からない等でターミナルやシステム管理ポータルにアクセスできなくなる危険性を回避するため、変更を元に戻すためのターミナルやシステム管理ポータルを予め開いておくことをお奨めします。 【スタジオ・ターミナルへのパスワード認証設定】 管理ポータルの、  [ホーム] > [システム管理] > [セキュリティ] > [サービス]で、以下のサービスのリンクを押下して、定義編集画面を開き、“許可する認証の有効/無効:”の、“パスワード”をチェックします。  スタジオ : %Service_Bindings  ターミナル : %Service_Console また、これと同様のことを、ターミナルから、^SECURITYルーチンを使用して実行することも可能です。実行手順は、前述の管理ポータルの場合と同様です。最初のオプション選択で、3) Service setup を選択し、設定して下さい。 ^SECURITYルーチンについては、以下ドキュメントをご参照ください。SECURITYルーチンについて【IRIS】
記事
Mihoko Iijima · 2022年3月29日

SSL/TLS を使用しているメールサーバへメールを送信するコードサンプルご紹介

これは、InterSystems FAQサイトの記事です。 メール送付のコードを記述する前に、管理ポータルで SSL/TLS 構成を作成します。 管理ポータル > システム管理 > セキュリティ > SSL/TLS 構成 メール送付までの流れは以下の通りです。 メールメッセージ用クラス:%Net.MailMessage のインスタンスを作成し、送信元メールアドレス、宛先メールアドレス、件名、本文を設定します。 認証情報設定用クラス:%Net.Authenticator のインスタンスを作成し、メール送付時に使用する認証情報を設定します。 SMTP用クラス:%Net.SMTP のインスタンスを作成し、SMTP サーバの設定、管理ポータルで作成した SSL/TLS 構成名の指定、2で作成した認証情報と 1で作成したメールメッセージを使用して、メールを送信します。 ターミナルからの実行例は以下の通りです(Gmail を利用しています)。 《メモ》現在(2022年5月30日以降)、Gmailを利用したメール送付を行う場合 OAuth2.0 の利用が必須となりました。Gmailを利用する場合の手順ついては、「OAuth 2.0 を利用して IRIS から Gmail を送信する」をご参照ください。 《そのほかの注意》Gmailを利用する場合、Gmailアカウントに対して「安全性の低いアプリのアクセス」を有効にする必要があります。また、GmailのSMTPを経由してメール送信するため、Gmailアカウントに対して「アプリ パスワード」を設定する必要があります。 詳細は、Googleアカウントのヘルプページ「アプリ パスワードでログインする」ご参照ください。 // メールメッセージ用インスタンスを準備します set message=##class(%Net.MailMessage).%New() set message.From = "<送信元メールアドレス>" do message.To.Insert("<宛先メールアドレス>") // メールの件名を設定 set message.Subject = "これはテストメールです" // メール本文の設定(末尾に改行を入れるメソッド) do message.TextData.WriteLine("本日は快晴でした") // メール本文の設定(指定した文字のみを出力するメソッド) do message.TextData.Write("明日も晴れるといいです。") // 認証情報設定 set auth = ##class(%Net.Authenticator).%New() set auth.UserName = "<ユーザID>" set auth.Password = "<パスワード>" // SSLを使用する場合(Gmailの例) set mbox = ##class(%Net.SMTP).%New() set mbox.smtpserver = "smtp.gmail.com" set mbox.port="465" // TLS の場合は "587" // SSL 構成のセットアップ set mbox.authenticator = auth set mbox.AuthFrom = auth.UserName set mbox.SSLConfiguration = "GMAILSSL" // SSL 構成名(管理ポータルで設定) set mbox.UseSTARTTLS=0 // (0は既定)TLSの場合は 1 // メール送信 set status = mbox.Send(message) write $system.OBJ.DisplayError(status) // メモ: エラーメッセージだけを文字で取得する場合は以下実行します write $system.Status.GetErrorText(status)
お知らせ
Masahito Miura · 2023年3月28日

Caché と Ensemble のメンテナンス・リリース

インターシステムズは、新旧すべての製品について、お客様に高品質な製品サポートを提供することをお約束します。Caché はリリースされて 25 年になります。製品リリースを重ねるに応じてそのサポートは進展されていきます。 2018 年にリリースされた InterSystems IRIS は、Caché と Ensemble の後継製品です。 多くの Caché/Ensemble のお客様が IRIS に移行しているか、今後数年のうちに移行する予定です。 Caché や Ensemble を使い続けているお客様は、以下の重要なお知らせにご留意ください: Caché/Ensemble のメンテナンス・リリースは、今後 4 年間 (2027 年の第 1 四半期まで) 継続されます。 2027 年 3 月 31 日以降は、Caché/Ensemble のメンテナンス・リリースの予定はありません。 これらのメンテナンス・リリースは、重要な欠陥、セキュリティの脆弱性および Windows と Red Hat Enterprise Linux (RHEL) の新しいバージョンについて、弊社の判断で対応します。 その他のプラットフォームについては、メンテナン ス・リリースを予定していません。(Caché/Ensemble の使用を継続されているお客様の大半は Windows または RedHat を使用されています) 弊社の計画では、12 か月ごとにメンテナンス・リリースを提供し、必要に応じて追加のセキュリティ・リリースを提 供する予定です。(製品リリースの新しい頻度についてについてを参照) Caché や Ensemble の製品サポートの終了日を発表する予定はありません。 インターシステムズは、既存のサポート契約またはサブスクリプション契約に基づき、他のすべての製品と同じ対応レベルでサポートを提供し続けます。 また、従来からのポリシーと同様に、すべての Caché および Ensemble プラットフォームでアドホック修正がベストエフォートで提供される予定です。製品の問題修正 (新機能ではない) は、リスクや複雑性に基づいて、弊社の判断で既存の Caché/Ensemble リリースにバックポートされる予定です。 弊社は、常にお客様のお役に立てるよう最善を尽くすことを目指しております。 ご質問がある場合は、John.Paladino@InterSystems.com (クライアント・サービス担当副社長) またはセールス・アカウント・マネージャにご連絡ください。
記事
Mihoko Iijima · 2023年6月29日

CSVファイルをテーブルにインポートする方法(LOAD DATA編)

これは InterSystems FAQ サイトの記事です。 LOAD DATAは、バージョン2022.1から追加されたSQLコマンドで、CSVファイルやJDBCソースからデータをテーブルにロードするコマンドです。データが存在するテーブルにLOAD DATAを実行した場合、データは追記されます。 ※ バージョン2022.1をご利用いただく場合は、バージョン2022.1.3 をご利用ください。(2022.1.0~2022.1.2は、使用するJARファイルの不備により動作しません。) LOAD DATAを利用する際、Javaの外部サーバ(Javaゲートウェイ)を使用するため、IRISをインストールした環境にJavaのインストールが必要です。サポート対象のJavaバージョンについては、ドキュメントの「サポート対象Javaテクノロジ」をご参照ください。 LOAD DATAを利用するためには、Javaインストール済、かつ外部言語サーバで %Java_Server 設定済の環境である必要があります。 ※ 環境変数JAVA_HOMEの設定がある場合は以下 %Java_Serverの設定は不要です。 %Java_Server 設定詳細は以下の通りです。 Javaホームディレクトリ:インストールしたJavaのホームディレクトリを指定します。 利用手順は以下の通りです。 以下のCSVを読み込む場合の手順を説明します。 ProductID,ProductName,PriceP0101,貼るホッカイロミニ(10個),300P0102,貼るホッカイロ(10個),460P0103,貼るホッカイロ足裏(30個),1000 1) テーブル定義の作成 ロードしたいCSVのセルの並びに合わせてテーブル定義を作成します。 CREATE TABLE Test.Product( ProductID VARCHAR(10) PRIMARY KEY, ProductName VARCHAR(50), Price INTEGER ) 2) LOAD DATAを実行する ヘッダ付きのUTF-8で保存したCSVファイルを利用する場合のコマンド指定方法は以下の通りです。 LOAD DATA FROM FILE 'ファイルフルパス' INTO スキーマ名.テーブル名 USING {"from":{"file":{"charset":"UTF-8","header":true}}} 実際の実行例は以下の通りです。 LOAD DATA FROM FILE 'c:\temp\test.csv' INTO Test.Product USING {"from":{"file":{"charset":"UTF-8","header":true}}} ※LOAD DATAの実行にはJavaの外部サーバを利用しています。初回実行時、外部サーバを開始するため少し時間がかかります。 3) 実行結果を確認する ロードに失敗してスキップされたレコード数を確認する場合 select * from %SQL_Diag.Result ロードに失敗した各レコードに関する詳細情報を参照する場合 select * from %SQL_Diag.Message %SQL_Diag.Message テーブルには、%SQL_Diag.Result の外部キーが含まれているため、以下のように2つのテーブルを組み合わせてエラー詳細を確認することもできます。 SELECT * FROM %SQL_Diag.Message JOIN %SQL_Diag.Result ON %SQL_Diag.Result.resultId = %SQL_Diag.Message.diagResult where %SQL_Diag.Result.resultId=1 LOAD DATAについて詳細は、ドキュメント「LOAD DATA」もご参照ください。
記事
Toshihiko Minamoto · 2021年2月8日

Caché データベースブロックの内部構造、パート 3。

これで 3 記事目になります ([パート 1](https://jp.community.intersystems.com/node/485976) と [パート 2](https://jp.community.intersystems.com/node/486191) をご覧ください) が、引き続き Caché データベースの内部構造をご紹介いたします。 今回は、興味深い内容をいくつかご紹介し、私の [Caché Blocks Explorer](https://github.com/daimor/CacheBlocksExplorer/) プロジェクトを使って作業の生産性をアップさせる方法について説明します。 [![](https://habrastorage.org/files/fad/ba3/2c3/fadba32c335a4ebeb26354cf1fc19838.png)](https://habrastorage.org/files/fad/ba3/2c3/fadba32c335a4ebeb26354cf1fc19838.png) この画像に表示されているものに見覚えがあるという方はたくさんおられると思います (クリック可能)。 グローバルの断片化した状態を視覚化する必要があったとき、様々なディスクデフラグツールが私の頭をよぎりました。 なんとか、そういったツールと同等の効果を発揮する製品を開発できたと願っています。 このユーティリティには複数のブロックで構成されたマップが表示されています。 四角はそれぞれブロックを表し、その色はレジェンドセクションに表示されている特定のグローバルに対応しています。 また、ブロックそのものは格納されているデータの量を示しているため、マップにさっと目を通すだけで、データベース全体の使用量を素早く推定できます。 グローバルレベルのブロックとマップレベルのブロックは未だ実装されていませんので、空のブロック同様に白のブロックとして表示されます。 データベースを選択すれば、すぐにブロックのマップが読み込みを開始します。 情報はそれぞれ順番にではなく、ブロックツリーに並ぶブロックの順番に従って読み込まれるため、そのプロセスは以下の画像のようになる可能性があります。 [![](https://habrastorage.org/files/53c/5de/696/53c5de696d3448398b62b19c600e7a47.gif)](https://habrastorage.org/files/53c/5de/696/53c5de696d3448398b62b19c600e7a47.gif) データベースは、前回の[記事](https://jp.community.intersystems.com/node/486191)で使用したものを引き続き使用しましょう。 グローバルは必要ありませんので、すべて削除しています。 また、SAMPLES データベースの Sample クラスパッケージを基に新しいデータを生成しました。 そのために、HABR と名付けた私のネームスペースへのパッケージマッピングを設定しています。 [![](https://habrastorage.org/files/c31/6a0/a7a/c316a0a7ac2444058536616554541da5.png)](https://habrastorage.org/files/c31/6a0/a7a/c316a0a7ac2444058536616554541da5.png) 以下のようにデータ生成コマンドを実行しました。 do ##class(Sample.Utils).Generate(20000) マップには以下の結果が表示されました。 [![](https://habrastorage.org/files/003/26f/cdd/00326fcdd5094c85af50f58c8ceccc63.png)](https://habrastorage.org/files/003/26f/cdd/00326fcdd5094c85af50f58c8ceccc63.png) ブロックが埋まり始める場所はファイルの先頭でないことにお気づきでしょうか。 ブロック 16 からトップレベルのポインタブロックが、そしてブロック 50 からデータブロックが始まります。 デフォルト値は 16 と 50 ですが、必要に応じて変更できます。 ポインタブロックの先頭は [SYS.Database](http://docs.intersystems.com/latestj/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=SYS.Database) クラスの_NewGlobalPointerBlock_ プロパティで定義されます。これにより、新しいグローバルのデフォルト値が設定されます。 既存のグローバルの場合は、_PointerBlock_ プロパティを使って[%Library.GlobalEdit](http://docs.intersystems.com/latestj/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%Library.GlobalEdit) クラスの中で変更できます。 一連のデータブロックを開始するブロックは、[SYS.Database](docs.intersystems.com/latestj/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=SYS.Database) クラスの_NewGlobalGrowthBlock_ プロパティに指定されます。 個別のグローバルの場合は、[%Library.GlobalEdit](http://docs.intersystems.com/latestj/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%Library.GlobalEdit) クラスの _GrowthBlock_ プロパティを使っても同じことができます。 こういったプロパティの変更は、トップポインタブロックやデータブロックの現在の位置には一切影響しないため、まだデータを格納していないグローバルに対してだけ行うことが理に適っていると言えます。  ここで、989 個のブロックを持つ **^Sample.PersonD** グローバルは 83% 埋まっており、それに次いで 573 個のブロックを持つ **^Sample.PersonI** グローバルは 70% 埋まっていることが分かります。 どのグローバルを選択しても、それに割り当てられたブロックを確認できます。 ^Sample.PersonI グローバルを選択すると、ほぼ空になっているブロックがいくつかあるのが分かります。 また、これら 2 つのグローバルに属するブロックが混合していることも分かります。 実はこれには理由があります。 新しいオブジェクトが作成されると、これら 2 つのグローバルは、片方はデータで、もう片方は Sample.Person テーブルのインデックスで埋まってしまうのです。 [![](https://habrastorage.org/files/f46/414/e0b/f46414e0b42c4be8a39be1c23e1dda65.png)](https://habrastorage.org/files/f46/414/e0b/f46414e0b42c4be8a39be1c23e1dda65.png) テストデータがいくつか手に入ったところで、Caché が提供するデータベース管理機能を活用して結果を確認することができます。 まずは、データを少し取り除いて、いかにもデータを追加したり、削除したりするアクティビティが実行されているかのように見せます。 ランダムなデータをいくつか削除するコードを実行します。     set id=""     set first=$order(^Sample.PersonD(""),1)     set last=$order(^Sample.PersonD(""),-1)     for id=first:$random(5)+1:last {         do ##class(Sample.Person).%DeleteId(id)     } このコードを実行すると、以下の結果が表示されます。 空のブロックがいくつかある一方で、64~67% 埋まっているブロックもあります。   [![](https://habrastorage.org/files/cbb/bbf/db3/cbbbbfdb32454d2b955a4573ac4d34d2.png)](https://habrastorage.org/files/cbb/bbf/db3/cbbbbfdb32454d2b955a4573ac4d34d2.png) このデータベースは、%SYS ネームスペースから ^[DATABASE](http://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_chui-mgmt#GCAS_chui-mgmt_database) ツールを使って操作することができます。 では、その機能をいくつか使ってみましょう。 [![](https://habrastorage.org/files/79f/4d4/9ba/79f4d49ba79a4a41a7c1d470d98f94fa.png)](https://habrastorage.org/files/79f/4d4/9ba/79f4d49ba79a4a41a7c1d470d98f94fa.png) まずは、ブロックがほとんど埋まっていないので、データベース内のすべてのグローバルを圧縮したらどうなるか試してみましょう。 [![](https://habrastorage.org/files/987/afe/6d0/987afe6d0a494b5b9f338d0126e94999.png)](https://habrastorage.org/files/987/afe/6d0/987afe6d0a494b5b9f338d0126e94999.png) [![](https://habrastorage.org/files/e6e/35f/f22/e6e35ff2286142a39b1fb0e71b06c90c.png)](https://habrastorage.org/files/e6e/35f/f22/e6e35ff2286142a39b1fb0e71b06c90c.png) ご覧のとおり、圧縮したことで、占有率を必要な 90% の値に限りなく近づけることができました。 この結果、空であったブロックは他のブロックから移動されてきたデータでいっぱいになりました。 データベースのグローバルは、^[DATABASE](http://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_chui-mgmt#GCAS_chui-mgmt_database) ツール (アイテム 7) を使うか、以下のコマンドにデータベースへのパスを 1 つ目のパラメーターとして渡して実行すれば圧縮できます。 do ##class(SYS.Database).CompactDatabase("c:\intersystems\ensemble\mgr\habr\") また、すべての空のブロックをデータベースの最後に移動することもできます。 これは、例えば、膨大なデータを削除したからデータベースを圧縮したいというときに必要となるかもしれません。 このデモとして、私たちのテスト用データベースからデータを削除する作業をもう一度実行してみましょう。     set gn=$name(^Sample.PersonD)     set first=$order(@gn@(""),1)     set last=$order(@gn@(""),-1)     for i=1:1:10 {         set id=$random(last)+first         write !,id         set count=0         for {             set id=$order(@gn@(id))             quit:id=""             do ##class(Sample.Person).%DeleteId(id)             quit:$increment(count)>1000         }     } 以下はデータを削除した結果です。 [![](https://habrastorage.org/files/610/a8e/0b7/610a8e0b7f514b75b002a63c5f2703f0.png)](https://habrastorage.org/files/610/a8e/0b7/610a8e0b7f514b75b002a63c5f2703f0.png) 空のブロックがいくつかあるのが分かります。 Caché では、こういった空のブロックをデータベースファイルの末尾に移動して圧縮することができます。 空のブロックを移動するには、システムネームスペースにある [SYS.Database](http://docs.intersystems.com/latestj/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=SYS.Database) クラスの FileCompact メソッドを使うか、^[DATABASE](http://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_chui-mgmt#GCAS_chui-mgmt_database) ツール (アイテム 13) を利用しましょう。 このメソッドには、データベースへのパス、ファイルの末尾の理想的な空きスケース (デフォルトは 0)、戻り値パラメーター (最終的な空きスペース) の 3 つのパラメーターを渡すことができます。 do ##class(SYS.Database).FileCompact("c:\intersystems\ensemble\mgr\habr\",999) 結果、空のブロックがなくなりました。 先頭のブロックは、設定通りに (上位のトップレベルポインタとデータブロックを開始する位置として) 置かれているだけなので、考慮しません。 [![](https://habrastorage.org/files/a01/284/6f9/a012846f9cfa4c019df2524a768450cc.png)](https://habrastorage.org/files/a01/284/6f9/a012846f9cfa4c019df2524a768450cc.png) ### デフラグ (最適化) これでグローバルを最適化する作業に取りかかれます。 このプロセスを実行すると、各グローバルのブロックの順番が並び替えられます。 デフラグを実行するには、データベースファイルの最後に、ある程度の空きスペースが必要になる場合があり、それが必要な状況ではスペースが追加される可能性があります。 このプロセスは、^[DATABASE](http://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_chui-mgmt#GCAS_chui-mgmt_database) ツールのアイテム 14 から始めるか、以下のコマンドを実行して始めることができます。 d ##class(SYS.Database).Defragment("c:\intersystems\ensemble\mgr\habr\") [![](https://habrastorage.org/files/a38/65d/87b/a3865d87b0cd47d6ab9d06ff5d150630.png)](https://habrastorage.org/files/a38/65d/87b/a3865d87b0cd47d6ab9d06ff5d150630.png) ### 空きスペースを増やす グローバルがきちんと並べられているのはいいのですが、 どうやら、デフラグによってデータベースファイルのスペースが余分に使われてしまったようです。 このスペースを開放するには、^[DATABASE](http://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_chui-mgmt#GCAS_chui-mgmt_database) ツールのアイテム 12 を使用するか、以下のコマンドを実行します。 d ##class(SYS.Database).ReturnUnusedSpace("c:\intersystems\ensemble\mgr\habr\") [![](https://habrastorage.org/files/4f7/4b4/efe/4f74b4efed0142759fb5f0b7820a777e.png)](https://habrastorage.org/files/4f7/4b4/efe/4f74b4efed0142759fb5f0b7820a777e.png) データベースの占有スペースは大幅に減りましたが、データベースファイルには空きスケースが 1 MB しか残っていません。 ブロックを移動し、空きスペースを作ることによってデータベース内のグローバルをデフラグし、空きスペースを管理するという可能性が発表されたのはつい最近の話です。 それまでは、データベースをデフラグしてデータベースファイルのサイズを縮小する必要があったときは、^[GBLOCKCOPY](http://docs.intersystems.com/latestj/csp/docbook/DocBook.UI.Page.cls?KEY=GSTU_remote#GSTU_remote_gblockcopy) ツールを使う必要がありました。 このツールは、ソースデータベースの各ブロックを新しく作成されたデータベースに 1 つずつコピーするもので、ユーザーはコピーするグローバルを選択できました。 このツールは現在も使用可能です。
記事
Toshihiko Minamoto · 2021年6月21日

SYSLOG - その正体と意味するもの

この記事では、syslogテーブルについて説明したいと思います。  syslogとは何か、どのように確認するのか、実際のエントリはどのようなものか、そしてなぜそれが重要であるのかについて説明します。  syslogテーブルには、重要な診断情報が含まれることがあります。  システムに何らかの問題が生じている場合に、このテーブルの確認方法とどのような情報が含まれているのかを理解しておくことが重要です。 syslogテーブルとは? Cachéは、共有メモリのごく一部を使って、関心のある項目をログに記録しています。  このテーブルは、次のようなさまざまな名前で呼ばれています。 * Cachéシステムエラーログ * errlog * SYSLOG * syslogテーブル この記事では、単に「syslogテーブル」と呼ぶことにします。 syslogテーブルのサイズは構成可能です。  デフォルトは500エントリで、  10~10,000エントリの範囲で構成できます。  syslogテーブルのサイズを変更するには、システム管理ポータル -> システム管理 -> 構成 -> 追加設定 -> 詳細メモリに移動し、「errlog」行の「編集」を選択します。  そこにsyslogテーブルに必要なエントリの数を入力してください。 **syslogテーブルのサイズを変更する理由は?** syslogテーブルが500エントリに構成されていると、501番目のエントリによって最初のエントリが上書きされ、そのエントリの情報は失われてしまいます。  これはメモリ内にあるテーブルであるため、出力を明示的に保存しない限りどこにも永続されません。  また、Cachéを停止した場合には、以下に説明する方法でエントリをconsole.logファイルに保存するように構成していない限り、すべてのエントリは失われてしまいます。 Cachéが多くのエントリをsyslogテーブルに書き込んでおり、問題の診断目的でそのエントリを確認する場合、テーブルのサイズが十分に大きくなければ、エントリは失われてしまいます。  syslogテーブルの**Date/Time**列を見ると、テーブルが書き込まれた期間を判別できます。  その上で、どれくらいのエントリ数を設定するのかを決めることができます。  個人的には、エントリを失わない数で判断を誤る方が良いと思います。  このことについては、以下でより詳しく説明します。 **syslogテーブルの確認方法** syslogテーブルの確認方法にはいくつかあります。 1. Cachéターミナルプロンプトから、%SYSネームスペースで「Do ^SYSLOG」を実行する。 2. Cachéターミナルプロンプトから、%SYSネームスペースで「do ^Buttons」を実行する。 3. 管理ポータル -> システム操作 -> 診断レポートに移動する。 4. -e1オプションでcstatを実行する。 5. Cachehungを実行する。 6. シャットダウン中にsyslogテーブルをconsole.logファイルに書き込むようにCaché を設定し、console.logファイルを確認する。  設定するには、システム管理ポータル -> 構成 -> 追加設定 -> 互換性に移動し、「ShutDownLogErros」行で「編集」を選択します。  Cachéのシャッドダウン中にsyslogのコンテンツをconsole.logに保存する場合は「true」、保存しない場合は「false」を選択します。 **syslogのエントリとは?** syslogテーブルの例を以下に示します。  この例は、Cachéターミナルプロンプトで「^SYSLOG」を実行して得られたものです。 %SYS>d ^SYSLOG Device: Right margin: 80 => Show detail? No => No Cache System Error Log printed on Nov 02 2016 at 4:29 PM -------------------------------------------------------- Printing the last 8 entries out of 8 total occurrences. Err Process Date/Time Mod Line Routine Namespace 9 41681038 11/02/2016 04:44:51PM 93 5690 systest+3^systest %SYS 9 41681038 11/02/2016 04:43:34PM 93 5690 systest+3^systest %SYS 9 41681038 11/02/2016 04:42:06PM 93 5690 systest+3^systest %SYS 9 41681038 11/02/2016 04:41:21PM 93 5690 systest+3^systest %SYS 9 41681038 11/02/2016 04:39:29PM 93 5690 systest+3^systest %SYS 9 41681036 11/02/2016 04:38:26PM 93 5690 systest+3^systest %SYS 9 41681036 11/02/2016 04:36:57PM 93 5690 systest+3^systest %SYS 9 41681036 11/02/2016 04:29:45PM 93 5690 systest+3^systest %SYS 列の見出しを見ればエントリの各項目が何であるかは明確のようですが、それらについて説明します。 **Printing the last 8 entries out of 8 total occurrences** これは、syslogテーブルエントリの一部ではありませんが、確認することは重要なので、ここで説明します。  この行から、いくつのエントリがsyslogテーブルに書き込まれたかがわかります。  この例では、Cachéが起動してから8つのエントリしか書き込まれていません。  エントリ数が少ないのは、これが私のテストシステムであるためです。  「Printing the last 500 entries out of 11,531 total occurrences(発生総数11,531件中最新の500件のエントリを出力)」と書かれていれば、多数のエントリが見逃されていることがわかります。  見逃されたエントリを確認する場合は、テーブルサイズは最大10,000に増やすか、より頻繁にSYSLOGを実行してください。  **Err** これは、関心のあるイベントについてログに記録される情報です。  エラーは必ずOSレベルで/usr/include/errno.h(Unix)から発生しているものと思われがちですが、実際には「必ず」ではなく、ほとんどの場合です。  ログに記録できるものであれば、何でも記録されます。  たとえば、診断アドホックのデバッグ情報、C変数の値、エラーコードの定義(10000を超えるもの)などを記録することができます。  どのように区別すればよいのでしょうか。  実際に、エントリの**Mod**と**Line**に示される、Cコードの行を確認する必要があります。  つまり、InterSystemsに連絡を取らなければ、それが実際に何であるかを区別することはできません。  では、わざわざ確認する必要はあるのでしょうか。  **err**の意味を正確に知らずとも、ほかの情報を見ることで把握できることがたくさんあるからです。  エントリがたくさんある場合や、普段から目にするエントリとは異なるエントリがある場合には、InterSystemsに問い合わせることもできます。  syslogテーブルのエントリは、必ずしもエラー状態を示すものではないことに注意してください。 **Process** これは、syslogテーブルにエントリを書き込んだプロセスのプロセスIDです。  たとえば、スタックしたプロセス、スピンし続けるプロセス、またはデッドプロセスがある場合、syslogテーブルに何か記録されていないかを確認できます。  記録されていれば、プロセスで障害が起きた理由の重要な手がかりになるかもしれません。 **Date/Time** これは、エントリが書き込まれた日時です。  問題になった原因の手がかりを得るために、エントリの日時とシステムイベントの日時を相関することは非常に重要です。 **ModとLine** Modは特定のCファイルに対応しており、Lineは、そのエントリをsyslogテーブルに書き込んだファイルの行番号です。  カーネルコードにアクセスできるInterSystemsの従業員のみが、これを検索できます。  このコードを調べるだけで、エントリに何が記録されたのかを正確に知ることができます。 **Routine** syslogテーブルにエントリが書き込まれたときにプロセスが実行していたタグ、オフセット、およびルーチンです。  何が起こっているのかを理解する上で非常に役立ちます。 ##### **Namespace** これは、プロセスが実行していたネームスペースです。 **では、err 9がsyslogテーブルに書き込まれた理由をどのようにして知ることができますか?** まず、示されているルーチンを確認します。  私の^systestルーチンは次のようになっています。 systest ;test for syslog post s file="/home/testfile" o file:10 u file w "hello world" c file q syslogエントリでは、エントリが書き込まれた時に実行していたものはsystest+3だったと示されています。  この行は次のようになっています。 u file w "hello world" プロセスがファイルに書き込もうとしていたため、これは実際にOSレベルのエラーである可能性があります。そこで、/usr/include/errno.hで9を探すと、次のようになっています。 #define EBADF 9 /* Bad file descriptor */ 9はファイル関連であり、示されたコードの行はファイルに書き込もうとしていたため、これは実際にOSエラーコードであると推測するのが合理的です。  **何がエラーになっているのかわかりますか?** これを解決するために、まず、/homeディレクトリとtestfileファイルの権限を確認しました。  両方とも777となっていたため、ファイルを開いて書き込むのは可能なはずです。  そこでコードをよく見ると、エラーに気づきました。  10秒のタイムアウトの前にコロンを2つ付けていなかったこと、そしてOpenコマンドにはパラメーターを使用していなかったことに気づいたのです。  以下は更新したルーチンです。実際にエラーなしで終了し、ファイルに書き込みます。 systest ;test for syslog post s file="/scratch1/yrockstr/systest/testfile" o file:"WNSE":10 u file w "hello world" c file q **最後に** syslogテーブルは、正しく使用すればデバッグに役立つ貴重なツールです。  使用するときは、次の点に注意してください。 1. **err**は必ずしもオペレーティングシステムのエラーとは限りません。ログに記録できるものは何でも記録されます。  記録された内容については、InterSystemsにお問い合わせください。 2. ログに記録されているその他の情報を使用して、何が起きているのかを判断します。  エラーと組み合わされたCOSコードの行から、それがOSのエラーであるかどうかについて、合理的に推測することができます。 3. 解決できない問題がある場合は、syslogテーブルを確認しましょう。手がかりが見つかるかもしれません。 4. **Date/Time**、エントリ数、および合計発生数を使って、syslogテーブルのサイズを増やす必要があるかどうかを判断します。 5. システムがsyslogテーブルに何を記録しているのかを把握しておきましょう。エントリに何らかの変更があったり、新しいエントリや異なるエントリが記録されたことに気づくことができます。 6. syslogテーブルのエントリは、必ずしも問題を示すものではありません。
記事
Toshihiko Minamoto · 2020年9月28日

Arduino で気象観測

InterSystems ハッカソンの時、Artem Viznyuk と私のチームは Arduino ボード(1 台)とその各種パーツ(大量)を所有していました。 そのため、私たちは活動方針を決めました。どの Arduino 初心者もそうであるように、気象観測所を作ることにしたのです。 ただし、Caché のデータ永続ストレージと DeepSee による視覚化を利用しました! デバイスの操作 InterSystems の Caché は、次のようなさまざまな物理デバイスと論理デバイスを直接操作することができます。 ターミナル TCP スプーラー プリンター 磁気テープ COM ポート その他多数 Arduino は通信に COM ポートを使用しているため、私たちの準備は万端でした。一般的に、デバイスの操作は次の5つのステップに分けることができます。 OPEN コマンドでデバイスを現在のプロセスに登録し、デバイスにアクセスする。 USE コマンドでデバイスをプライマリにする。 実際の操作を行う。 READ でデバイスからデータを受信し、WRITE でデータを送信する。 もう一度 USE でプライマリデバイスを切り替える。 CLOSE コマンドでデバイスを解放する。 理論上はこうなりますが、実際はどうなのでしょうか? Caché からの点滅操作 まず、私たちはCOM ポートから数値を読み取り、指定したミリ秒の間だけ LED に電力を供給する Arduino デバイスを作りました。 回路: C のコード(Arduino 用) /* Led.ino * COM ポートでデータを受信 * led を ledPin に接続 * // led を接続するピン #define ledpin 8 // 受信したデータバッファ String inString = ""; // 開始時に 1 回だけ実行 void setup() { Serial.begin(9600); pinMode(ledpin, OUTPUT); digitalWrite(ledpin, LOW); } // 無期限に実行 void loop() { // com からデータを取得 while (Serial.available() > ) { int inChar = Serial.read(); if (isDigit(inChar)) { // 同時に 1 文字 // さらにデータバッファに追加 inString += (char)inChar; } // 改行を検出 if (inChar == '\n') { // led の電源をオン digitalWrite(ledpin, HIGH); int time = inString.toInt(); delay(time); digitalWrite(ledpin, LOW); // バッファをフラッシュ inString = ""; } } } 最後に、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) // テストデータを送信 hang 1 write 1000 _ $Char(10) // 1000\n を送信 use old // 古いターミナルに戻る close port // デバイスを解放 } «0801n0» は Com ポートにアクセスするためのパラメーターを含む文字列であり、ドキュメントに記載されています。 また、/BAUD=9600 は言うまでもなく接続速度です。 このメソッドをターミナルで実行する場合、次のようになります。 do ##class(Arduino.Habr).SendSerial() 上記を実行しても何も出力されませんが、LED が 1 秒間点滅します。 データ受信 次はキーパッドを Cache に接続し、入力データを受信します。 これは、 認証委任と ZAUTHENTICATE.mac ルーチンを使用するカスタムユーザー認証として使用できます。 回路: C のコード /* Keypadtest.ino * * Keypad ライブラリを使用します。 * Keypad を rowPins[] と colPins[] で指定されているように * Arduino のピンに接続します。 * */ // リポジトリ: // https://github.com/Chris--A/Keypad #include 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 ); void setup() { Serial.begin(9600); } void loop() { 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 = $io use port read str // 行末文字を検出するまで読み込みます use old close port } catch ex { close port } return str } ターミナルで以下を実行します。 write ##class(Arduino.Habr).ReceiveOneLine() また、(行末文字として送信される)# が押されるまで入力待ち状態になります。その後、入力されたデータがターミナルに表示されます。 以上が Arduino-Caché I/O の基本です。これで、自前の気象観測所を作る準備が整いました。 気象観測所 これで気象観測所に着手できる状態になりました。 私たちはフォトレジスタと DHT11 温湿度センサを使用し、データを収集しました。 回路: C のコード /* Meteo.ino * * 湿度、気温、光の強度を記録して * それらを COM ポートに送信します * 出力例: H=1.0;T=1.0;LL=1; */ // フォトレジスタのピン(アナログ) int lightPin = ; // DHT-11 のピン(デジタル) int DHpin = 8; // DHT-11 の一次データを保存する配列 byte dat[5]; void setup() { Serial.begin(9600); pinMode(DHpin,OUTPUT); }   void loop() { delay(1000); // 1 秒ごとにすべてを測定 int lightLevel = analogRead(lightPin); //輝度レベルを取得 temp_hum(); // 気温と湿度を dat 変数に格納 // And output the result Serial.print("H="); Serial.print(dat[], DEC); Serial.print('.'); Serial.print(dat[1],DEC); Serial.print(";T="); Serial.print(dat[2], DEC); Serial.print('.'); Serial.print(dat[3],DEC); Serial.print(";LL="); Serial.print(lightLevel); Serial.println(";"); } // DHT-11 のデータを dat に格納 void temp_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
記事
Toshihiko Minamoto · 2022年2月14日

統合AIデモサービススタックにML/DLモデルをデプロイする

**キーワード**: IRIS、IntegratedML、Flask、FastAPI、Tensorflow Serving、HAProxy、Docker、Covid-19 ## 目的: 過去数か月に渡り、潜在的なICU入室を予測するための単純なCovid-19 X線画像分類器やCovid-19ラボ結果分類器など、ディープラーニングと機械学習の簡単なデモをいくつか見てきました。  また、ICU分類器のIntegratedMLデモ実装についても見てきました。  「データサイエンス」の旅路はまだ続いていますが、「データエンジニアリング」の観点から、AIサービスデプロイメントを試す時期が来たかもしれません。これまでに見てきたことすべてを、一式のサービスAPIにまとめることはできるでしょうか。  このようなサービススタックを最も単純なアプローチで達成するには、どういった一般的なツール、コンポーネント、およびインフラストラクチャを活用できるでしょうか。   ## 対象範囲 ### **対象:** ジャンプスタートとして、docker-composeを使用して、次のDocker化されたコンポーネントをAWS Ubuntuサーバーにデプロイできます。 * **HAProxy ** - ロードバランサー * **Gunicorn** と **Univorn ** - Webゲートウェイ****サーバー * **Flask** と **FastAPI** - WebアプリケーションUI、サービスAPI定義、およびヒートマップ生成などのアプリケーションサーバー * **Tensorflow Model Serving** と **Tensorflow-GPU Model Serving** - 画像や分類などのアプリケーションバックエンドサーバー * IRIS **IntegratedML** - SQL インターフェースを備えたアプリとデータベースを統合した AutoML * **ベンチマーキング**用クライアントをエミュレートする、**Jupyterノートブック**のPython3 *  Dockerと**docker-compose** * Testla T4 GPU搭載の**AWS Ubuntu** 16.04  注意: GPUを使用したTensorflow Servingはデモのみを目的としています。GPU関連の画像(dockerfile)と構成(docker-compose.yml)は、単純にオフにできます。 ### **対象外**またはウィッシュリスト: * **Nginx **または**Apache**などのWebサーバーは、今のところこのデモでは省略されています。 * **RabbitMQ**とRedis  - IRISまたはEnsembleと置き換えられる、信頼性の高いメッセージングキューブローカー。    * **IAM**([Intersystems API Manger](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=AIAM))または**Kong**はウィッシュリストに含まれます。 * **SAM **(InterSystemsの[システムアラートと監視](https://docs.intersystems.com/sam/csp/docbook/DocBook.UI.Page.cls?KEY=ASAM))  * **Kubernetes** Operator付きの**ICM**([InterSystems Cloud Manager](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=PAGE_DEPLOYMENT_ICM))- 誕生したときからずっとお気に入りの1つです。 * **FHIR**(IntesyStems IRISベースのFHIR R4サーバーとFHIRアプリのSMART用FHIRサンドボックス) * **CI/CD**開発ツールまたは**Github Actions** 「機械学習エンジニア」は、サービスのライフサイクルに沿って本番環境をプロビジョニングする際に、必然的にこれらのすべてのコンポーネントを操作しなければならないでしょう。 今後徐々に、焦点を当てていきたいと思います。   ## GitHubリポジトリ 全ソースコードの場所: また、新しいリポジトリとともに、[integratedML-demo-templateリポジトリ](https://github.com/intersystems-community/integratedml-demo-template)も再利用します。   ## デプロイメントのパターン 以下に、この「DockerでのAIデモ」テストフレームワークの論理的なデプロイパターンを示します。 デモの目的により、意図的にディープラーニング分類とWebレンダリング用に個別のスタックを2つ作成し、HAProxyをソフトロードバランサーとして使用して、受信するAPIリクエストをこれらの2つのスタックでステートレスに分散できるようにしました。 * Guniorn + Flask + Tensorflow Serving * Univcorn + FaskAPI + Tensorflow Serving GPU 前の記事のICU入室予測と同様に、機械学習デモのサンプルには、IntegratedMLを使用したIRISを使用します。 現在のデモでは、本番サービスでは必要または検討される共通コンポーネントをいくつか省略しました。 * Webサーバー: NginxまたはApacheなど。 HAProxyとGunicorn/Uvicornの間で、適切なHTTPセッション処理を行うために必要となります(DoS攻撃を防止するなど)。 * キューマネージャーとDB: RabbitMQやRedisなど。Flask/FastAPIとバックエンドサービングの間で、信頼性のある非同期サービングとデータ/構成の永続性などに使用されます。   * APIゲートウェイ: IAMまたはKongクラスター。単一障害点を作らないように、HAProxyロードバランサーとAPI管理用Webサーバーの間に使用されます。 * 監視とアラート: SAMが適切でしょう。 * CI/CD開発のプロビジョニング: クラウドニューラルデプロイメントと管理、およびその他の一般的な開発ツールによるCI/CDにはK8を使用したICMが必要です。 実際のところ、IRIS自体は、信頼性の高いメッセージングのためのエンタープライズ級のキューマネージャーとしても、高性能データベースとしても確実に使用することができます。 パターン分析では、IRISがRabbitMQ/Redis/MongoDBなどのキューブローカーとデータベースの代わりになることは明らかであるため、レイテンシが大幅に低く、より優れたスループットパフォーマンスとさらに統合する方が良いでしょう。  さらに、IRIS Webゲートウェイ(旧CSPゲートウェイ)は、GunicornやUvicornなどの代わりに配置できますよね?     ## 環境トポロジー 上記の論理パターンを全Dockerコンポーネントに実装するには、いくつかの一般的なオプションがあります。 頭に思い浮かぶものとしては以下のものがあります。   * docker-compose * docker swarmなど * Kubernetesなど  * K8演算を使用したICM このデモは、機能的なPoCといくつかのベンチマーキングを行うために、「docker-compose」から始めます。 もちろん、いつかはK8やICMを使いたいとも考えています。  [docker-compose.yml](https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/docker-compose.yml)ファイルに説明されているように、AWS Ubuntuサーバーでの環境トポロジーの物理的な実装は、以下のようになります。   上図は、全Dockerインスタンスの**サービスポート**が、デモの目的でどのようにマッピングされており、Ubuntuサーバーで直接公開されているかを示したものです。 本番環境では、すべてセキュリティ強化される必要があります。 また、純粋なデモの目的により、すべてのコンテナは同じDockerネットワークに接続されています。本番環境では、外部ルーティング可能と内部ルーティング不可能として分離されます。   ## Docker化コンポーネント  以下は、[docker-compose.yml](https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/docker-compose.yml)ファイルに示されるとおり、ホストマシン内でこれらの**ストレージボリューム**が各コンテナインスタンスにどのようにマウントされているかを示します。  ubuntu@ip-172-31-35-104:/zhong/flask-xray$ tree ./ -L 2 ./ ├── covid19 (Flask+GunicornコンテナとTensorflow Servingコンテナがここにマウントされます) │ ├── app.py (Flaskメインアプリ: WebアプリケーションとAPIサービスインターフェースの両方がここに定義されて実装されます) │ ├── covid19_models (CPU使用の画像分類Tensorflow Servingコンテナ用のTensorflowモデルはここに公開されてバージョン管理されます) │ ├── Dockerfile (Flask サーバーとGunicorn: CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:5000", "--workers", "4", "--threads", "2"]) │ ├── models (.h5形式のFlaskアプリ用モデルとX線画像へのGrad-CAMによるヒートマップ生成のAPIデモ) │ ├── __pycache__ │ ├── README.md │ ├── requirements.txt (全Flask+Gunicornアプリに必要なPythonパッケージ) │ ├── scripts │ ├── static (Web静的ファイル) │ ├── templates (Webレンダリングテンプレート) │ ├── tensorflow_serving (TensorFlow Servingサービスの構成ファイル) │ └── test_images ├── covid-fastapi (FastAPI+UvicornコンテナとGPU使用のTensorflow Servingコンテナはここにマウントされます) │ ├── covid19_models (画像分類用のTensorflow Serving GPUモデルは、ここに公開されてバージョン管理されます) │ ├── Dockerfile (Uvicorn+FastAPIサーバーはここから起動されます: CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4" ]) │ ├── main.py (FastAPIアプリ: WebアプリケーションとAPIサービスインターフェースの両方がここに定義されて実装されます) │ ├── models (.h5形式のFastAPIアプリ用モデルとX線画像へのGrad-CAMによるヒートマップ生成のAPIデモ) │ ├── __pycache__ │ ├── README.md │ ├── requirements.txt │ ├── scripts │ ├── static │ ├── templates │ ├── tensorflow_serving │ └── test_images ├── docker-compose.yml (フルスタックDocker定義ファイル。 Docker GPU「nvidia runtime」用にバージョン2.3を使用。そうでない場合はバージョン3.xを使用可) ├── haproxy (HAProxy dockerサービスはここに定義されます。 注意: バックエンドLBにはスティッキーセッションを定義できます。 ) │ ├── Dockerfile │ └── haproxy.cfg └── notebooks (TensorFlow 2.2とTensorBoardなどを含むJupyterノートブックコンテナサービス) ├── Dockerfile ├── notebooks (機能テスト用に外部APIクライアントアプリをエミュレートするサンプルノートブックファイルとロードバランサーに対してPythonによるAPIベンチマークテストを行うサンプルノートブックファイル) └── requirements.txt 注意: 上記の[docker-compose.yml](https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/docker-compose.yml)はCovid-19 X線画像のディープラーニングデモ用です。  別の[integratedML-demo-template](https://github.com/intersystems-community/integratedml-demo-template)の[docker-compose.yml](https://github.com/intersystems-community/integratedml-demo-template/blob/master/docker-compose.yml)とともに、環境トポロジーに表示されるフルサービススタックを形成するために使用されています。     ## サービスの起動  すべてのコンテナサービスを起動するには、単純な**docker-compose up -d**を使用します。 ubuntu@ip-172-31-35-104:~$ docker psCONTAINER ID        IMAGE                                 COMMAND                  CREATED             STATUS                PORTS                                                                              NAMES31b682b6961d        iris-aa-server:2020.3AA               "/iris-main"             7 weeks ago         Up 2 days (healthy)   2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:8091->51773/tcp, 0.0.0.0:8092->52773/tcp   iml-template-master_irisimlsvr_16a0f22ad3ffc        haproxy:0.0.1                         "/docker-entrypoint.…"   8 weeks ago         Up 2 days             0.0.0.0:8088->8088/tcp                                                             flask-xray_lb_171b5163d8960        ai-service-fastapi:0.2.0              "uvicorn main:app --…"   8 weeks ago         Up 2 days             0.0.0.0:8056->8000/tcp                                                             flask-xray_fastapi_1400e1d6c0f69        tensorflow/serving:latest-gpu         "/usr/bin/tf_serving…"   8 weeks ago         Up 2 days             0.0.0.0:8520->8500/tcp, 0.0.0.0:8521->8501/tcp                                     flask-xray_tf2svg2_1eaac88e9b1a7        ai-service-flask:0.1.0                "gunicorn app:app --…"   8 weeks ago         Up 2 days             0.0.0.0:8051->5000/tcp                                                             flask-xray_flask_1e07ccd30a32b        tensorflow/serving                    "/usr/bin/tf_serving…"   8 weeks ago         Up 2 days             0.0.0.0:8510->8500/tcp, 0.0.0.0:8511->8501/tcp                                     flask-xray_tf2svg1_1390dc13023f2        tf2-jupyter:0.1.0                     "/bin/sh -c '/bin/ba…"   8 weeks ago         Up 2 days             0.0.0.0:8506->6006/tcp, 0.0.0.0:8586->8888/tcp                                     flask-xray_tf2jpt_188e8709404ac        tf2-jupyter-jdbc:1.0.0-iml-template   "/bin/sh -c '/bin/ba…"   2 months ago        Up 2 days             0.0.0.0:6026->6006/tcp, 0.0.0.0:8896->8888/tcp                                     iml-template-master_tf2jupyter_1 **docker-compose up --scale fastapi=2 --scale flask=2 -d**   for example will horizontally scale up to 2x Gunicorn+Flask containers and 2x Univcorn+FastAPI containers: ubuntu@ip-172-31-35-104:/zhong/flask-xray$ docker psCONTAINER ID        IMAGE                                 COMMAND                  CREATED             STATUS                PORTS                                                                              NAMESdbee3c20ea95        ai-service-fastapi:0.2.0              "uvicorn main:app --…"   4 minutes ago       Up 4 minutes          0.0.0.0:8057->8000/tcp                                                             flask-xray_fastapi_295bcd8535aa6        ai-service-flask:0.1.0                "gunicorn app:app --…"   4 minutes ago       Up 4 minutes          0.0.0.0:8052->5000/tcp                                                             flask-xray_flask_2 ... ... 「integrtedML-demo-template」の作業ディレクトリで別の「docker-compose up -d」を実行することで、上記リストのirisimlsvrとtf2jupyterコンテナが起動されています。   ## テスト ### **1. 単純なUIを備えたAIデモWebアプリ** 上記のdockerサービスを起動したら、一時アドレスのAWS EC2インスタンスにホストされている[Covid-19に感染した胸部X線画像の検出](https://community.intersystems.com/post/run-some-covid-19-lung-x-ray-classification-and-ct-detection-demos)用デモWebアプリにアクセスできます。 以下は、私の携帯でキャプチャした画面です。  デモUIは非常に単純です。基本的に「Choose File」をクリックして「Submit」ボタンを押せば、[X線画像](https://github.com/zhongli1990/Covid19-X-Rays/tree/master/all/test)がアップロードされて分類レポートが表示されます。 Covid-19感染のX線画像として分類されたら、DLによって「検出された」病変領域をエミュレートする[ヒートマップが表示](https://community.intersystems.com/post/explainability-and-visibility-covid-19-x-ray-classifiers-deep-learning)されます。そうでない場合は、分類レポートにはアップロードされたX線画像のみが表示されます。          このWebアプリはPythonサーバーページです。このロジックは主に[FastAPI's main.py](https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/covid-fastapi/main.py)ファイルと[Flask's app.py](https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/covid19/app.py)ファイルにコーディングされています。 もう少し時間に余裕がある際には、FlaskとFastAPIのコーディングと規則の違いを詳しく説明かもしれません。  実は、AIデモホスティングについて、FlaskとFastAPIとIRISの比較を行いたいと思っています。    ### **2. テストデモAPI**       FastAPI(ポート8056で公開)には、以下に示すSwagger APIドキュメントが組み込まれています。 これは非常に便利です。 以下のようにURLに「/docs」を指定するだけで、利用することができます。  ![](/sites/default/files/inline/images/images/image(875).png) いくつかのプレースホルダー(/helloや/itemsなど)と実際のデモAPIインターフェース(/healthcheck、/predict、predict/heatmapなど)を組み込みました。   **これらのAPIに簡単なテストを実行してみましょう**。[私がこのAIデモサービス用に作成したJupyterノートブックサンプル](https://github.com/zhongli1990/covid-ai-demo-deployment/tree/master/notebooks/notebooks)の1つで、Python行を(APIクライアントアプリエミュレーターとして)いくつか実行します。   以下では、例としてこちらのファイルを実行しています。 まず、バックエンドのTF-Serving(ポート8511)とTF-Serving-GPU(ポート8521)が稼働していることをテストします。  !curl http://172.17.0.1:8511/v1/models/covid19 # tensorflow serving !curl http://172.17.0.1:8521/v1/models/covid19 # tensorflow-gpu serving { "model_version_status": [ { "version": "2", "state": "AVAILABLE", "status": { "error_code": "OK", "error_message": "" } } ] } { "model_version_status": [ { "version": "2", "state": "AVAILABLE", "status": { "error_code": "OK", "error_message": "" } } ] }   次に、以下のサービスAPIが稼働していることをテストします。 Gunicorn+Flask+TF-Serving Unicorn+FastAPI+TF-Serving-GPU 上記の両サービスの手間にあるHAProxyロードバランサー r = requests.get('http://172.17.0.1:8051/covid19/api/v1/healthcheck') # tf srving docker with cpu print(r.status_code, r.text) r = requests.get('http://172.17.0.1:8056/covid19/api/v1/healthcheck') # tf-serving docker with gpu print(r.status_code, r.text) r = requests.get('http://172.17.0.1:8088/covid19/api/v1/healthcheck') # tf-serving docker with HAproxy print(r.status_code, r.text) そして、以下のような結果が期待されます。 200 Covid19 detector API is live! 200 "Covid19 detector API is live!\n\n" 200 "Covid19 detector API is live!\n\n"   入力X線画像の分類とヒートマップ結果を返す、**/predict/heatmap**などの機能的なAPIインターフェースをテストします。  受信する画像は、API定義に従ってHTTP POST経由で送信される前にbased64にエンコードされています。 %%time # リクエストライブラリをインポート import argparse import base64 import requests # api-endpointを定義 API_ENDPOINT = "http://172.17.0.1:8051/covid19/api/v1/predict/heatmap" image_path = './Covid_M/all/test/covid/nejmoa2001191_f3-PA.jpeg' #image_path = './Covid_M/all/test/normal/NORMAL2-IM-1400-0001.jpeg' #image_path = './Covid_M/all/test/pneumonia_bac/person1940_bacteria_4859.jpeg' b64_image = "" # JPGやPNGなどの画像をbase64形式にエンコード with open(image_path, "rb") as imageFile: b64_image = base64.b64encode(imageFile.read()) # APIに送信されるデータ data = {'b64': b64_image} # POSTリクエストを送信しレスポンスをレスポンスオブジェクトとして保存 r = requests.post(url=API_ENDPOINT, data=data) print(r.status_code, r.text) # レスポンスを抽出 print("{}".format(r.text)) このようなすべての[テスト画像もGitHubにアップロード済み](https://github.com/zhongli1990/Covid19-X-Rays/tree/master/all/test)です。  上記のコードの結果は以下のようになります。 200 {"Input_Image":"http://localhost:8051/static/source/0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png","Output_Heatmap":"http://localhost:8051/static/result/Covid19_98_0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png.png","X-Ray_Classfication_Raw_Result":[[0.805902302,0.15601939,0.038078323]],"X-Ray_Classification_Covid19_Probability":0.98,"X-Ray_Classification_Result":"Covid-19 POSITIVE","model_name":"Customised Incpetion V3"} {"Input_Image":"http://localhost:8051/static/source/0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png","Output_Heatmap":"http://localhost:8051/static/result/Covid19_98_0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png.png","X-Ray_Classfication_Raw_Result":[[0.805902302,0.15601939,0.038078323]],"X-Ray_Classification_Covid19_Probability":0.98,"X-Ray_Classification_Result":"Covid-19 POSITIVE","model_name":"Customised Incpetion V3"} CPU times: user 16 ms, sys: 0 ns, total: 16 ms Wall time: 946 ms   ### **3. デモサービスAPIをベンチマークテストする** HAProxyロードバランサーインスタンスをセットアップします。 また、Flaskサービスを4個のワーカーで開始し、FastAPIサービスも4個のワーカーで開始しました。 ノートブックファイルで直接8個のPythonプロセスを作成し、8個の同時APIクライアントがデモサービスAPIにリクエストを送信する様子をエミュレートしてみてはどうでしょうか。  #from concurrent.futures import ThreadPoolExecutor as PoolExecutor from concurrent.futures import ProcessPoolExecutor as PoolExecutor import http.client import socket import time start = time.time() #laodbalancer: API_ENDPOINT_LB = "http://172.17.0.1:8088/covid19/api/v1/predict/heatmap" API_ENDPOINT_FLASK = "http://172.17.0.1:8052/covid19/api/v1/predict/heatmap" API_ENDPOINT_FastAPI = "http://172.17.0.1:8057/covid19/api/v1/predict/heatmap" def get_it(url): try: # 画像をループ for imagePathTest in imagePathsTest: b64_image = "" with open(imagePathTest, "rb") as imageFile: b64_image = base64.b64encode(imageFile.read()) data = {'b64': b64_image} r = requests.post(url, data=data) #print(imagePathTest, r.status_code, r.text) return r except socket.timeout: # 実際のケースではおそらく # ソケットがタイムアウトする場合に何かを行うでしょう pass urls = [API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB] with PoolExecutor(max_workers=16) as executor: for _ in executor.map(get_it, urls): pass print("--- %s seconds ---" % (time.time() - start)) したがって、8x27 = 216個のテスト画像を処理するのに74秒かかっています。 これは負荷分散されたデモスタックが、(分類とヒートマップの結果をクライアントに返すことで)1秒間に3個の画像を処理できています。 --- 74.37691688537598 seconds --- PuttyセッションのTopコマンドから、上記のベンチマークスクリプトが実行開始されるとすぐに8個のサーバープロセス(4個のGunicornと4個のUvicorn/Python)がランプアップし始めたことがわかります。   ## 今後の内容 この記事は、テストフレームワークとして「全DockerによるAIデモ」のデプロイメントスタックをまとめるための出発点に過ぎません。 次は、Covid-19 ICU入室予測インターフェースなどさらに多くのAPIデモインターフェースを理想としてはFHIR R4などによって追加し、サポートDICOM入力形式を追加したいと思います。 これは、IRISがホストするML機能とのより緊密な統合を検討するためのテストベンチになる可能性もあります。 医用画像、公衆衛生、またはパーソナル化された予測やNLPなど、さまざまなAIフロントを見ていく過程で、徐々に、より多くのMLまたはDL特殊モデルを傍受するためのテストフレームワーク(非常に単純なテストフレーム)として使用していけるでしょう。 ウィッシュリストは、[前の投稿(「今後の内容」セクション)](https://jp.community.intersystems.com/node/507006)の最後にも掲載しています。   
記事
Tomoko Furuzono · 2023年4月11日

データ更新中に、インデックスの再構築を実行する

これは、InterSystems FAQサイトの記事です。データの登録/更新/削除を実行中でも、インデックスを再構築することは可能です。ただし、再構築中は更新途中の状態で参照されますので、専用ユーティリティを使用することをお勧めします。手順は以下の通りです。 追加予定のインデックス名をクエリオプティマイザから隠します。 インデックス定義を追加し、再構築を実施します。 再構築が完了したら、追加したインデックスをオプティマイザに公開します。 実行例は以下の通りです。Sample.Person の Home_State(連絡先住所の州情報)カラムに対して標準インデックス HomeStateIdx を定義する目的での例で記載します。 1、追加予定のインデックス名を Caché のクエリオプティマイザから隠します。 >write $system.SQL.SetMapSelectability("Sample.Person","HomeStateIdx",0)1 2、インデックス定義を追加した後、再構築を実施します。  定義例)Index HomeStateIdx On Home.State; >do ##class(Sample.Person).%BuildIndices($LB("HomeStateIdx")) 3、再構築が完了したら、追加したインデックスをオプティマイザに公開します >write $system.SQL.SetMapSelectability("Sample.Person","HomeStateIdx",1)1 インデックスが使用されたか/されないか、の確認はクエリプランを参照します。以下の例では、ターミナルを $system.SQL.Shell() でSQL実行環境に切り替えた状態でのプラン確認結果を表示しています(管理ポータルで参照する場合は、クエリ実行画面でSQL実行後「プラン表示」ボタンを押下します)。 SAMPLES>do $system.SQL.Shell()SQL Command Line Shell----------------------------------------------------The command prefix is currently set to: <>.Enter q to quit, ? for help.SAMPLES>>select ID,Name from Sample.Person where Home_State='NY'1. select ID,Name from Sample.Person where Home_State='NY'ID Name61 Alton,Debby O.138 Isaksen,Charlotte L.175 Walker,Emily O.3 Rows(s) Affectedstatement prepare time(s)/globals/lines/disk: 0.0026s/35/974/0ms execute time(s)/globals/lines/disk: 0.0017s/216/2447/0ms cached query class: %sqlcq.SAMPLES.cls1---------------------------------------------------------------------------SAMPLES>>show plan // ★ インデックス未使用時のプラン表示 DECLARE QRS CURSOR FOR SELECT ID , Name FROM Sample . Person WHERE Home_State = ?Read master map Sample.Person.IDKEY, looping on ID.For each row: Output the row.SAMPLES>>show plan // ★ インデックス使用時のプラン表示 DECLARE QRS CURSOR FOR SELECT ID , Name FROM Sample . Person WHERE Home_State = ?Read index map Sample.Person.HomeStateIdx, using the given %SQLUPPER(Home_State), and looping on ID.For each row: Read master map Sample.Person.IDKEY, using the given idkey value. Output the row.SAMPLES>> 詳細は、以下ドキュメントをご参照ください。READ および WRITE アクティブシステム上でのインデックスの構築について【IRIS】READ および WRITE アクティブシステム上でのインデックスの構築についてまた、下記の関連トピックもご確認ください。アプリケーション使用中にインデックス再構築を複数プロセスで実行する方法クエリをチューニングする方法
記事
Megumi Kakechi · 2022年3月23日

ジャーナルレコードに記録されるタイプについて

これは、InterSystems FAQサイトの記事です。ジャーナル・ファイルの処理でジャーナルファイルに記録されるタイプが、それぞれどのような状況下で記録されるのかについて説明します。 処理 (管理ポータルの)タイプ 説明 具体的にどのような処理で記録? JRNSET SET ローカルノードを設定 Set コマンドによりグローバルを更新した場合 JRNMIRSET MirrorSET ミラーノードを設定 ミラー環境にてSet コマンドによりグローバルを更新した場合 JRNBITSET BitSET ノード内の指定されたビット位置を設定 $Bit関数によりグローバルを更新した場合 JRNKILL KILL ローカルノードを削除 Kill コマンドによりグローバルを更新した場合 JRNKILLDES KILLdes 下位ノードを削除(※1) トランザクション中に従属ノードが存在するグローバルノードを削除した場合 JRNMIRKILL MirrorKILL ミラーノードを削除 ミラー環境にてKill コマンドによりグローバルを更新した場合 JRNZKILL ZKILL 従属ノードを削除せずにローカルノードを削除 ZKill コマンドによりグローバルを更新した場合 JRNNSET 現在使用されていません JRNNKILL 現在使用されていません JRNNZKILL 現在使用されていません JRNMIRSET 内部ミラー処理 ミラーリングのシステムグローバルに対する内部的な更新 JRNMIRKILL 内部ミラー処理 ミラーリングのシステムグローバルに対する内部的な更新 ※1:JRNKILLDESの例 USER>zw ^test^test(1)=1^test(1,1)=1 USER>ts TL1:USER>k ^test(1) ジャーナルレコード: 以下のタイプはデータベースへの更新は行いません。 処理 (管理ポータルの)タイプ 説明 JRNBEGTRANS BeginTrans トランザクションを開始 JRNTBEGINLEVEL BeginTrans with level トランザクション・レベルを開始 JRNCOMMIT CommitTrans トランザクションをコミット JRNTCOMMITLEVEL CommitTrans with level 分離されたトランザクション・レベルをコミット JRNTCOMMITPENDLEVEL CommitTrans Pending with level 保留中のトランザクション・レベルをコミット JRNMARK Marker ジャーナル・マーカ JRNBIGNETECP netsyn ECPネットワーキング JRNTROLEVEL Rollback トランザクションをロールバック あわせて、以下の関連記事も是非ご覧ください。 ジャーナルファイルを削除する方法 ジャーナルファイルが長時間消されずに残ってしまう原因 コマンドでジャーナルファイルにある特定のグローバル変数を検索する方法 誤って削除したグローバルを復旧させる方法 ジャーナルファイルの内容を管理ポータル以外で参照する方法 ジャーナルファイルの使用量をチェックする方法
記事
Megumi Kakechi · 2023年3月28日

テーブル名、カラム名、インデックス名の変更方法

これは InterSystems FAQ サイトの記事です。 テーブル名/カラム名/インデックス名を変更したい場合、以下のケース別に変更方法をご案内します。 A. テーブル名・カラム名の変更B. インデックス名の変更 -------------------------------------------------------------------------A. テーブル名・カラム名の変更する方法------------------------------------------------------------------------- テーブル(クラス)名とカラム(プロパティ)名は基本的には変えないようにしてください。 もし「SQLアクセス時の名前だけ変更したい」場合は、以下のように新しい名前を SqlTableName(テーブル名)、SqlFieldName(カラム名) として指定することができます。 Class User.test Extends %Persistent [ SqlTableName = test2 ] { Property p1 As %Integer [ SqlFieldName = xx ]; .... 以後 "SELECT xx from test2" としてアクセス可能になります。こちらの方法の場合は、スタジオで定義を記述し、再コンパイルすると即座に反映されます。 どうしてもクラス名を変更したい場合は以下の(1)、プロパティ名を変更したい場合は以下の(2)が必要となります。 (1) クラス名変更はできないため、新規クラス作成し、定義を丸ごとコピーする必要があります。  コピーには、スタジオの「ツール > クラスをコピー」を使用できます。  ストレージ定義やクラス名のインスタンスもすべてコピーする場合は、以下のようにチェックを入れます。 ※元のクラスは、新規クラスをコンパイルする前にコマンドで削除してください。 以下の実行例を参考にしてください。 実行例: USER>do $SYSTEM.OBJ.Delete("User.Test") クラスを削除中 User.Test USER>write ##class(%ExtentMgr.Util).DeleteExtentDefinition("User.Test","cls") 1 (2) プロパティ名変更は、スタジオで定義を変更するだけです。  ただしグローバルのストレージ場所が変わらないように、手動でストレージ情報を編集する必要があります。 【注意】クラス名やプロパティ名を変える場合、手順を間違えると 【ご参考】にある関連トピックのような問題が発生します。十分に注意した上で行うようにしてください。 -----------------------------------------------------B. インデックス名の変更----------------------------------------------------- インデックス名の変更については、以下の手順になります。旧インデックスを削除 → スタジオでインデックス名を変更 → 新インデックス再構築 【例】Index a1 を Index b1 に変更する場合 (1) インデックスa1データ削除 do ##class(User.test).%PurgeIndices($LB("a1")) (2) スタジオでインデックス名を変更し、コンパイル (3) インデックス b1データ再構築 do ##class(User.test).%BuildIndices($LB("b1")) 【ご参考】クラス定義でプロパティ名を変更するとそれまで参照できていたデータが参照できなくなる/参照時にエラーが発生する場合の対処方法