0 フォロワー · 496 投稿

  

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

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

ドキュメント

記事 Toshihiko Minamoto · 1月 19, 2021 7m read

デバッガーは使わないという人はいますか? 最後に使ったのはいつだったか、記憶にないですね。 それは、嫌いだからではなくて、単に必要ないからなんです。 その一番の理由は、ある種の開発手法を使っているおかげで、バグの発生を少なく抑えたり、単体テストのレベルで発見したり、バグをとても簡単に追跡できたりするからです。

以下にいくつかヒントを紹介します...

1. 独自の COS チートシートを作成する。

これは主に COS の初心者が対象になります。 バグを引き起こす大きな原因の一つとして、特定のコマンドや関数の動作を理解していないということがあります。 時間をかけて言語を学び、そのすべてのバリエーションを試すことをおすすめします。 それから独自のチートシートを作成することで、知識が固まり、さっと使えるレビューツールも出来上がります。 生産性アップにつながるほか、避けられるはずのバグをうっかり書いてしまう頻度を確実に減らすことができます。

2. スタジオで「変数の追跡」をオンにする

まだオンにしていない方は、スタジオの 「 ツール 」「 オプション 」メニューにて、「 環境」フォルダの「クラス」と順に移動し、「変数を追跡する」のチェックボックスにチェックを入れてください。 宣言時に初期化されていない変数を使用する場合は、その変数の下に波線が引かれます。 このおかげで、うっかりタイプミスはありません。

また「option explicit」も使用できます。 個人的には使わなくてもいいと思いますが、ひょっとしたら役に立つかもしれません。 すべての変数において、最初に #dim 宣言を行うことが強制されます。

3. 構文チェックをオフにしない

これは、主な理由として、エラーに下線を付けてくれるので、デフォルトでオンにしておくべきです。 パフォーマンスに影響すると不平を言いながら、このオプションをオフにするデベロッパーを何人が見たことがあります。 言い訳はやめて、処理の早いマシンにしてください。 これもスタジオのオプション設定にあります。

4. 常にオートコンプリートを使用する

変に聞こえるかもしれませんが、私はオブジェクトに対してプロパティやメソッド名を最後まで入力することは一切ありません。 いつも数文字だけ入力してから、オートコンプリートの項目を明示的に選択するようにしています。 こうすれば、インスタンスメンバーの入力ミスでバグが発生することは絶対にありません。 使っていると思っていたクラスと実際に使っているクラスが違うという深刻な問題を見つけることもできます。

オブジェクトがオートコンプリートしない場合は、コードに #dim を追加してください。そうすれば、オートコンプリートするようになり、他のデベロッパーにとってもそのコードが読みやすくなります。

5. コードを簡潔にする

分かりきったことですが、コードを簡潔にすれば、バグが発生しにくくなります。 よく考えずに数百行にもおよぶコードを書き出すデベロッパーを何度か目にしたことがあります (実際に必要だったのはその半分もしくはそれ以下でした)。 コードをコンパクトにまとめるということではなく、慎重に整理すべきだということです。 もちろん、最初は少し時間がかかるかもしれませんが、結局は先週他の人が書いたコードのデバッグ作業で無駄になる時間をそれに充てていると考えればいいのです。 落ち着いてやれば、作業はスムーズに進みます。

6. コードを適切な場所に書き込む

「患者」というようなエンティティクラスがあれば、患者に影響するすべてがそのクラス内に記述されている必要があります。 ベテランの OO (オブジェクト指向) デベロッパーには当たり前に聞こえるかもしれませんが、このようなコードを非エンティティのメソッドに埋め込むデベロッパーは未だ少なくありません。

このコードを移動させることで、厳しくテストされたコードの再利用が促進されるほか、非エンティティのメソッドも小さくなり、デバッグしやすくなります。

7. すべてのステータスコードを処理する

この戦略は必要不可欠なもので、私は習慣的に行っています。 ステータスコードが返されることがあれば、次の行に進む前に検証しています。 エラーが出る場合は、ステータスコードを確認してから終了し、エラーを処理するレベルに戻ります。

これは、私が見る限りでは、他のデベロッパーが実行していないという問題の中で一番大きな問題でしょう。 やっていない方にはおすすめします。

Ensemble で開発している方には、Ensemble ライブラリのあちこちで使用されている、便利なマクロがあります...

  $$$QuitOnError(sc)
  
macro soup を好まない私も、これは気に入っています。コードをより簡潔にしてくれるので、肝心な部分だけを読むことができます。

また、try-catch 文を使う場合は、catch をそのメソッドのステータスコードとして強調表示します。

8. 再利用可能なモジュール単位のコードを書く

これはおそらく「コードを簡潔にする」の見出しに該当すると思われますが、できるだけ小さくて機能性の高いメソッドを書く、ということに重点を置いています。 各メソッドの目的は 1 つに限定するべきです。 1 つのメソッドを入力と出力を明確にした、いくつかの小さなメソッドに分割できるなら、そうするべきです。

9. 単体テスト、単体テスト、単体テスト

上述のヒントをすべて実行したら、このアクションが次に行う最も重要なステップとなります。なぜなら、デバッガーを起動することが必要になる前に、エラーを除去できるからです。 単体テストの結果としてエラーが発生すれば、該当する小さなコードブロックからそのエラーを除去することに集中できます。 デバッグ作業を行ったり、この問題を探してコードの中をあちこち探し回ったりする必要はありません。エラーの発生場所は的確に分かります。

私は、ブラウザーから動作する非常に便利な社内用の単体テストツールを持っています。 コマンドラインの面倒で複雑なセットアップは不要です。 興味のある方がいれば、オープンソースとして公開します。 主に、Ensemble を使ったトランスフォーメーションの単体テストに便利です。

10. そして、ついに、何をやっても効果がない場合は...

コードをステップスルーせずにデバッグするには、グローバル変数が欠かせません。 そこで、私は以下のようなコードを書きます...

  Set ^debug($zh,"foovar")=foovar
  
そして、コマンドラインから zwrite ^debug を実行するか、for loop の監視を実行します。

こうすることで、何行もあるコードをいちいちステップスルーする場合よりも、すばやく問題を発見できます。非同期プロセスの場合はなおさらです。

11. 最後に、少しお恥ずかしいのですが。。。

大したことではないですが、私はよくやっています。 問題を突き止められず困っているときは、ちょっと散歩に出かけて、視野を広げてみます。 戻ってきたら、明らかなものでも先ほどは視野が狭すぎて見えなかったというエラーの存在に気が付くと思います。 個人的には、違うネームスペースにアクセスしているなんてことをよくやらかしています。(オイオイ!)

バグの少ないコードを書くことについて、他のヒントがあれば是非聞かせてください。

最新情報...

12. ラバーダッキング

ラバーダック・デバッグ

13. コードレビュー

よく見落としがちだが、重要な提案です。 スタイルガイドが良い補足になると思います。

14. ログの記録

これもまた良い提案だと思います。 Ensemble にはコードに関する情報をログしたり、表示したりできる一連の便利な機能があります。以前は隠れていたエラーを強調表示したり、断続的に発生するため発見しにくいような問題を追跡できます。

Caché デベロッパーの皆さんには、開発者コミュニティに掲載されているログソリューションが重宝するかもしれませんね。

Sean

0
0 166
記事 Mihoko Iijima · 1月 15, 2021 2m read

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

$ZSTRIP() 関数を使用します。

この関数を使用すると、指定文字列から、文字のタイプと文字を削除できます。

下記の例のように、第2引数で、"<"、">"、"<>"を指定することにより、SQLのLTRIM、RTRIM、TRIM関数と同等の処理が可能になります。

// アクションコード "<>" を指定すると前後の空白を削除する
// マスクコード:W で空白 ($C(9), $C(32), $C(160)) を削除する
// 第3引数(全角スペース)は $C($ZHEX("3000")) でも指定可能

USER>Set a=" 全角 ・半角 スペースを含む文字列 "

USER>Set b=$ZSTRIP(a,"<>W"," ")
                                                               
USER>Write "["_b_"]"
[全角 ・半角 スペースを含む文字列]
USER>Set c=$ZSTRIP(a,"*W"," ")      // アクションコード "*" を指定すると全ての空白を削除する

USER>Write "["_c_"]"
[全角・半角スペースを含む文字列]
USER>
0
0 997
記事 Toshihiko Minamoto · 1月 11, 2021 4m read

前回の記事では、データの変更を簡単に記録できる方法をお見せしました。 今回は、監査ログが記録されるデータ構造と監査データを記録する「Audit Abstract クラス」を変更しました。

また、データ構造は親構造と子構造に変更し、それぞれに「トランザクション」とそのトランザクションで「その値によって変更されたフィールド」を記録するテーブルが 2 つ設けられます。

新しいデータモデルをご覧ください。

「監査クラス」から変更したコードをご覧ください。

Class Sample.AuditBase [ Abstract ]
{
Trigger SaveAuditAfter [ CodeMode = objectgenerator, Event = INSERT/UPDATE, Foreach = row/object, Order = 99999, Time = AFTER ]
{
          #dim %compiledclass As %Dictionary.CompiledClass
          #dim tProperty As %Dictionary.CompiledProperty
          #dim tAudit As Sample.Audit
          Do %code.WriteLine($Char(9)_"; get username and ip adress")
          Do %code.WriteLine($Char(9)_"Set tSC = $$$OK")
          Do %code.WriteLine($Char(9)_"Set tUsername = $USERNAME")
          Set tKey = ""
          Set tProperty = %compiledclass.Properties.GetNext(.tKey)
          Set tClassName = %compiledclass.Name
          Do %code.WriteLine($Char(9)_"Try {")
          Do %code.WriteLine($Char(9,9)_"; Check if the operation is an update - %oper = UPDATE")
          Do %code.WriteLine($Char(9,9)_"if %oper = ""UPDATE"" { ")
          Do %code.WriteLine($Char(9,9,9)_"Set tAudit = ##class(Sample.Audit).%New()")
          Do %code.WriteLine($Char(9,9,9)_"Set tAudit.Date = +$Horolog")
          Do %code.WriteLine($Char(9,9,9)_"Set tAudit.UserName = tUsername")
          Do %code.WriteLine($Char(9,9,9)_"Set tAudit.ClassName = """_tClassName_"""")
          Do %code.WriteLine($Char(9,9,9)_"Set tAudit.Id = {id}")
          Do %code.WriteLine($Char(9,9,9)_"Set tSC = tAudit.%Save()")
          do %code.WriteLine($Char(9,9,9)_"If $$$ISERR(tSC) $$$ThrowStatus(tSC)")
          Do %code.WriteLine($Char(9,9,9)_"Set tAuditId = tAudit.%Id()")
          While tKey '= "" {
                    set tColumnNbr = $Get($$$EXTPROPsqlcolumnnumber($$$pEXT,%classname,tProperty.Name))
                    Set tColumnName = $Get($$$EXTPROPsqlcolumnname($$$pEXT,%classname,tProperty.Name))
                    If tColumnNbr '= "" {
                              Do %code.WriteLine($Char(9,9,9)_";")
                              Do %code.WriteLine($Char(9,9,9)_";")
                              Do %code.WriteLine($Char(9,9,9)_"; Audit Field: "_tProperty.SqlFieldName)
                              Do %code.WriteLine($Char(9,9,9)_"if {" _ tProperty.SqlFieldName _ "*C} {")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAuditField = ##class(Sample.AuditField).%New()")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAuditField.Field = """_tColumnName_"""")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAuditField.OldValue = {"_tProperty.SqlFieldName_"*O}")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAuditField.NewValue = {"_tProperty.SqlFieldName_"*N}")
                              Do %code.WriteLine($Char(9,9,9,9)_"Do tAuditField.AuditSetObjectId(tAuditId)")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tSC = tAuditField.%Save()")
                              do %code.WriteLine($Char(9,9,9,9)_"If $$$ISERR(tSC) $$$ThrowStatus(tSC)")
                              Do %code.WriteLine($Char(9,9,9)_"}")
                    }
                    Set tProperty = %compiledclass.Properties.GetNext(.tKey)
          }
          Do %code.WriteLine($Char(9,9)_"}")
          Do %code.WriteLine($Char(9)_"} Catch (tException) {")
                    Do %code.WriteLine($Char(9,9)_"Set %msg = tException.AsStatus()")
                    Do %code.WriteLine($Char(9,9)_"Set %ok = 0")
                    Do %code.WriteLine($Char(9)_"}")
                    Set %ok = 1
}
}

Test() クラスメソッドを使ってデータを変更することで、Audit クラス (Sample.Audit) から「parent record」が見えるようになり、「children fields」も「Audit Field」クラスから変更されていることが分かります。 (Sample.AuditField)  

d ##class(Sample.Person).Test(1)
INSERT INTO Sample.Person (Name, Age) VALUES ('TEST PARENT-CHILD', '01')
SQLCODE: 0
ID Age Name
1 01 TEST PARENT-CHILD
1 Rows(s) Affected
UPDATE Sample.Person SET Name = 'INTERSYSTEMS DEVELOPER COMMUNITY', Age = '100' WHERE Name = 'TEST PARENT-CHILD'
SQLCODE:0
ID Age Name
1 100 INTERSYSTEMS DEVELOPER COMMUNITY
1 Rows(s) Affected
<p>
  監査クラス: 
</p>

<p style="margin-left: 40px;">
  <img alt="" src="/sites/default/files/inline/images/image9.png" />
</p>

<p style="margin-left: 40px;">
  <img alt="" src="/sites/default/files/inline/images/image10.png" />
</p>

<p>
  Sample.AuditField の記録には、監査フィールド = 1 を使った Sample.Audit クラスへの参照があります。 以下に示すように、両クラスの関係を使えば、データに対してクエリを実行できます。
</p>

<p>
  <img alt="" src="/sites/default/files/inline/images/image11.png" />
</p>

 これで完了です。 結果として、最初とは異なるログデータ構造ができました。

0
0 158
記事 Megumi Kakechi · 1月 6, 2021 1m read

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

Web サービス(SOAP)またはREST で IIS を経由してクライアントにエラー応答する際、既定では IISが生成するデフォルトのエラーページ( HTTP 500 エラー )が返されます。

SoapFaultなど、サーバ側で出力したエラーの詳細情報は返されません。

IIS7 以降、WebクライアントがWeb サービスの障害の詳細情報を取得するための設定オプションが追加されました。

HTTP ステータスコードがエラーの場合に、既存の応答をどうするかを existingResponse 属性で指定することができます。

こちらの値を、既定の Auto から PassThrough に変更することで、エラーの詳細情報が返されるように変更できます。
※設定変更後、IISの再起動が必要です。

詳細や設定手順については、以下ドキュメントをご参照ください。
Microsoft IIS 7 以降の設定手順について【IRIS】

Microsoft IIS 7 以降の設定手順について

※web.config ファイルの手動編集よりも、IIS マネージャに組み込まれている構成エディタ を使用する方法がエラーが少なく安全です。

 

HTTP エラー について
 

0
0 1820
記事 Toshihiko Minamoto · 1月 5, 2021 9m read

はじめに

多くのアプリケーションに共通する要件は、データベース内のデータ変更のログ記録です。どのデータが変更されたか、誰がいつ変更したかをログに記録する必要があります(監査ログ)。 このような質問について書かれた記事は多く存在し、Caché で行う方法の切り口もさまざまです。

そこで、データ変更を追跡して記録するためのフレームワークを実装しやすくする仕組みを説明することにします。 これは、永続クラスが「監査抽象クラス」(Sample.AuditBase)から継承すると「objectgenarator」メソッドを介してトリガーを作成する仕組みです。 永続クラスは Sample.AuditBase から継承されるため、永続クラスをコンパイルすると、変更を監査するためのトリガーが自動的に生成されます。


監査クラス  

次は、変更が記録されるクラスです。

  Class Sample.Audit Extends %Persistent
{
          Property Date As %Date;
          Property UserName As %String(MAXLEN = "");
          Property ClassName As %String(MAXLEN = "");
          Property Id As %Integer;
          Property Field As %String(MAXLEN = "");
          Property OldValue As %String(MAXLEN = "");
          Property NewValue As %String(MAXLEN = "");
}

### 監査抽象クラス  

これは、永続クラスの継承元となる抽象クラスです。 このクラスには、監査テーブル(Sample.Audit)に変更を書き込むほか、どのフィールドが変更されたのか、誰が変更したのか、変更前と後の値は何であるかなどを識別する方法を知っているトリガーメソッド(objectgenerator)が含まれています。

    Class Sample.AuditBase [ Abstract ]
{
Trigger SaveAuditAfter [ CodeMode = objectgenerator, Event = INSERT/UPDATE, Foreach = row/object, Order = 99999, Time = AFTER ]
{
          #dim %compiledclass As %Dictionary.CompiledClass
          #dim tProperty As %Dictionary.CompiledProperty
          #dim tAudit As Sample.Audit
          Do %code.WriteLine($Char(9)_"; get username and ip adress")
          Do %code.WriteLine($Char(9)_"Set tSC = $$$OK")
          Do %code.WriteLine($Char(9)_"Set tUsername = $USERNAME")
          Set tKey = ""
          Set tProperty = %compiledclass.Properties.GetNext(.tKey)
          Set tClassName = %compiledclass.Name
          Do %code.WriteLine($Char(9)_"Try {")
          Do %code.WriteLine($Char(9,9)_"; Check if the operation is an update - %oper = UPDATE")
          Do %code.WriteLine($Char(9,9)_"if %oper = ""UPDATE"" { ")
          While tKey '= "" {
                    set tColumnNbr = $Get($$$EXTPROPsqlcolumnnumber($$$pEXT,%classname,tProperty.Name))
                    Set tColumnName = $Get($$$EXTPROPsqlcolumnname($$$pEXT,%classname,tProperty.Name))
                    If tColumnNbr '= "" {
                              Do %code.WriteLine($Char(9,9,9)_";")
                              Do %code.WriteLine($Char(9,9,9)_";")
                              Do %code.WriteLine($Char(9,9,9)_"; Audit Field: "_tProperty.SqlFieldName)
                              Do %code.WriteLine($Char(9,9,9)_"if {" _ tProperty.SqlFieldName _ "*C} {")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit = ##class(Sample.Audit).%New()")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.ClassName = """_tClassName_"""")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.Id = {id}")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.UserName = tUsername")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.Field = """_tColumnName_"""")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.Date = +$Horolog")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.OldValue = {"_tProperty.SqlFieldName_"*O}")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.NewValue = {"_tProperty.SqlFieldName_"*N}")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tSC = tAudit.%Save()")
                              do %code.WriteLine($Char(9,9,9,9)_"If $$$ISERR(tSC) $$$ThrowStatus(tSC)")
                              Do %code.WriteLine($Char(9,9,9)_"}")
                    }
                    Set tProperty = %compiledclass.Properties.GetNext(.tKey)
          }
          Do %code.WriteLine($Char(9,9)_"}")
          Do %code.WriteLine($Char(9)_"} Catch (tException) {")
          Do %code.WriteLine($Char(9,9)_"Set %msg = tException.AsStatus()")
          Do %code.WriteLine($Char(9,9)_"Set %ok = 0")
          Do %code.WriteLine($Char(9)_"}")
          Set %ok = 1
}
}

### データクラス(永続クラス)

これは、ユーザー(アプリケーション)が変更を加えたり、レコードを作成したり、レコードを削除したりなど、ユーザーに許可したことをすべて実行するユーザーデータクラスです。 :)。 つまり、通常は %Persistent クラスということです。   変更を追跡して記録するには、この永続クラスを抽象クラス(Sample.AuditBase)から継承する必要があります。

      Class Sample.Person Extends (%Persistent, %Populate, Sample.AuditBase)
{
          Property Name As %String [ Required ];
          Property Age As %String [ Required ];
          Index NameIDX On Name [ Data = Name ];
}

### テスト

監査抽象クラス(Sample.AuditBase)からデータクラス(Sample.Person)を継承しているため、データの挿入、変更の追加、および変更内容の確認を監査クラス(Sample. Audit)で行うことができます。

これをテストするには、Sample.Person クラスまたは他の任意のクラスに Test() クラスメソッドを作成する必要があります。

ClassMethod Test(pKillExtent = )
{
          If pKillExtent '= 0 {
                    Do ##class(Sample.Person).%KillExtent()
                    Do ##class(Sample.Audit).%KillExtent()
          }
          &SQL(INSERT INTO Sample.Person (Name, Age) VALUES ('TESTE', '01'))
          Write "INSERT INTO Sample.Person (Name, Age) VALUES ('TESTE', '01')",!
          Write "SQLCODE: ",SQLCODE,!!!
          Set tRS = $SYSTEM.SQL.Execute("SELECT * FROM Sample.Person")
          Do tRS.%Display()
          &SQL(UPDATE Sample.Person SET Name = 'TESTE 2' WHERE Name = 'TESTE')
          Write !!!
          Write "UPDATE Sample.Person SET Name = 'TESTE 2' WHERE Name = 'TESTE'",!
          Write "SQLCODE:",SQLCODE,!!!
          Set tRS = $SYSTEM.SQL.Execute("SELECT * FROM Sample.Person")
          Do tRS.%Display()
          Quit
}
Test() メソッドを実行しましょう。
        d ##class(Sample.Person).Test(1)

パラメータの「1」は、Sample.Person と Sample.Audit クラスからエクステントを削除します。

このテストクラスメソッドは次の内容を実行します。

  • 「TEST」という名前で新しい人を挿入する
  • 挿入の結果を表示する
  • 「TEST」という人を「TEST ABC」に更新する
  • 更新の結果を表示する

ここで、監査ログテーブルを確認してみましょう。 これを行うには、システム管理ポータル -> システムエクスプローラ -> SQL を開きます。 (ネームスペースを忘れずに切り替えてください)

次の SQL コマンドを実行して結果を確認します。

SELECT * FROM Sample.Audit 

OldValue が「TEST」、NewValue が「TEST ABC」であることに注意してください。これ以降は、「TEST ABC」を自分の名前に変更したり、年齢の値を変更したりして、独自のテストを行ってみると良いでしょう。 次に例を示します。

UPDATE Sample.Person SET Name = 'Fabio Goncalves' WHERE Name = 'TEST ABC'

### 生成されるコード

以下の監査メカニズムを実装しているとした上で、コンピュータで Studio(または Atelier)を起動し、永続クラス(Sample.Person)を開いて、Sample.Person クラスをコンパイルした後に生成される中間コードを調べてみましょう。 これを行うには、Ctrl + Shift + V(ほかのソースコードを表示)を押して、.INT を検査します。 zSaveAuditAfterExecute ラベルまでスクロールし、生成されたコードを確認します。

### メリット

古いデータのロールアウトに基づいて監査ログ機能を実装するのは簡単です。 追加のテーブルは必要ありません。 メンテナンスも簡単で、 古いデータを削除するのであれば、1 つの SQL で済みます。 ほかのテーブルでも監査ログ機能を実装する必要がある場合は、抽象クラス(Sample.AuditBase)から継承するだけです。 必要に応じて変更してください。 例: ストリームの変更を記録する。 変更されたフィールドのみを記録し、 変更したレコード全体を保存しないでください。

デメリット

データが変更されると、レコード全体がコピーされるため、変更されていないデータもコピーされてしまうことが問題となる場合があります。 テーブル Person に写真が含まれるバイナリデータ(stream)を持つ「photo」という列がある場合、ユーザーが写真を変更するたびに、ストリーム全体が記録されてしまいます(ディスクスペースが消費されてしまいます)。 もう 1 つの難点は、監査ログをサポートする各テーブルの複雑さが増すところにあります。 レコードの取得は容易にはいかないことを肝に銘じておきましょう。 SELECT 句は必ず条件「...WHERE Status = active」とともに使用するか、「DATE INTERVAL」などを検討するようにしてください。 すべてのデータ変更は共通テーブルにログされます。 トランザクションをロールバックとして考えるようにしましょう。

 


 アプリケーションの効率化には、監査は重要な要件です。 通常、データ変更を判別するには、アプリケーション開発者が、トリガー、タイムスタンプ列、およびその他のテーブルを組み合わせてアプリケーションにカスタムの追跡メソッドを実装することが必要です。 こういった仕組みを作成するには大抵、多くの作業を実装する必要があり、スキーマの更新や高パフォーマンスのオーバーヘッドが生じることがよくあります。 この記事は簡単な例として、独自のフレームワークを作成し始める際に役立ててください。
0
0 261
記事 Mihoko Iijima · 12月 28, 2020 3m read

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

データベース暗号化は、ディスクヘの書き込みまたはディスクからの読み取りで暗号化と復号が実行されるため、アプリケーションのロジックに手を加える必要はありません。

この機能のドキュメントについては、以下ご参照ください。

マネージド・キー暗号化【IRIS】

マネージド・キー暗号化


暗号化データベース作成までの流れは、以下の通りです。

(1) 暗号化キーの作成

  • (a) 管理者 ユーザ名/パスワード
  • (b) 暗号化キーファイル

(2) 暗号化キーの有効化

(3) 暗号化されたデータベースの作成

暗号化データベース作成後の運用のための設定は以下の通りです。

〇 データベース暗号化の起動設定(暗号化キーの有効化をどのように行うか)

暗号化されたデータベースは、"暗号化キーの有効" が行われてアクセスできるようになります。
既定の設定では、"暗号化キーの有効"を行いませんので、以下3種類の方法から選択します。

① キーを有効化しない起動の構成

既定の設定のまま、インスタンス起動時に "暗号化キーの有効" が行われません。
暗号化されたデータベースをマウントする前に管理ポータルなどから "暗号化キーの有効" を行う必要があります。

以下の場合、この運用は適応できません。

0
0 349
記事 Henrique Dias · 12月 25, 2020 2m read

皆さん、こんにちは。

iris-analytics-パッケージには、「企業が自社のソフトウェアで InterSystems Analytics の サポートをいかに簡単、そしてシンプルに利用できるかを示す」意図があります。

新しいシンプルなソリューションを作成したり、OpenExchange を使用して既存のソリューションを改善したりすることもできます。

InterSystems IRISにアップグレードしている企業のほとんどは、ツールが提供するすべての機能を活用しています。

このコンテストでの私のもう一つのターゲットは、インターシステムズを長く利用していても、自分たちがアクセスしている可能性をフルに活用していない企業です。

このプロジェクトは、他のプロジェクトをベースにしてインスピレーションを得て作成しました。ありがとう  @Evgeny Shvarov @Guillaume Rongier @Peter Steiwer 

それらのプロジェクトが一緒になって、このウィザードになった。
 

インポートウィザード

メインページはシンプルで、そこに至るまでのプロセスがわかりやすい。

ウィザードを適切に使用するためのフィールドがいくつかあります。

0
0 487
記事 Toshihiko Minamoto · 12月 23, 2020 3m read

新しい動的 SQL クラス(%SQL.Statement および %StatementResult)のパフォーマンスは %ResultSet より優れてはいますが、%ResultSet の使用方法をせっかく学習したので、しばらくの間新しい方を使用せずにいましたが、 やっとチートシートを作ったので、新しいコードを書いたり古いコードを書き直す際に役立てています。 皆さんのお役に立てればいいなと思っています。

次に示すのは、私のチートシートの詳細版です。

<th>
  %ResultSet::%New()
</th>

<th>
  %SQL.Statement::%New()
</th>
<td>
     Prepare() インスタンスメソッドを呼び出す
</td>

<td>
     %Prepare() インスタンスメソッドを呼び出す
</td>
<td>
     前のステップがステータスを返すので、それを確認
</td>

<td>
     前のステップがステータスを返すので、それを確認
</td>
<td>
     Execute() インスタンスメソッドを呼び出す
</td>

<td>
     %Execute() インスタンスメソッドを呼び出す
</td>
<td>
     前のステップがステータスを返すので、それを確認
</td>

<td>
     前のステップが %SQL.StatementResult のインスタンスを返すので、次のステップでそれを使用
</td>
<td>
     Next() インスタンスメソッドを呼び出す(while ループでイテレートなど)
</td>

<td>
     %Next() インスタンスメソッドを呼び出す(while ループでイテレートなど)
</td>
<td>
     GetData() インスタンスメソッドを呼び出して、列番号で列を取得
</td>

<td>
     %GetData() インスタンスメソッドを呼び出して、列番号で列を取得
</td>
<td>
     %Get() インスタンスメソッドを呼び出して、列番号で列を取得
</td>
1
2
3
4
5
6
7
   Get() または Data() インスタンスメソッドを呼び出して、列番号で列を取得

 

そして、これが私が実際に使用している簡易版チートシートです。

<th>
  %ResultSet::%New()
</th>

<th>
  %SQL.Statement::%New()
</th>
<td>
     Prepare()
</td>

<td>
     %Prepare()
</td>
<td>
     ステータスを確認
</td>

<td>
     ステータスを確認
</td>
<td>
     Execute()
</td>

<td>
     %Execute()
</td>
<td>
     ステータスを確認
</td>

<td>
     %Execute の戻り値を次のステップに使用
</td>
<td>
     Next()
</td>

<td>
     %Next()
</td>
<td>
     GetData()
</td>

<td>
     %GetData()
</td>
<td>
     %Get()
</td>
1
2
3
4
5
6
7
   Get() または Data()
0
0 226
記事 Toshihiko Minamoto · 12月 21, 2020 9m read

$LIST のフォーマットと%DynamicArray、%DynamicObject クラス

IRIS には、様々なデータ値を含むシーケンスを作成する方法がいくつかあります (以前は Cache にもありました)。  長年に渡り使用されているデータシーケンスの 1 つに $LIST の文字列があります。  より最近のデータシーケンスには %DynamicArray クラスと %DynamicObject クラスがあり、両者ともに JSON の文字列表現に対応する IRIS サポートの一部となっています。  これら 2 つのシーケンスにはそれぞれ非常に異なるトレードオフがあります。

$LIST の文字列形式

$LIST 形式は、かつてメモリアドレスのスペースが小さいだけでなく、ディスクドライブも小さく、読み取り速度が遅かった時代に考案されました。  $LIST の形式は、複数の異なるデータ型で構成されるシーケンスをバイト数を可能な限り抑えながら 8 ビットの一般的な文字列にパッキングするためにデザインされました。

$LIST のシーケンスは、ObjectScript の $LISTBUILD 関数を使って作成します。 

$LIST の文字列の最も重要な機能は、データを 8 ビットの値で構成される最小のシーケンスにぎっしりパッキングできるという点です。  $LIST の文字列には、ObjectScript の様々なデータ型の複数の異なる表現を含めることができます。  それらのデータ型には、文字列型 (ObjectScript では引用符で囲んだ文字列リテラルを使って作成)、10 進浮動小数点型 (ObjectScript では数値リテラルを使って作成)、および IEEE 規格の 2 進浮動小数点型 (ObjectScript では $DOUBLE 関数を数値式に適用して作成) が含まれます。  ObjectScript の oref 型は $LIST の文字列によってサポートされていません。  $LIST の要素が作成されるとき、これらの値の内部表現に対しバイナリとバイトの非常にシンプルな圧縮化が行われます。  

$LIST の文字列の 2 つ目の重要な機能は、これらの 8 ビットの値を IRIS インスタンスまたは転送メディアのエンディアン特性 (ビッグなのかリトルなのか) を気にせずに、両立させながら同インスタンス間を送信できるという点です。  特に明記すべきは、ビッグエンディアンを使うデータベースとリトルエンディアンを使うデータベースの間で $LIST の文字列を移動させる際に、$LIST のコンポーネントの値が変更されないという点です。

データのパッキングと転送機能に次いで重要なのがパフォーマンスです。  すべての $LIST オペレーション ($LISTVALID を除く) は、実行するマシンインストラクションの数を最小限に抑えようとします。  $LIST の構造が無効なために、セグメントフォールトや他のシステム例外が起り得る場合を除き、$LIST オペレーションは $LIST のデータ構造が有効であるという想定の基に実行されます。  $LIST オペレーションは、それを有効性を確認するためのインストラクションは実行しません。  

空の $LIST は空の文字列です。  $LIST の文字列を連結するには、文字列の一般的な連結方法を用います。

$LIST の文字列をタイトにパッキングするということは、$LIST の i 個目の要素 ($LIST(ListString,i)) に直接ジャンプするのに効果的な情報はなく、先行する $LIST の要素をまず最初にスキャンする必要があることを意味します。  $LIST のすべての要素をスキャンする場合は、以下のコードを使ってはいけません     

Set N=$LISTLENGTH(ListString)
For i=1:1:N {
   Write $LIST(ListString,i),!
}

なぜなら、上の ObjectScript コードは、$LIST の値を O(N**2) の時間計算量でループするからです。  代わりに以下を使います。

Set P=0
While $LISTNEXT(ListString,P,value) {
    Write value,!
}

このコードは $LIST の値を O(N) の時間計算量でループします。  

%DynamicArray クラス

最近では、%DynamicArray クラス (および %DynamicObject クラス) が作成されています。  今は、はるかに大きなメモリを使用できるようになり (現在のメモリは $LIST の文字列がデザインされた時代のディスクドライブよりも大きくなっている)、ディスクドライブも大幅にサイズアップしています。  今は構造化されたデータを異なるシステム間で送信するための標準的な形式があります。  これらには、XML や JSON が含まれます。  %DynamicArray クラス (および %DynamicObject クラス) には、JSON の値と ObjectScript の値 (oref 型の値も含む) を正確に表現する機能があります。  JSON の値と ObjectScript の値は非常によく似ている上に、1 つの種類の値が別の種類の値に変換されても、形式はほとんど変わりません (例外は、ObjectScript の oref 型の値で、JSON 特有の表現には変換できません)。

JSON の仕様では、JSON 配列リテラルは角括弧で囲まれると説明されています。  例えば、 [0.1,"One tenth",2.99792E8,"speed of light in m/s"] のように囲みます。  JSON のオブジェクトリテラルは、中括弧で囲まれます。  例えば、 {"Name":"IRIS", "Size":64} のように囲みます。  ObjectScript 言語では、JSON の配列リテラルとオブジェクトリテラルを使用できる他、JSON 配列または JSON オブジェクトが持つ要素の値を丸括弧で囲み、ObjectScript のランタイム式として使用できるという拡張機能が 1 つあります。  例えば、 [(.1),("One " _ "tenth"),(2.99793*1000)] のようにできます。  丸括弧の中では、JSON の構文の代わりに ObjectScript の構文が使用されることに注意してください。

$LIST の文字列と %DynamicArray オブジェクトにはいくつか相違点があります。 (%DynamicObject オブジェクトのプロパティは %DynamicArray オブジェクトのプロパティと似ているので、以下のディスカッションで %DynamicObjectについて毎回言及するのは控えます。)

%DynamicArray の最初の要素は、インデックス 0 である一方で、$LIST の最初の要素はインデックス 1 となります。

%DynamicArray 要素は、ObjectScript の式で評価されるか、JSON 文字列に変換されるまでは、それが持つ元々の JSON の値または元々の ObjectScript の値を正確に表現でき、変換オペレーションにより小さな変化が生じることは一切ありません。

$LIST の文字列の最大サイズ (結果的にその要素のサイズ) は、現時点で 3641144 文字というObjectScript 文字列の最大長により制限されています。  %DynamicArray の最大サイズ (結果的にその要素のサイズ) はIRIS プロセスのメモリ空間のサイズにのみ制限されます。  これを踏まえ、多数の大きな %DynamicArray オブジェクトを同時に持つことは避けてください。これは、仮想アドレス空間を無制限で使用すると、過度のメモリーページングを引き起こす可能性があり、システムのパフォーマンスに影響するためです。

$LIST の文字列は、ObjectScript の文字列を格納できる場所であれば、どこにでも格納できます。 これは、文字列の長さが 3641144 文字をオーバーしないローカル変数やグローバル変数を含んでいます。  %DynamicArray オブジェクトをグローバル変数、ストリーム、またはファイルに移動する前には、%DynamicArray を他のデータ型 (通常は、JSON の表現を使用する文字列) に変換する必要があります。  JSON 文字列を持つ ObjectScript の文字列を返すには、引数なしの %ToJSON() メソッドを使用できます。  しかし、%DynamicArrays が大きいと、ObjectScript のグローバル変数や ObjectScript 式の中に収まりきらないほど長い JSON の文字列が生成される場合があります。  この場合、%ToJSON(output) メソッドを実行すると、%DynamicArray が JSON 文字列に変換され、%ToJSON メソッドの引数によって指定される %Stream もしくはファイルに送付されます。

新しい %DynamicArray を効率よく作成するには、ObjectScript コンストラクタを使ったり、%FromJSON(input) メソッドを呼び出したりすると良いでしょう。  %DynamicArray のコンポーネントを調べる際に %Get メソッドを使用するのも効率の良い方法です。  特に、$LIST(ListVar,i) を評価する場合とは違い、DynArray.%Get(i) の評価にかかる時間が 'i' の値や DynArray.%Size() の値に左右されない点に効率の良さが伺えます。  例えば、ObjectScript を使った次のループを実行すると、

Set N=DynArray.%Size()
For i=O:1:N-1 {
    Write DynArray.%Get(i),!
}

%DynamicArray に含まれるすべての要素が O(N) の時間計算量で出力され、$LIST(ListVar,i) メソッドを使いアクセスした要素が出力されるループを実行した場合に発生する O(N**2) の時間計算量は避けることができます。

次のようなループを書くこともできます。

Set iter = DynObject.%GetIterator()
While iter.%GetNext(.key , .value ) {
    Write "key = "_key_" , value = "_value,!
}

このループは、'DynObject' の未定義の要素をスキップする上に、%DynamicObject の要素と %DynamicArray の要素を出力するのに便利でもあります。

しかし、既に作成済みの %DynamicArray の内部要素を変更するのに 'Do DynArray.%Set(i,newvalue)' を使用すると、DynArray に割り当てられたメモリの圧縮がある程度必要になる可能性があるほか、様々な内部インデックスの変更が必要になると思われるため、処理に時間がかかる可能性があります。  配列要素を大幅に変更する場合は、ObjectScript の多次元配列変数を使ってデータを表す方が無難と言えます。それは、ObjectScript の多次元配列は、配列要素の変更、挿入、削除に必要な時間を最小限に短縮できるようデザインされているためです。

IRIS 2019.1 では、%Get(key,default,type) メソッドと %Set(key,value,type) メソッドの機能が拡張されています。  引数 'default' と 'type' は省略可能です。  'default' 引数には、指定された 'key' を持つ要素が未定義である場合に、DynObject.%Get(key,default) が返す値が入ります。  'type' パラメータとして使用できる値に、"stream" があります。これを使うことで、文字列値要素が ObjectScript の文字列に収まりきらないほど大きい場合に、%DynamicArray/%DynamicObject 要素の値を %Stream オブジェクトとして取得することができます。  'type' パラメータには、"json" という値も使用できます。これは、ObjectScript の値に使用される表現への変換を防止する JSON 仕様に従ってフォーマットされた文字列にアクセスします。 詳細は、クラスリファレンスのウェブページをご覧ください。  サポートされる 'type' の値は、IRIS の今後のリリースでさらに追加されると思われます。 

0
0 976
記事 Mihoko Iijima · 12月 20, 2020 1m read

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

ミラーリングが同期の対象とするのはデータベースファイルのみです。

アプリケーションに必要なその他のファイル(CSPファイル、画像ファイル、ドキュメントファイルなど)をミラーセットを構成する二台のサーバー間で同期させるには、

  1. NASなどを導入して共有ディスク上にそれらのファイルを配置する方法
  2. または同期ソフトを導入して二台のサーバー間のファイルを同期させる方法

などの方法が考えられます。 また、2の方法では Windows 上では RoboCopy、Linuxの場合には rsync という同期ソフトを使った実例があります。

0
0 101
記事 Mihoko Iijima · 12月 20, 2020 3m read

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

揮発性テーブル(多数のINSERT、DELETEが行われるテーブル)では、ビットマップ・インデックス用ストレージは徐々に効率が低下する可能性があります。

例えば、以下の定義からなるデータが数千件あり、一定期間保持した後 TRUNCATE TABLE で一括削除を行うオペレーションが繰り返し行われているとします。

Class MyWork.MonthData Extends (%Persistent, %Populate)
{
/// 満足度
Property Satisfaction As %String(VALUELIST = ",満足,やや満足,やや不満,不満,");
/// 年齢
Property Age As %Integer(MAXVAL = 70, MINVAL = 20);
Index AgeIdx On Age [ Type = bitmap ];
}

 

INSERT によってできたビットマップ・インデックスのストレージのイメージ(一部)は以下の通りです。

0
0 299
記事 Megumi Kakechi · 12月 17, 2020 1m read

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

選択性(または Selectivity)の数値とは、カラムに対するユニークデータの割合を示す数値です。

例) Person テーブルの個別値である PID カラム、住所のうち都道府県名が入る Pref カラム
 Pref カラムの選択性 = 約 2%
 PID カラム(ユニーク属性のカラム)の選択性 = 1

InterSystems製品のクエリオプティマイザは、クエリ実行経路(プラン)とインデックスの選択を決定するため、エクステントサイズ(テーブル内のレコード数)と選択性の数値を使用します。

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

テーブルの最適化【IRIS】

テーブルの最適化

なお、選択性の数値は、テーブル単位に計算するまで設定されていません。

計算方法については関連トピックをご参照ください。

【関連情報】(コミュニティ/FAQトピックをリンクしています)

0
0 386
記事 Toshihiko Minamoto · 11月 12, 2020 15m read

Prometheus時系列データの収集に適した監視システムです。

このシステムのインストールと初期構成は比較的簡単です。 このシステムにはデータ視覚化用の PromDashと呼ばれる画像サブシステムが組み込まれていますが、開発者は Grafana と呼ばれる無料のサードパーティ製品を使用することを推奨しています。 Prometheus は多くの要素(ハードウェア、コンテナ、さまざまな DBMS の構成要素)を監視できますが、この記事では Caché インスタンス(正確に言えば Ensemble インスタンスですが、メトリックは Caché からのものになります)の監視に注目したいと思います。 ご興味があれば、このまま読み進めてください。

非常に単純なケースでは、Prometheus と Caché は単一のマシン(Fedora Workstation 24 x86_64)上に存在します。 Caché のバージョンは以下のとおりです。

%SYS>write $zv
Cache for UNIX (Red Hat Enterprise Linux for x86-64) 2016.1 (Build 656U) Fri Mar 11 2016 17:58:47 EST

インストールと構成

公式サイトから適切な Prometheus の配布パッケージをダウンロードし、/opt/prometheus フォルダーに保存してください。

アーカイブを解凍し、必要に応じてテンプレート構成ファイルを変更してから Prometheus を起動します。 Prometheus はデフォルトでコンソールにログを表示するため、ここではアクティビティレコードをログファイルに保存することにします。

Prometheus の起動

# pwd
/opt/prometheus
# ls
prometheus-1.4.1.linux-amd64.tar.gz
# tar -xzf prometheus-1.4.1.linux-amd64.tar.gz
# ls
prometheus-1.4.1.linux-amd64 prometheus-1.4.1.linux-amd64.tar.gz
# cd prometheus-1.4.1.linux-amd64/
# ls
console_libraries consoles LICENSE NOTICE prometheus prometheus.yml promtool
# cat prometheus.yml
global:
  scrape_interval: 15s # スクレイプ間隔を 15 秒に設定します。 デフォルトは 1 分ごとです。
scrape_configs:
  - job_name: 'isc_cache'
    metrics_path: '/metrics/cache'
    static_configs:
    - targets: ['localhost:57772']

# ./prometheus > /var/log/prometheus.log 2>&1 &
[1] 7117
# head /var/log/prometheus.log
time=«2017-01-01T09:01:11+02:00» level=info msg=«Starting prometheus (version=1.4.1, branch=master, revision=2a89e8733f240d3cd57a6520b52c36ac4744ce12)» source=«main.go:77»
time=«2017-01-01T09:01:11+02:00» level=info msg=«Build context (go=go1.7.3, user=root@e685d23d8809, date=20161128-09:59:22)» source=«main.go:78»
time=«2017-01-01T09:01:11+02:00» level=info msg=«Loading configuration file prometheus.yml» source=«main.go:250»
time=«2017-01-01T09:01:11+02:00» level=info msg=«Loading series map and head chunks...» source=«storage.go:354»
time=«2017-01-01T09:01:11+02:00» level=info msg=«23 series loaded.» source=«storage.go:359»
time=«2017-01-01T09:01:11+02:00» level=info msg="Listening on :9090" source=«web.go:248»

prometheus.yml 構成ファイルは YAML 言語で記述されているため、タブ文字の使用は好ましくありません。したがって、スペースのみを使用する必要があります。 また、すでにお伝えしたとおり、メトリックは http://localhost:57772 からダウンロードされ、リクエストは /metrics/cache に送信されます(アプリケーション名は任意)。したがって、メトリック収集用の宛先アドレスは http://localhost:57772/metrics/cache になります。 「job = isc_cache」タグが各メトリックに追加されます。 大まかに言えば、タグは SQL の WHERE に相当するものです。 ここではタグを使用しませんが、複数のサーバーを使用する場合は役に立つでしょう。 例えばサーバー(またはインスタンス)の名前をタグに保存し、タグを使用してグラフ描画用のリクエストをパラメーター化することができます。 では、Prometheus が動作していることを確認しましょう(上記の出力では、9090 番ポートでリッスンしていることが分かります)。

Web インターフェイスが開きます。これは、Prometheus が機能していることを意味します。 ただし、Caché のメトリックはまだ表示されていません([Status] → [Targets] をクリックして確認しましょう)。

メトリックの準備

目標は、Prometheus が http://localhost:57772/metrics/cache適切なフォーマットでメトリックにアクセスできるようにすることです。 ここではその単純さを考慮し、Caché の REST 機能を使用します。 Prometheus は数値メトリックのみを「理解」するため、ここでは文字列メトリックをエクスポートしません。 後者を取得するには、SYS.Stats.Dashboard クラスの API を使用します。 このようなメトリックは、Caché 自体がシステムツールバーを表示する目的で使用されています。

同じ内容をターミナルで表示した例:

%SYS>set dashboard = ##class(SYS.Stats.Dashboard).Sample()  
 
%SYS>zwrite dashboard
dashboard= <OBJECT REFERENCE> [2@SYS.Stats.Dashboard]
+----------------- general information ---------------
|      oref value: 2
|      class name: SYS.Stats.Dashboard
| reference count: 2
+----------------- attribute values ------------------
|  ApplicationErrors = 0
|        CSPSessions = 2
|    CacheEfficiency = 2385.33
|      DatabaseSpace = "Normal"
|          DiskReads = 14942
|         DiskWrites = 99278
|       ECPAppServer = "OK"
|      ECPAppSrvRate = 0
|      ECPDataServer = "OK"
|     ECPDataSrvRate = 0
|            GloRefs = 272452605
|      GloRefsPerSec = "70.00"
|            GloSets = 42330792
|     JournalEntries = 16399816
|       JournalSpace = "Normal"
|      JournalStatus = "Normal"
|         LastBackup = "Mar 26 2017 09:58AM"
|     LicenseCurrent = 3
|  LicenseCurrentPct = 2
. . .

ここでは USER スペースがサンドボックスになります。 まず、REST アプリケーションの /metrics を作成しましょう。 非常に基本的な安全対策を行うため、ログインをパスワードで保護し、Web アプリケーションをリソースに関連付けます。このようなリソースを PromResource と呼びましょう。 リソースへの公開アクセスを無効にするため、次の内容を実行してください。

%SYS>write ##class(Security.Resources).Create("PromResource", "Resource for Metrics web page", "")
1

Web アプリの設定:

このリソースにアクセスできるユーザーも必要です。 このユーザーもデータベース(この場合は USER)から読み取り、そこへデータを保存できる必要があります。 また、別件ですがコードの後半部では %SYS スペースに切り替えるため、このユーザーには CACHESYS システムデータベースの読み取り権限が必要になります。 ここでは標準のスキームに従います。すなわち、これらの権限を持つ PromRole ロールを作成した後にこのロールに割り当てられた PromUser ユーザーを作成します。 パスワードには「Secret」を使いましょう。

%SYS>write ##class(Security.Roles).Create("PromRole","Role for PromResource","PromResource:U,%DB_USER:RW,%DB_CACHESYS:R"
1
%SYS>write ##class(Security.Users).Create("PromUser","PromRole","Secret")
1

Prometheus の構成では、この PromUser ユーザーを認証に使用します。 完了後はサーバープロセスに SIGNUP シグナルを送信し、構成を再読み込みします。

より安全な構成

# cat /opt/prometheus/prometheus-1.4.1.linux-amd64/prometheus.yml
global:
  scrape_interval: 15s # スクレイプ間隔を 15 秒に設定します。 デフォルトは 1 分ごとです。
 
scrape_configs:
  - job_name: 'isc_cache'
    metrics_path: '/metrics/cache'
    static_configs:
    - targets: ['localhost:57772']
    basic_auth:
      username: 'PromUser'
      password: 'Secret'

#
# kill -SIGHUP $(pgrep prometheus) # または kill -1 $(pgrep prometheus)

以上で Prometheus がメトリックを含む Web アプリケーションを使用するための認証をパスできるようになりました。

メトリックは、my.Metrics リクエスト処理クラスによって提供されます。以下にその実装を示します。

Class my.Metrics Extends %CSP.REST
{

Parameter ISCPREFIX = "isc_cache";

Parameter DASHPREFIX = {..#ISCPREFIX_"_dashboard"};

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/cache" Method="GET" Call="getMetrics"/>
</Routes>
}

/// 出力は Prometheus の出力フォーマットに従う必要があります。 ドキュメントは以下で確認できます。
/// https://prometheus.io/docs/instrumenting/exposition_formats/
/// 
/// このプロトコルは行指向です。 改行文字(\n)は行を区切ります。 
/// 最後の行は改行文字で終了する必要があります。 空の行は無視されます。
ClassMethod getMetrics() As %Status
{
    set nl = $c(10)
    do ..getDashboardSample(.dashboard)
    do ..getClassProperties(dashboard.%ClassName(1), .propList, .descrList)
    
    for i=1:1:$ll(propList) {
        set descr = $lg(descrList,i)
        set propertyName = $lg(propList,i)
        set propertyValue = $property(dashboard, propertyName)
        
        // Prometheusは時系列データベースをサポートします。 
        // そのため、空(バックアップメトリックなど)や非デジタルメトリックを 
        // 取得した場合はそれらを単に省略します。
        if ((propertyValue '= "") && ('$match(propertyValue, ".*[-A-Za-z ]+.*"))) {
            set metricsName = ..#DASHPREFIX_..camelCase2Underscore(propertyName)
            set metricsValue = propertyValue
            
            // 各メトリックの説明(ヘルプ)を記述します。
            // フォーマットはPrometheusが要求するものです。
            // 複数行の説明は1つの文字列に結合する必要があります。
            write "# HELP "_metricsName_" "_$replace(descr,nl," ")_nl
            write metricsName_" "_metricsValue_nl
        }
    }
    
    write nl
    quit $$$OK
}

ClassMethod getDashboardSample(Output dashboard)
{
    new $namespace
    set $namespace = "%SYS"
    set dashboard = ##class(SYS.Stats.Dashboard).Sample()
}

ClassMethod getClassProperties(className As %String, Output propList As %List, Output descrList As %List)
{
    new $namespace
    set $namespace = "%SYS"
    
    set propList = "", descrList = ""
    set properties = ##class(%Dictionary.ClassDefinition).%OpenId(className).Properties
    
    for i=1:1:properties.Count() {
        set property = properties.GetAt(i)
        set propList = propList_$lb(property.Name)
        set descrList = descrList_$lb(property.Description)
    }
}

/// キャメルケースのメトリック名を小文字のアンダースコア名に変換します
/// 例: 入力 = WriteDaemon、出力 = _write_daemon
ClassMethod camelCase2Underscore(metrics As %String) As %String
{
    set result = metrics
    set regexp = "([A-Z])"
    set matcher = ##class(%Regex.Matcher).%New(regexp, metrics)
    while (matcher.Locate()) {
        set result = matcher.ReplaceAll("_"_"$1")
    }
    
    // 小文字にします
    set result = $zcvt(result, "l")
    
    // _e_c_p (_c_s_p) を _ecp (_csp) にします
    set result = $replace(result, "_e_c_p", "_ecp")
    set result = $replace(result, "_c_s_p", "_csp")
    
    quit result
}
}

コンソールを使用して、私たちの作業が無駄ではなかったことを確認しましょう(curl がプログレスバーの表示を邪魔しないように --silent キーを追加しました)。

# curl --user PromUser:Secret --silent -XGET 'http://localhost:57772/metrics/cache' | head -20
# HELP isc_cache_dashboard_application_errors Number of application errors that have been logged.
isc_cache_dashboard_application_errors 0
# HELP isc_cache_dashboard_csp_sessions Most recent number of CSP sessions.
isc_cache_dashboard_csp_sessions 2
# HELP isc_cache_dashboard_cache_efficiency Most recently measured cache efficiency (Global references / (physical reads + writes))
isc_cache_dashboard_cache_efficiency 2378.11
# HELP isc_cache_dashboard_disk_reads Number of physical block read operations since system startup.
isc_cache_dashboard_disk_reads 15101
# HELP isc_cache_dashboard_disk_writes Number of physical block write operations since system startup
isc_cache_dashboard_disk_writes 106233
# HELP isc_cache_dashboard_ecp_app_srv_rate Most recently measured ECP application server traffic in bytes/second.
isc_cache_dashboard_ecp_app_srv_rate 0
# HELP isc_cache_dashboard_ecp_data_srv_rate Most recently measured ECP data server traffic in bytes/second.
isc_cache_dashboard_ecp_data_srv_rate 0
# HELP isc_cache_dashboard_glo_refs Number of Global references since system startup.
isc_cache_dashboard_glo_refs 288545263
# HELP isc_cache_dashboard_glo_refs_per_sec Most recently measured number of Global references per second.
isc_cache_dashboard_glo_refs_per_sec 273.00
# HELP isc_cache_dashboard_glo_sets Number of Global Sets and Kills since system startup.
isc_cache_dashboard_glo_sets 44584646

これで、Prometheus のインターフェースで同じ内容を確認できるようになりました。

以下は上記メトリックのリストです。

これらのメトリックの Prometheus での表示内容については詳述しません。 必要なメトリックを選択して「Execute」ボタンをクリックしてください。 「Graph」タブを選択すると、グラフが表示されます(キャッシュの効率が表示されます)。

メトリックの視覚化

メトリックを視覚化するため、Grafana をインストールしましょう。 この記事では、tarball からインストールすることにしました。 ただし、パッケージからコンテナまで、他のインストール方法もあります。 次の手順を実行してみましょう(/opt/grafana フォルダーを作成し、そこに切り替えた後)。

とりあえず設定は変更せずにそのままにしておきましょう。 最後のステップでは、Grafana をバックグラウンドモードで起動します。 Prometheus の場合と同じように、Grafana のログをファイルに保存します。

# ./bin/grafana-server > /var/log/grafana.log 2>&1 &

デフォルトでは、3000 番ポートで Grafana の Web インターフェースにアクセスできます。 ログイン/パスワードは、admin/admin です。

詳細な Prometheus と Grafana の連携手順については、こちらを参照してください。 簡単に言えば、Prometheus タイプの新しいデータソースを追加する必要があります。 また、次のように direct/proxy アクセスのオプションを選択してください。

完了後、必要なパネルを含むダッシュボードを追加する必要があります。 ダッシュボードのテストサンプルは、メトリック収集クラスのコードと共に公開されています。 ダッシュボードは Grafana に簡単にインポートできます([Dashboards] → [Import])。

インポート後、次のようになります。

ダッシュボードを保存します。

時間範囲と更新間隔は右上で選択できます。

監視種類の例

グローバルへの呼び出しの監視をテストしてみましょう。

USER>for i=1:1:1000000 {set ^prometheus(i) = i}
USER>kill ^prometheus

以下のグラフでは、1秒あたりのグローバルへの参照数が増加してキャッシュ効率が低下していることが分かります(^Prometheus グローバルがまだキャッシュされていない)。

ライセンスの使用状況を確認しましょう。 そのためには、次のように PromTest.csp というプリミティブな CSP ページを USER ネームスペースに作成しましょう。


監視は正常に機能しています!

そして、何度もアクセスしてください(/csp/user アプリケーションがパスワード保護されていないことを想定しています)。

# ab -n77 http://localhost:57772/csp/user/PromTest.csp

ライセンスの使用状況について、次の図が表示されます。

まとめ

ご覧のとおり、監視機能の実装はまったく難しくありません。 いくつかの初期手順を実行しただけでも、ライセンスの使用状況、グローバルキャッシュの効率、アプリケーションエラーなど、システムの動作に関する重要な情報を取得できます。 このチュートリアルでは SYS.Stats.Dashboard を使用しましたが、SYS / %SYSTEM / %SYS パッケージの他のクラスも注目に値します。 また、特定タイプのドキュメントの数など、独自アプリケーションのカスタムメトリックを提供する独自のクラスを作成することもできます。 いくつかの有用なメトリックは、最終的に Grafana 用の個別テンプレートにコンパイルされます。

今後の予定

本件についてより詳細な情報が必要な場合は、このテーマを詳しく説明するつもりです。 以下に私の予定を記しておきます。

  1. ログデーモンのメトリックを含む Grafana テンプレートの準備について。 ^mgstat と同等の、少なくともそのメトリックに対応した何らかのグラフィカルツールを作成するのが望ましいと考えています。

  2.   <li>
        <p>
          Web アプリケーションのパスワード保護は優れていますが、証明書を使用できる可能性を確認するのが望ましいと考えています。
        </p>
      </li>
      
      <li>
        <p>
          Prometheus、Grafana、および Prometheus を Docker コンテナとしてエクスポートするツールの使用について。
        </p>
      </li>
      
      <li>
        <p>
          新しい Caché インスタンスを Prometheus の監視リストに自動追加するための検出サービスの使用について。 また、Grafana とそのテンプレートの利便性を(実際に)説明したいと考えています。 これは、選択した特定のサーバーのメトリックがすべて同じダッシュボードに表示される動的なパネルのようなものです。
        </p>
      </li>
      
      <li>
        <p>
          Prometheus Alertmanager について。
        </p>
      </li>
      
      <li>
        <p>
          データの保存期間に関連する Prometheus の構成設定、および多数のメトリックと短い統計収集間隔を持つシステムに考えられる最適化について。
        </p>
      </li>
      
      <li>
        <p>
          途中で発生するさまざまで微妙な差異について。
        </p>
      </li>
    </ol>
    

    リンク

    この記事を準備中にいくつかの有益なサイトにアクセスし、次のようなたくさんの動画を視聴しました。

  <li>
    <p>
      <a href="http://grafana.org/">Grafana プロジェクトの Web サイト</a>
    </p>
  </li>
  
  <li>
    <p>
      <a href="https://www.robustperception.io/blog/">Brian Brazil という Prometheus 開発者のブログ</a>
    </p>
  </li>
  
  <li>
    <p>
      <a href="https://www.digitalocean.com/community/tutorials/how-to-use-prometheus-to-monitor-your-ubuntu-14-04-server">DigitalOcean のチュートリアル</a>
    </p>
  </li>
  
  <li>
    <p>
      <a href="https://www.youtube.com/channel/UCtiFWOeRSTP3M6QUnTEKwpw">Robust Perception の動画数点</a>
    </p>
  </li>
  
  <li>
    <p>
      <a href="https://www.youtube.com/channel/UC4pLFely0-Odea4B2NL1nWA/videos">Prometheus を対象とする多数のカンファレンス動画</a>
    </p>
  </li>
</ul>

最後までお読みいただき、ありがとうございました!

3
0 628
記事 Hiroshi Sato · 12月 16, 2020 4m read

この文書では、インターシステムズの製品の中で、InterSystems CachéおよびIRIS data platformに関するライセンスポリシーを説明します。


インターシステムズのライセンスポリシーは、ソリューション・パートナー契約の際の契約書一式あるいはエンドユーザーに直接販売する際の契約書一式に含まれる製品に関する条件(この文書は製品別に存在します)という文書で規定されています。

なおこの文書は一般には公開していません。

ここでは、CachéおよびIRIS data platformのライセンスポリシーについてこの文書に書かれていることを要約および補足して説明します。

まずCachéシステムおよびIRIS data platformはこの文書で規定されているライセンスポリシーにでき得る限り忠実にそうようにライセンスチェック機構を実装しています。

しかしながら様々な技術的な制約によりライセンスポリシーとこれら製品のライセンスシステムを完全に一致させることはできません。

そしてもしシステム上のライセンスチェック機構の動作とライセンスポリシー上に不一致が発生した場合には、いかなる場合でもライセンスポリシーが優先されます。

つまりライセンスシステム上許容されている動作であっても、ライセンスポリシーに合致していない場合には、ライセンスポリシーに合うような運用を行わなければなりません。

0
1 1435
記事 Hiroshi Sato · 12月 16, 2020 2m read

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

SQLインジェクションに関しては、様々なWebサイトで対策等が公開されていますが、InterSystems SQLを使ったアプリケーションでも、他のRDBMSと同様にそれらの対策を適切に実施することで、SQLインジェクションは防げると考えられます。

さらに、InterSystems Data Platform(以下IRIS)の場合、一般的なRDBMSに比較して、SQLインジェクションを実行しづらい、幾つかの施策が組み込まれています。

0
0 239
記事 Hiroshi Sato · 12月 15, 2020 3m read

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

InterSystems OpenExchangeのVS Code用のプラグインを利用することでVS Code上でクラス定義の編集が可能です。

(今後は、AtelierではなくVS Codeの使用を推奨しています。)


詳細は、以下ページをご参照ください。


vscode-objectscript

また、逆にテーブル定義からクラス定義を生成することも可能です。

方法①として、他社RDBMS用に作成したDDL文をインターシステムズ製品上で実行、またはインポートする方法があります。


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


DDL文の実行について【IRIS】
DDL文の実行について

方法②として、クラス定義クラス・%Dictionary.ClassDefinitionで定義されているAPIを利用し、プログラミングにより操作する方法があります。

クラス定義クラスは、システムクラスであり、スタジオや他のエディターで作成されたクラスは全てこのシステムクラスを使いクラスディクショナリに定義情報が格納されています。

このクラス詳細については、以下ドキュメントをご参照ください。

0
0 332
記事 Megumi Kakechi · 12月 15, 2020 1m read

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

%SYSTEM.SQL クラスの Purge* メソッドを使用して削除することが可能です。

※各メソッドの詳細は、以下ドキュメントをご参照ください。

%SYSTEM.SQLクラスについて【IRIS】

%SYSTEM.SQLクラスについて

①システム内のすべてのクエリキャッシュを削除する場合

Do $SYSTEM.SQL.PurgeAllNamespaces()


②ネームスペース内のクエリキャッシュを削除する場合

// ネームスペース内のすべてのクエリキャッシュを削除する
Do $SYSTEM.SQL.Purge()
// 日付指定してクエリキャッシュを削除する場合
// 以下は、過去30日間に使用されていないクエリキャッシュを削除

Do $SYSTEM.SQL.Purge(30) 

③クエリキャッシュを指定して削除する場合

Do $SYSTEM.SQL.PurgeCQClass("%sqlcq.USER.cls13")


④特定テーブルのクエリキャッシュを削除する場合

Do $SYSTEM.SQL.PurgeForTable("MedLab.Patient")
0
0 525
記事 Mihoko Iijima · 12月 15, 2020 1m read

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

デフォルトでは、セキュリティ脆弱性対応の観点でウェブサービス用テストページの実行を許可していません。

テスト目的等で利用する場合は、テストページへのアクセスを有効にする必要があり、以下グローバル変数のセットを %SYS ネームスペースで実行する必要があります。

set ^SYS("Security","CSP","AllowClass",0,"%SOAP.WebServiceInvoke")=1
set ^SYS("Security","CSP","AllowClass",0,"%SOAP.WebServiceInfo")=1

 

詳細については、以下のドキュメントもご参照下さい。

カタログおよびテスト・ページについて【IRIS】

カタログおよびテスト・ページについて 

0
0 163
記事 Minoru Horita · 12月 14, 2020 14m read

優れた執筆者は、題名で読者を引き込み、答えを記事のどこかに隠すべきだと考えられています。  だとすると、私は出来の悪い執筆者ということになってしまいます。私の自尊心は見知らぬインターネットユーザーの皆さんのご意見にかかっていますので、お手柔らかにお願いします。

私の同僚 Brendan が Developer Community に記載されている情報をレビューしていたとき、Tune Table について書き忘れがあったことに気が付いたのです!  これはクエリパフォーマンスにおいて 2番目に重要なツール (1 番はインデックス、インデックスがないと処理が遅い) なので、時間をかけて理解しておく価値があります。  今回使用する例の多くは完全なものではありませんが、詳細は簡単に入力できます。 すべての例において、特に明記が無い限り、フィールドはすべて個別にインデックスが作成されているものと想定してください。

それでは、以下のクエリがあるとしましょう。
SELECT *
FROM   People
WHERE  Home_State = 'MA'
       AND PersonId = '123-45-6789' 

どのインデックスを使うべきでしょうか?  冗談なしで、一旦読むのをやめて、答えを聞かせてください。  大声で答えてください。  同僚の皆さんに変な人だなんて思われませんよ、大丈夫です。
正解は、もちろん、PersonId のインデックスです。  なぜこれが正解なのか?  各 State (州) には何百万もの人々が生活している可能性がある中で、PersonId はほぼ一意の数値 (的な) 識別子であることが分かります。  ここで理解する必要があるのは、この答えが分かるのは、列の識別子を見るとスキーマに関する情報がある程度は分かるためである、という点です。  コンピューターにとって、これらの名前は何の意味もなしません。  この点を分かりやすく説明するために、こちらのクエリを見てみましょう。
SELECT *
FROM   TheTable
WHERE  Field1 = 32
       AND Field2 = 0 

使うのはインデックスは Field1?それとも Field2?  上のクエリは、クエリオプティマイザによって最適化されるようなクエリです。  この場合はどのようにインデックスを選べばいいのでしょう?  適切な決断をするにはデータについて知る必要があります。  これを解決するのが Tune Table です。 

Tune Table は、テーブル内のデータサンプルを調べ、テーブルに関する統計を保管することで、クエリオプティマイザが適切な決断をできるようにします。  今まで Tune Table を実行したことが無い場合は、クエリがランダムでも効果的にインデックスを選択しているということです (厳密には違いますが、ほぼそういうことです)。  実際、クエリが遅いことについてサポートに電話されることがあれば、まず最初に「Tune Table は実行されましたか?」と聞かれると思います。  ‘はい’ と言えるようにしておいてください。 

Tune Table の実行方法
Tune Table を実行するには、ターミナルに移動して、次を実行します。
d $SYSTEM.SQL.TuneTable(<Table>)
これに渡せるフラグは沢山ありますが、私のおすすめはこちらです。

d $SYSTEM.SQL.TuneTable(<Table> ,1,1,.msg,1)


 

これらのフラグにより、クラス定義が更新され、新しい値が表示されるほか、最後のフラグによりクラスが最新の状態に維持されます。  クエリが Tune Table の新しい情報を利用できるようにするには、すべての埋め込みクエリをコンパイルし、キャッシュされているクエリを消去する必要があります。  フラグに関する詳細は、こちらをお読みください。
https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL#METHOD_TuneTable

Tune Table の機能
Tune Table の実行ステップを紹介します。  まず最初に、SELECT COUNT(*) を実行してテーブル内の行数を計算します。  その行数を基に、サンプルとして使用する行数を決定します (漸近的には行数の平方根に似ています)。  そして、テーブルで定義されている各 ID に対し、加重サンプルを適切に判断した上で、その行をサンプルに含めるかどうかを決定します。  このサンプルを使って統計を算出し、それを使ってクエリを最適化します。

ここには重要なポイントが 2 つあります。  1 つ目はカウントを得る必要があること。  2 つ目は、テーブル内の各 ID を取得できる必要があることです。  この結果、エクステントインデックスがあることにより、Tune Table の実行が大幅に改善されるのです。  テーブルがビットマップに対応している (つまり正の整数 IDKEY がある) 場合は、ビットマップのインデックスがあれば、自動的にビットマップのエクステントを得ることができます。 


Tune Table はどのような指標を測定するのか?  年代順に古い方から説明していきます。

2 つの OG (旧世代の指標)

次に紹介する 2 つの指標は Tune Table の最も古い指標です。  どれだけ古いと思いますか?  私が高校に入学する前 (1999年) に追加されたものです。  Y2K 以前の Caché を実行している方がおられましたら、私までメールをください。是非、お話しさせていただきたいです。
Extent Size は、テーブル内の行数を測定します。 

これは一番理解しやすい指標で、JOIN を実行する順序を決定するのに便利です。  では、下のクエリがあるとしましょう。
SELECT *
FROM   Table1 T1
       JOIN Table2 T2
         ON T1.Field1 = T2.Field1 

T1 を読み取ってから、T2 のフィールドで JOIN を実行するべきか、またはその逆にするべきか? Extent Size は、行の数が少ない方のテーブルを示すので、どちらのテーブルから始めるべきなのかが分かります。  極端なケースとして、T1 には 100 億行あり、T2 には 100 行しかないと仮定します (T1 はこれまで病院に行った回数の合計を示すテーブルで、T2 は現在病院にいる患者の数を示すテーブルだと考えてください)。 この場合は、小さい方のテーブルから始め、大きな方で JOIN を実行すると良いでしょう。それは、JOIN を実行すると、読み取る必要のある行の数が制限されるためです。 

Selectivity は、指定されたフィールドの値の一意性を測定します。
この指標を理解するにはもう少し説明が必要です。  考え方は 2 種類あります。

  1. 1/x。x はフィールドに設定され得る値の数。
  2. クエリ SELECT * FROM T WHERE field=? の実行結果として返されるテーブルの平均パーセンテージ

いずれの説明もこれだけでは分かりにくいので、いくつか例を挙げてはっきりさせたいと思います。 

先ほどのクエリに、Home_State というフィールドがありました。  (アメリカの場合だと) そのフィールドの Selectivity は 2.0% になります。  なぜかと言いますと、 アメリカには 50 の州があるため、1/50 = .02 = 2% という計算になります。  これは簡単ですね。  しかし、分布はどうなのか?  明らかに、複数の州で人口が均等に分布しているはずがありません!  それは事実なのですが、Selectivity はそれを考慮しません。 均等な分布は、ほとんどのクエリにおいて適切な予測とされる上に、計算も簡単になるため、クエリはよりスピーディにコンパイルします。
 

もう 1 つの例として、PersonId について考えます。  人は亡くなったり、新しく生まれたりするので、 PersonId はたまに再利用する必要があるかもしれません。なので、その数字は完全に一意という訳でははありません。  しかし、一意であることに変わりはありません。  そのため、.00001% の Selectivity が見られる場合があるかもしれません。  それは非常に適切な Selectivity と言えます!  クエリ SELECT * FROM People WHERE PersonId = ? は、 実行される度に 1 つまたは 2 つの行を返します。  一般的に、Selectivity は低いほど良いとされています。  しかし、これには例外があります…

一意のフィールドがあるとしたらどうでしょう。ID はいい例ですね。  一意のフィールドの Selectivity は 1 です。  ちなみに、これまで述べた Selectivity はすべてパーセンテージです。  1 はパーセンテージではなく、行数です。  したがい、クエリ SELECT * FROM People WHERE ID = ? は、 いつも 1 行を返します。 

1 つのフィールドについて沢山の情報をカバーしましたが、おそらく一番重要な情報であると言えるでしょう。  原則として、パーセンテージは低い方が良く、1 が最適な値です。 

「昔から」存在していた指標

次に紹介する統計は、いつも予測されてはいたものの、バージョン 2013.1 までは明示的に測定されていませんでした。
Block Count – 各マップ (インデックスとマスターマップ) がディスク上で占めるブロックの数を測定します。 

Block Count は長年に渡って予測してきましたが、2013 年になって初めて明示的に測定する決断をしました。  Extent Size があるので不必要に思えるかもしれませんが、他にも実用性があります。  こちらのクエリ
SELECT COUNT(*) FROM MyTable
を説明すると理解に役立つでしょう。
各フィールドにインデックスが付いているとしたら、どれを読み取ればいいでしょうか?  読み取りたいのは、ディスク上で占めるスペースが一番少ないフィールドです。  この指標はその情報を明示的に示してくれます。  より複雑な状況でインデックスのかたまりを読み取る際のコストを予測するときにも表示されます。  通常、これはマップの幅を測定するのに優れている一方で、Extent Size はその長さを測定するのに優れています。

Block Count は、親 / 子関係を使用する場合にも便利です。  この場合は、(デフォルトで) 両方のテーブルが同じグローバル変数に格納されます。しかし、Block Count がなければ、クエリオプティマイザは、潜在的に小さな親テーブルで読み取られたマップには膨大な数の子が含まれているために、実はとても大きなマップである可能性があるということを全く知ることができません。 実に、これは私たちが新規開発において親 / 子関係の使用を一切おすすめしていない理由の 1 つです (どうしてもという場合は、サポートにお電話の上、ご相談ください!)。
 

Brendan からのメモ / トリビア: Extent Size、Selectivity、Block Count は FDBMS が使われていた時代、すなわち 1992 年には既に存在していたのですが、当時は手動で設定する必要がありました。


すべてを一変させた指標

バージョン 2014.1 では、フィールドの 1 つの値が過剰に大きな割合を占めていないかどうかを判断するための指標を追加しました。  これにより、Selectivity を計算する方法が大幅に変わりました。

Outlier Selectivity (外れ値の選択性) -  テーブル内で過剰な割合を占めるフィールドの選択性。

外れ値を伴う状況を理解するために、病院について考えてみましょう。  病院は一般的に地域のコミュニティにサービスを提供するものです。  Home_State フィールドは、そのほとんどの値が病院のある州で同じになります。  では、MA (マサチューセッツ州) の病院について考えます (私が住んでいる州なので)。  私の地元の病院は、患者の 90% が MA の人たちです。それ以外は他の州から来た人たちや州を訪れている人たちです。  Home_State=’MA’ を検索することは選択的でない一方で、それ以外の Home_State を検索することは選択的である、ということを示す必要があります。  これを実現するのが Outlier Selectivity です。 
例えば、上述した Home_State フィールドだと、Tune Table はその Selectivity を0.04% として算出し、Outlier Selectivity を 90%、Outlier 値 (外れ値) を ‘MA’ として示します。  通常、Selectivity は低下します。  Selectivity の計算方法が変わり、人々をビックリさせたクエリプランもいくつか考案されたこともあり、これは大きな変化であったと言えます。  バージョン 2014.1 全体を更新する予定の方は、この点に注意してください。

最後の指標

この最後の指標は、一時ファイルのサイズをより正確に予測することを目的に、バージョン 2015.2 で追加したものです。
Average Field Size – フィールドの平均サイズ。

これも簡単に理解できる指標です。  サンプルを取る間に、テーブル内の各フィールドの平均サイズを計算してくれます。 これは一時ファイルのサイズを判断するのに便利です。また、これにより、オプティマイザは一時ファイルをメモリ内で構築できるのか (ローカル配列)、またはディスクベースの構造 (プライベートグローバルを処理する) が必要なのかを判断できます。

実用的な検討事項

次に考えるのが「どれくらいの頻度で実行する必要があるのか?」という疑問です。  これといった答えはないのですが、 少なくとも (絶対に!!!) 一度は実行してください。でなければ、自分で値を用意しなくてはいけなくなります。  その後は、もう二度と実行しなくてもよいかもしれません。  現在のパフォーマンスに満足なら、実行する理由はありません、というのが一般的な答えでしょう。  実に、データベースの成熟度が高く、テーブル内のデータも安定したペースで成長しているのなら、今後もおそらく適切な値が得られるでしょう。 

もし、テーブルの変化と共に SQL のパフォーマンスが劣化することがあれば、Tune Table を再度実行することをおすすめします。  また、新しいデータが多く、テーブルが大幅に変化している場合は、Tune Table を実行したほうが無難かもしれません。  しかし、これは「すべてのクエリが遅い」という状況にあれば予測できることです。  この状況に陥っている方は、WRC までお問い合わせください。システム設定を確認させていただきます。

Tune Table を実行する場合は、調整するテーブルに関連しているすべてのテーブルに対して実行してください。 そうしないと、テーブル間で Extent Sizes と Block Counts を比較てきなくなります (これによりクエリのパフォーマンスが低下します)。
2016.2 よりも新しいバージョンをご利用の方は、Tune Table を実行する前にクエリプランをフリーズできる Frozen Query Plans をご利用いただけます。  このプラクティスは、Tune Table を実行したときに、新しいプランが役に立つと判断した場合にそれだけを利用できるという機能があるため、強くお奨めしています (%NOFPLAN を使ってクエリをテストすれば、新しいプランが役に立つのかどうかを確認できます)。 

Frozen Query Plans についてはもう 1 つ大切なことがあり、このテクノロジーを使えば、Tune Table を実行するときに、システム上で既に実行したクエリをもう一度実行してしまうことを避けられます。  これは、現在のパフォーマンスに影響を与えることを心配せずに、Tune Table を実行するための手段です!  もう言い訳はできません! Frozen Query Plans の詳細に興味がある方は、私が以前行ったウェビナーをご覧ください:
https://learning.intersystems.com/course/view.php?id=969

最後になりましたが、クエリパフォーマンスについては、Tune Table の統計を把握しておくことが大切です。  Tune Table は、クエリオプティマイザが適切な判断をするのに必要な情報を与えてくれます。  既にテーブルにインデックスを追加されている方は、次のステップとして Tune Table を実行すれば、クエリを超高速化できること間違いなしです。

今回の記事で使用しました SQL フォーマッタ (http://dpriver.com/pp/sqlformat.htm) を私に紹介してくれた Aaron Bentley に感謝の意を送りたいと思います。
 

また、私の記事を実際に理解できる内容に編集してくれる Brendan Bannon にも感謝いたします。

0
0 312
記事 Hiroshi Sato · 12月 8, 2020 1m read

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

連番を生成する関数($INCREMENT)を使用してユニークな番号を自動付番することができます。

SQLのSELECT文で使用する場合には OracleのSequence相当の機能を実装したクラスを利用する方法があります。

サンプルを以下のGitHubリポジトリより取得することができます。

OracleのSequence機能を実装するサンプル

0
0 478
記事 Mihoko Iijima · 12月 8, 2020 1m read

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

$SYSTEM.Process.TerminalPrompt() を使用してターミナルのプロンプトを変更できます。

プロンプトの表示形式は、以下情報の組み合わせで指定できます。

  1.  ホスト名
  2. 現在のネームスペース
  3. 構成名
  4. 現在の時刻
  5. PID: オペレーティングシステム上でのプロセスID
  6. ユーザーネーム
  7. 最後のコマンドを実行してからの経過時間

例えば、Do $SYSTEM.Process.TerminalPrompt(1,3) ではホスト名と構成名をターミナルのプロンプトとして表示します。 

*実行例は、ホスト名="HOST1"、構成名="IRIS"*

USER>Do $SYSTEM.Process.TerminalPrompt(1,3)
HOST1:IRIS>
0
0 228
記事 Mihoko Iijima · 12月 7, 2020 2m read

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

SELECT ... FOR UPDATE は明示的な行ロック取得の方法として多くの RDBMS で実装されているため、この機能を使われているケースも多いと思います。

このシンタックスは InterSystems 製品ではエラーにはなりませんが、期待されるような行ロックの取得は行われません。

この記事では同等の機能を実現する方法をご紹介します。

DECLARE CURSOR C1 IS
SELECT Name FROM Person WHERE Name LIKE 'A%' FOR UPDATE
OPEN C1
LOOP FETCH C1 INTO name 
... name を表示
... 終了ならLOOPをEXIT
END LOOP
CLOSE C1

 

上記のようなSQL文は、下記のSQL文で代替可能です。

※下記2行目のUPDATE文の実行により対象行に対して排他ロックがかかるため、他DBの動作と異なる点ご注意ください。
 

0
0 491
記事 Toshihiko Minamoto · 12月 7, 2020 10m read

    以前の記事では Arduino を使い始め、最終的には気象観測所のデータを表示できるようになりました。 この記事ではさらに掘り下げ、InterSystems Caché アプリケーションに対して RFID カードと Arduino を介した認証をセットアップします。

 

認証の委任

Caché には認証コードの書き込みを許可することで、認証を委任するための仕組みがあります。 この仕組みを有効にするには、次の手順を実行する必要があります。

  • ZAUTHENTICATE ルーチンにユーザー認証コードを記述します。 このルーチンにはユーザー名/パスワードの取得、それらの検証と権限の割り当て、パスワード変更、トークン生成の 4 つのエントリポイントがあります。 詳細については、以下をお読みください。
    1. Caché で委任認証を有効にします([SMP] → [System Administration] → [Security] → [System Security] → [Authentication/CSP Session Options] を開き、[Allow Delegated authentication] ボックスにチェックを入れて設定を保存します)。
    2. 関連するサービスかアプリケーションの委任認証を有効にします(前者の場合は [SMP] → [Menu] → [Manage Services] → [Service] → [Allowed Authentication Methods] → [Delegated] を選択 → [Save]、後者の場合は [SMP] → [Menu] → [Manage Web Applications] → [Application] → [Allowed Authentication Methods] → [Delegated] を選択 → [Save])。

    仕組み

    委任認証は、委任認証が有効になっているサービスや Web アプリケーションに対してユーザーが認証される際に発生します。

    1. ZAUTHENTICATE ルーチンが呼び出されます。 このルーチンのコードはユーザーによって書かれたものであり、OS への呼び出しを含む任意の Caché ObjectScript コードである可能性があります。
    2. 次のステップは、ZAUTHENTICATE の呼び出しが成功したかどうかによって決まります。
    • ZAUTHENTICATE の呼び出しが成功し、ユーザーが ZAUTHENTICATE で認証されたのが初めてだった場合は「委任されたユーザー」が作成されます。 ZAUTHENTICATE がロールやその他の権限をユーザーに割り当てた場合は、それらがユーザープロパティになります。
    • ZAUTHENTICATE の呼び出しが成功し、ユーザーが ZAUTHENTICATE で認証されたのが初めてではなかった場合はそのユーザーのレコードが更新されます。
    • ZAUTHENTICATE の呼び出しが成功しなかった場合、ユーザーはアクセスエラーを受け取ります。
    1. インスタンスとサービスで 2 要素認証が有効になっている場合は、ユーザーの電話番号と事業者の検索が開始されます。 これらの情報が入力されている場合は、2 要素認証が実行されます。 入力されていない場合は、ユーザーは認証されません。

    ユーザー情報の出処は?

    次のように、アプリケーション/サービスで有効になっている認証方法に応じた 2 つの認証方法があります。

    • 委任: ユーザー名/パスワードは ZAUTHENTICATE ルーチン(GetCredentials エントリポイント)から取得され、ZAUTHENTICATE を使用して検証されます(ユーザータイプ: 委任)。
  • 委任およびパスワード: ユーザー名/パスワードは GetCredentials から取得されますが、標準の Caché ツールを使用してチェックされます(ユーザータイプ: Caché)。
  • 次に、ZAUTHENTICATE ルーチンとそのエントリポイントを見てみましょう。

    ZAUTHENTICATE

    これはメインルーチンであり、次の 4 つのエントリポイントで構成されています。

    GetCredentials

    このエントリポイントはサービスで委任認証が有効になっている場合に呼び出され、ユーザーにユーザー名/パスワードの入力を求める代わりに呼び出されます。 このルーチンのコードは、ユーザー名とパスワードを(何らかの方法で)取得します。 その後、(このエントリポイントの外部で)受信したユーザー名とパスワードはユーザーから通常の方法で入力されたかのように認証されます。 ユーザー名とパスワードは、キーボードからの入力、API、外部デバイスを使用したスキャンなど、任意の方法で取得できます。 この記事では、RFID カードを使用した認証を実装します。

    このエントリポイントはステータスを返します。ステータスがエラーの場合は監査ログに記録され、認証試行は拒否されます。 ただし、エラーステータス $SYSTEM.Status.Error($$$GetCredentialsFailed) が返された場合は、例外的に通常のユーザー名/パスワードの入力が続きます。 シグネチャは次のとおりです。

    GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public { }

    説明:

    • ServiceName – 接続が確立されるサービスの名前
    • Namespace – ネームスペース(接続時に指定されている場合)
    • Username – ユーザー名
    • Password – パスワード
  • Credentials – 現在使用されていません
  • このエントリポイントの重要な機能について説明します。 委任認証とパスワード認証の両方がサービスやアプリケーションで有効になっている場合、ユーザー名とパスワードは GetCredentials エントリポイントを介して受信されますが、それらの情報は標準のパスワード認証に使用されます(ユーザーが手動で入力した場合と同じ)。また、認証が成功した場合のユーザーは委任ユーザーではなく通常の Cache ユーザーになります。

    ZAUTHENTICATE

    初回認証が成功すると、ZAUTHENTICATE はロールやその他のユーザープロパティを定義します。 初回認証以外の場合はプロパティが更新されます(例えば、Roles はログインのたびに指定する必要があります)。 そのために、Properties 配列のプロパティを定型化したコードで設定する必要があります。 シグネチャは以下のとおりです。

    ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) Public { }

    Properties 配列の説明:

    • Properties("Comment") — コメント
    • Properties("FullName") — 氏名
    • Properties("NameSpace") — 初期ネームスペース
    • Properties("Roles") — カンマ区切りのロールのリスト
    • Properties("Routine") — 初期ルーチン
    • Properties("Password") — パスワード
    • Properties("Username") — ユーザー名
    • Properties("PhoneNumber") — ユーザーの電話番号
    • Properties("PhoneProvider") — 電話会社
  • Properties("AutheEnabled") — 標準の 2 要素認証を有効化します(この目的のために、$$$AutheTwoFactorSMS に等しい値を設定する必要があります)
  • ChangePassword

    ユーザーパスワードを変更するためのエントリポイントです。シグネチャは次のとおりです。

    ChangePassword(Username, NewPassword, OldPassword, Status) Public { }

    説明:

    • NewPassword — 新しいパスワード
    • OldPassword — 古いパスワード
  • Status — パスワード変更の結果
  • SendTwoFactorToken

    標準の 2 要素認証で使用されるものです。 リクエストの形式と認証トークンを指定します。 シグネチャは以下のとおりです。

    SendTwoFactorToken(Username, ServiceName,Namespace,Application,Credentials,SecurityToken,TwoFactorTimeout,UserPhoneNumber) Public { }
    

    説明:

    • Application — ユーザーが接続している CSP アプリケーションまたはルーチン
    • SecurityToken — ユーザーに送信されるトークン
    • TwoFactorTimeout — トークンの有効期限が切れる時間
  • UserPhoneNumber — ユーザーの電話番号
  • まずは簡単な例から始めましょう。Windows での Caché ターミナルを担う %Service_Console サービスは、ユーザー名とパスワードの入力をユーザーに要求します。 このサービスに対して委任認証を有効にしましょう。 以下は、ユーザーにユーザー名とパスワードの入力を要求する ZAUTHENTICATE ルーチン(%SYS ネームスペース内)です。

    ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC {
        #Include %occStatus
        Quit $$$OK
    }
    GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public {
        #Include %occErrors
        #Include %occStatus
        Do ##class(%Prompt).GetString("USER:",.Username)
        Do ##class(%Prompt).GetString("PASS:",.Password)
        Quit $$$OK
    }

    ターミナルの場合、これは通常のユーザー名認証と同じように見えます。

    >USER: _SYSTEM
    >PASS: SYS

    RFID

    それでは、RFID による認証を見てみましょう。 考え方は単純で、Caché が暗号化されたユーザー名とパスワードをカードに書き込み、認証中に Caché がカードをスキャンして復号化し、受け取ったユーザー名とパスワードを認証に使用するというものです。

    まず、Arduino Uno と RFID-RC522 モジュールの回路図をご覧ください。

    MF522 ライブラリを使用した C のコードはここにあります。 このコードでは、COM ポート経由で次の 2 つのコマンドを受信できます。

    • Get – RFID カードのブロック 2 / 4 / 5 / 6 の内容が COM ポートに渡されます
  • Set@bloc2@bloc4@bloc5@bloc6 — ブロック 2 / 4 / 5 / 6 の値が受信したデータに置き換えられます
  • Caché 側には Arduino.Delegate クラスがあり、その中に次の 2 つの対応するエントリポイントがあります。

    • SetCredentials — ユーザー名とパスワードの入力を取得し、それをシステムに格納されているキーを使用して AES 暗号化で暗号化し、RFID カードに書き込みます。
  • GetCredentials — カードから暗号化テキストを受信して復号化し、ユーザー名、パスワード、および操作のステータスを返します。
  • また、GetCredentials を使用して Arduino.Delegated クラスを呼び出す ZAUTHENTICATE ルーチンは以下のとおりです。

    ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC {
        #Include %occStatus
        Quit $$$OK
    }
    GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public {
        #Include %occErrors
        #Include %occStatus
        Quit ##class(Arduino.Delegated).GetCredentials(.Username, .Password)
    }
    

    これで準備完了です! 組み立て後のデバイスは次のようになります。

    次のようにターミナルでシステム暗号化キーを設定します(%SYS ネームスペースと Arduino.Delegated クラスを使用できる必要があります)。

    Do ##class(Arduino.Delegated).InitEncryption(Key, IV)
    

    ここで、Key は暗号化キー、IV は初期化ベクトルです。 これらは、ユーザー名とパスワードを暗号化するために使用されます。 コマンドを使用して認証するには、Arduino を Caché に接続し、カードに情報を書き込みます。

    Do ##class(Arduino.Delegated).SetCredentials("_SYSTEM", "SYS")
    

    適切なサービスまたは Web アプリケーション(端末やシステム管理ポータルなど)で委任認証とパスワード認証を有効にすると、カードを RFID カードリーダーにかざすことで認証できるようになります。

    考えられる機能強化

    • マネージド暗号化キーを使用してユーザー名とパスワードを暗号化すると、セキュリティを強化できます。
    • 2 要素認証を使用すると、セキュリティを強化できます。具体的には、先にユーザー名とパスワードのペアを取得してから、ユーザーに固有のキーが格納されているカードを読み取ります。 次に、受信したキーをシステムに格納されている特定ユーザーのキーで確認する必要があります。 任意のユーザーデータを格納する方法は、InterSystems のコミュニティで議論されています
  • それぞれ 15 文字を超えるユーザー名とパスワードを格納する機能を追加します。
  • まとめ

    柔軟性の高い Caché の認証システムを使えば、任意のユーザー認証ロジックを実装できます。

    リンク

    0
    0 1034
    記事 Toshihiko Minamoto · 12月 4, 2020 5m read

    みなさん、こんにちは。

    数日前、SOAP(Web)サービスを使用して、REST に基づく新しいアプリケーション API と同じ認証を使用できるように、既存のレガシーアプリケーションを拡張したい、とお客様から伺いました。 新しいアプリケーションは OAuth2 を使用しているため、課題は明らかでした。SOAP リクエストを含むアクセストークンをどのようにしてサーバーに渡すか、ということです。

    Google でしばらく調べてみたところ、SOAP エンベロープにヘッダー要素を追加してから、アクセストークンを検証するために必要なことを Web サービス実装が実行できるようにするのが 1 つの実現方法であることがわかりました。

    幸い、私たちは、SOAP リクエストにカスタムヘッダーを提供するための仕組みを提供しています。 そこで、ドキュメントを確認したところ(詳細はこちら)、次のクラスが出てきました。

    カスタムヘッダークラス

    Class API.SOAP.OAuth2Header Extends %SOAP.Header
    {
    
    Property accessToken As %String(MAXLEN = "");
    
    }
    

    確かに、非常に単純です。 必要なのはアクセストークンを渡すことだけですが、ほかの情報も渡すように拡張することもできます。

    Webサービス実装クラス

    /// API.SOAP.MyService
    Class API.SOAP.MyService Extends %SOAP.WebService [ ProcedureBlock ]
    {
    
    /// Webサービスの名前。
    Parameter SERVICENAME = "MyService";
    
    /// TODO: これを実際の SOAP ネームスペースに変更してください。
    /// Webサービスの SOAP ネームスペース
    Parameter NAMESPACE = "http://tempuri.org";
    
    /// 参照先クラスのネームスペースが WSDL に使用されます。
    Parameter USECLASSNAMESPACES = 1;
    
    /// TODO: 引数と実装を追加してください。
    /// GetVersion
    Method GetAccountBalance(pAccNo As %String) As API.SOAP.DT.Account [ WebMethod ]
    
    {
      #define APP       "ANG RESOURCES"
      try {
        #dim tAccessTokenHeader as API.SOAP.OAuth2Header=..HeadersIn.GetAt("OAuth2Header")
    
        $$$THROWONERROR(tSC,##class(%SYS.OAuth2.AccessToken).GetIntrospection($$$APP,tAccessTokenHeader.accessToken,.jsonObjectAT))
    
        /* service specific check */
        // check whether the request is asking for proper scope for this service
        if '(jsonObjectAT.scope["account") set reason="scope not supported" throw 
            
        if '(##class(%SYS.OAuth2.Validation).ValidateJWT($$$APP,tAccessTokenHeader.accessToken,,,.jsonObjectJWT,.securityParameters,.tSC)) {
          set reason="unauthorized access attempt"
          throw
        }           
        set tAccountObject=##class(API.SOAP.DT.Account).%New()
        set tAccountObject.accno=pAccNo
        set tAccountObject.owner=jsonObjectJWT."acc-owner"
        set tAccountObject.balance=$random(200000)
      } catch (e) {
        set fault=..MakeFault($$$FAULTServer,"SECURITY",reason)
        Do ..ReturnFault(fault)
      }
      Quit tAccountObject
    }
    
    XData AdditionalHeaders
    
    {
    <parameters xmlns="http://www.intersystems.com/configuration">
    <request>
    <header name="OAuth2Header" class="API.SOAP.OAuth2Header"/> 
    </request>
    </parameters>
    }
    }
    

    アクセストークンの確認について、少し説明しましょう。 ご覧のとおり、最初に行うタスクは、カスタムヘッダーからアクセストークンを取得して、オブジェクト表現に逆シリアル化することです。

    その後は、スコープをチェックするかどうか、JWT トークンの検証などさらなる検証を実行するのかを決めることができます(OAuth2 認証サーバーの設定方法や、私が非常にお勧めしているOpenID を使用するかどうかに依存しています)。

    では、クライアント側を確認してみましょう。

    クライアントが OAuth2 認証サーバーのアクセストークンをどのように受け取るのかについては他の記事で説明しているので、ここでは深く言及せずにおきますが、代わりに、アクセストークンを Web サービスクライアントに提供する方法を確認することにしましょう。 (Web サービスクライアントクラスは、Atelier または Studio IDE から標準の SOAP ウィザード/クライアントオプションを実行して生成します。)

    次は私のクライアントのコードスニペットです。

      set tWSClient=##class(Web.WSC.MyServiceSoap).%New()
      set tWSHeader=##class(Web.WSC.s0.OAuth2Header).%New()
    
      set tWSHeader.accessToken=accessToken
      do tWSClient.HeadersOut.SetAt(tWSHeader,"access-token")
      #dim tAccountObject as Web.WSC.s0.Account=tWSClient.GetAccountBalance(tAccNo)
    

    リソースサーバーにおけるセキュリティ設定に関する考慮事項

    リソースサーバー(Web サーバー)で CSP アプリケーションをセットアップするには、認証されていないユーザーを許可するのが最も簡単なやり方ですが、これにはリスクが伴います。サービスやメソッドごとにアクセストークンをチェックし検証するのはあなた次第です。 アクセストークンが存在しないか有効でない場合は、SOAP フォルトを返さなければなりません。

    より優れた代替手段としては、委任認証を使用し、ZAUTHENTICATE ルーチンでアクセストークンを取得して、何らかの意図的なユーザー名(アクセストークンリクエストのスコープ付きで OpenID プロファイルが提供されている場合は JWT から取得可能)を、Web サーバーメソッドを実行するために最低限必要なロールで割り当てる方法があります。

    0
    0 809
    記事 Toshihiko Minamoto · 12月 2, 2020 3m read

    皆さん、こんにちは。
    ストリームデータをデータベースに格納する場合、ファイルなどから読み取る際に漢字コード変換を行い、Unicode形式で%GlobalCharacterStreamに格納されるかと思いますが、時々、バイナリのままで読み込んでしまい、漢字コード変換を行わないといけない状況があるかと思います。


    ファイルストリームでしたら%FileCharacterStreamクラスのTranslateTableプロパティに元の漢字コードを指定すれば、以下のようにコード変換しながら読みだすことは可能です。

    0
    1 477
    記事 Toshihiko Minamoto · 12月 1, 2020 6m read

    %Net.SSH.Session クラスを使用すると、SSH を使ってサーバーに接続することができます。 一般的にはSFTP、特に FTP インバウンドアダプタとFTPアウトバウンドアダプタで使用されています。

    この記事では、簡単な例を示しながら、このクラスを使用して SSH サーバーに接続する方法、認証のオプションを記述する方法、そして問題が発生した場合のデバッグ方法について説明します。

    次は接続を行う例です。

    Set SSH = ##class(%Net.SSH.Session).%New()
    Set return=SSH.Connect("ftp.intersystems.com")​
    

    上記のコードは新しい接続を作成してから、ftp.intersystems.com の SFTP サーバーにデフォルトのポートで接続します。 この時点で、クライアントとサーバーは暗号化アルゴリズムとオプションを選択済みですが、ユーザーはまだログインしていません。

    接続したら、認証方法を選択できます。 選択できるメソッドには次の 3 つがあります。

    • AuthenticateWithUsername
    • AuthenticateWithKeyPair
    • AuthenticateWithKeyboardInteractive

    上記はそれぞれ異なる認証方式です。 各方式を簡単に説明します。

    AuthenticateWithUsername

    これは、ユーザー名とパスワードを使用します。

    AuthenticateWithKeyPair

    これは、公開鍵と秘密鍵のペアを使用します。 公開鍵は事前にサーバーに読み込まれている必要があり、それに一致する秘密鍵が必要となります。 秘密鍵がディスク上で暗号化されている場合、メソッドへの呼び出しで、それを復号化するためのパスフレーズを指定します。 注意: 秘密鍵を他人に送信してはいけません。

    公開鍵は OpenSSH 形式であり、秘密鍵は PEM で暗号化されている必要があります。 OpenSSH の形式は次のような書式です。

    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCfi2Vq+u0rtt2OC84pyrkq1k7WkrS+s76u3a+2gdD43KQ2Z3vSUUfksymJjp11JBZEpOtBVIAy221UKdc7j7Qk6sUjZaK8LIy+bzDVwMyFWgVvQge7EjdWjrJLBRCDXYML6y1Y25XexThkTWSGyXzGNdr+wfIHYn/mIt0hfvrusauvT/9Wz8K2MGAj4BL7UQZpFJrlXzGmewe6++6cZDQQYi0aztwLK798oc9j0LsccdMpqWrjqoU1uANFhYIuUu/T47TEhT+e6M+KFYK5TR998eJTO25IjdN2Tgw0feXhQFF/nngbol0bA4auSPaZQsgokKK+E+Q/8UtBdetEofuV user@hostname
    

    PEM で暗号化されている秘密鍵には、ファイルの上部に次のようなヘッダーがあります。

    -----BEGIN RSA PRIVATE KEY-----
    

    そして、最後に次の行があります。

    -----END RSA PRIVATE KEY-----
    

    AuthenticateWithKeyboardInteractive

    これは、Cache 2018.1 以降で提供されている新しいオプションです。 チャレンジレスポンス認証を実行できます。 たとえば、テキストメッセージで送信されるか、Google 認証システムアプリで生成されたワンタイムパスワードを要求することがあるでしょう。 この認証方式を使用するには、サーバーが送信するプロンプトを処理するためのラムダ関数を記述する必要があります。

    この認証方式を、ユーザーのパスワード認証と同じように、ユーザー名とパスワードのプロンプトのみで使用しているサーバーに遭遇することがあるかもしれませんが、 以下に説明する SSH デバッグフラグを使えば、これに遭遇しているかどうかを判定しやすくなります。

    認証に関する注意事項: 1 つの接続で 2 つの認証方式の使用を検討している方は、Cache 2018.1 または InterSystems IRIS を使用してください。 これらのバージョンには、鍵ペアとユーザー名といった、複数の方式を使用できるようにするための更新があります。

    問題が発生した場合

    発生する可能性のある一般的なエラー

    バナーの取得に失敗しました(Failed getting banner)

    これは次のように表示されます。

    ERROR #7500: SSH Connect Error '-2146430963': SSH Error [8010100D]: Failed getting banner [FFFFFFFF8010100D] at Session.cpp:231,0
    

    SSH クライアントが最初に行うのは、バナーの取得です。 このエラーが発生した場合、適切なサーバーに接続しており、それが SFTP サーバーであることを確認してください。

    たとえば、サーバーが実際には FTPS サーバーであった場合に、このエラーが発生します。 FTPS サーバーは SSH ではなく SSL を使用するため、%Net.SSH.Session クラスでは動作しません。 FTPS サーバーに接続するには、%Net.FtpSession クラスを使用してください。

    暗号化鍵を交換できません(Unable to exchange encryption keys)

    このエラーは次のように表示されます。

    ERROR #7500: SSH Connect Error '-2146430971': SSH Error [80101005]: Unable to exchange encryption keys [80101005] at Session.cpp:238,0
    

    このエラーは通常、クライアントとサーバーの暗号化または MAC アルゴリズムが合致しなかったことを指しています。 これが発生した場合は、新しいアルゴリズムのサポートを追加するために、クライアントかサーバーのいずれかをアップグレードする必要があるかもしれません。

    2017.1 より前のバージョンの Cache を使用している場合は、2017.1 以降を使用することをお勧めします。 libssh2 ライブラリは 2017.1 でアップグレードされており、新しいアルゴリズムがいくつか追加されています。

    詳細については、以下に説明するデバッグフラグが提供するログを参照してください。

    提供された公開鍵の署名が無効です(Invalid signature for supplied public key)

    Error [80101013]: Invalid signature for supplied public key, or bad username/public key combination [80101013] at Session.cpp:418
    

    これは非常に誤解を招きやすいエラーです。 サーバーが 2 つの認証方式を必要としているにも関わらず、1 つしか提供しなかった場合に発生します。 この場合は、そのまま続けて次の方式を試しましょう! このエラーがあっても、すべてうまく動作する可能性があります。

    Error -37

    エラー -37 に関するメッセージが表示されることがあります。 たとえば、次のデバッグログを見てください。

    [libssh2] 0.369332 Failure Event: -37 - Failed getting banner
    

    エラー -37 が示されている場合は必ず、失敗した操作が再試行されます。 このエラーが最終的な失敗の原因であることはありません。 ほかのエラーメッセージを確認してください。

    SSH デバッグフラグ

    接続に SSH デバッグフラグを使うと、SSH 接続の詳細なログを取得できます。 このフラグを有効にするには、SetTraceMethod メソッドを使います。 次に、このフラグを使った接続の例を示します。

    Set SSH = ##class(%Net.SSH.Session).%New()
    Do SSH.SetTraceMask(511,"/tmp/ssh.log")  
    Set Status=SSH.Connect("ftp.intersystems.com")​ 
    

    SetTraceMask の最初の引数は、何を収集するかを指示します。 ビットの 10進表現です。 511 は 512 を除くすべてのビットを要求しており、最も一般的に使用される設定です。 各ビットに関する詳細については、%Net.SSH.Session クラスのクラスドキュメントをご覧ください。

    2 つ目の引数は、接続に関するログ情報をどのファイルに格納するかを指示します。 この例では、/tmp/ssh.log ファイルを指定しましたが、任意の絶対または相対パスを使用できます。

    上記の例では、Connect メソッドのみを実行しました。 認証に問題がある場合は、該当する認証方式も実行する必要があります。

    テストを実行したら、ログファイルで情報を確認できます。 ログファイルの解釈に不安がある場合は、WRC をご覧ください。

    0
    0 1742
    記事 Mihoko Iijima · 12月 1, 2020 2m read

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

    以下の例では Test.Class2 クラスの Images プロパティに画像ファイルが保存できるように定義しています。
    input() メソッドを利用して画像ファイルを Images プロパティに登録し、データベースに保存したとします。

    0
    0 438
    記事 Mihoko Iijima · 12月 1, 2020 2m read

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

    プログラムでロック情報を取得するには以下2種類の方法があります。

    1. %SYS.LockQuery クラス を使用する方法
    2. SSVN(構造化システム変数)を使用する方法

    1.  %SYS.LockQuery クラス を使用する方法

     #dim rset As %SQL.StatementResult set stmt=##class(%SQL.Statement).%New() set st=stmt.%PrepareClassQuery("%SYS.LockQuery","List") set rset=stmt.%Execute() while rset.%Next() {   write !,rset.%Get("FullReference")   write !,rset.%Get("Owner")   write !,rset.%Get("DelKey"),! }

    より詳細なロック情報を取得する場合には List クエリではなく Detail クエリを使用します。

    set st=stmt.%PrepareClassQuery("%SYS.LockQuery","List")  を

    0
    0 353
    記事 Megumi Kakechi · 11月 24, 2020 2m read

    これはInterSystems FAQ サイトの記事です。
    InterSystemsの製品には同時ユーザー数でライセンスの容量を決定する製品とサーバーのCPUコア数で容量を決定する製品の2種類があります。

    同時ユーザー数ライセンスタイプの場合、CPUコア数により使用可能なライセンスのグレードに制限があります。

    物理サーバーの場合には、そのサーバーが保持しているCPUコア数の総数をカウントします。

    そして、その総数が該当グレードの最大CPUコア数を超えている場合には、その上位グレードを使用しなければなりません。

    サーバー仮想化ソフトウェア(VMWare、Hyper-Vなど)を利用する場合には、その仮想サーバーに割り当てられたCPUコア数をカウントします。

    同様にその総数が該当グレードの最大CPUコア数を超えている場合には、その上位グレードを使用しなければなりません。

    CPUコア数ライセンスタイプの場合、コア数によりライセンスの容量が決まります。

    CPUコア数の数え方は、同時ユーザーライセンスタイプと同じです。

    物理サーバーの場合は、そのサーバーが保持しているCPUコア数の総数をカウントします。

    サーバー仮想化ソフトウェアを利用する場合には、その仮想サーバーに割り当てられたCPUコア数をカウントします。

    【関連情報】(コミュニティ/FAQトピックをリンクしています

    0
    0 311
    記事 Megumi Kakechi · 11月 24, 2020 1m read

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

    以下に当てはまる場合、差分バックアップではなくフルバックアップが取られます。

    ・リストの中に1つでもフルバックアップを取っていないデータベース(DB)が含まれるとき
    ・リストの中に読み込み専用のDBが含まれるとき
     →読込専用DBはバックアップが取れないため、対象リストのDBは毎回フルバックアップが取られます

    ReadOnlyマウントしたDBをバックアップしようとすると、以下のようなログが出力されます。

    WARNING - the following directories could not be backed up 
     c:\intersystems\ensemble\mgr\xxx\ (Database is readonly)


    読込専用DBはバックアップリストから除く必要があります。

    もし、読込専用DBをバックアップする場合は、別途バックアップする必要があります。

    0
    0 184