GitLab を使用した InterSystems ソリューションの継続的デリバリー - パート XI: 相互運用性
CI/CD シリーズの新しい章へようこそ。ここでは、InterSystems テクノロジーと GitLab を使用したソフトウェア開発の様々な可能なアプローチを取り上げています。
今回は、相互運用性についてご紹介しましょう。
問題
アクティブな相互運用性プロダクションがある場合、2 つの個別のプロセスフローが存在します。メッセージを処理する稼動中のプロダクションと、コード、プロダクションの構成、およびシステムデフォルト設定を更新する CI/CD プロセスフローです。
明らかに、CI/CD プロセスは相互運用性に影響しますが、 本題は次にあります。
- 更新中には実際に何が起きているのか?
- 更新中の本番停止を最小限に抑えるか失くしてしまうには、どうすればよいのか?
用語
- ビジネスホスト(BH)- 相互運用性プロダクションの構成可能な要素: ビジネスサービス(BS)、ビジネスプロセス(BP、BPL)、ビジネスオペレーション(BO)。
- ビジネスホストジョブ(ジョブ)- ビジネスホストのコードを実行し、相互運用性プロダクションによって管理される InterSystems IRIS ジョブ。
- プロダクション - 相互に接続されたビジネスホストのコレクション。
- システムデフォルト設定(SDS)- InterSystems IRIS がインストールされている環境に固有の値。
- アクティブメッセージ - 1 つのビジネスホストジョブによって現在処理されているリクエスト。 1 つのビジネスホストジョブに対するアクティブメッセージは最大 1 つです。 アクティブメッセージのないビジネスホストジョブはアイドルです。
何が起きているのか?
プロダクションのライフサイクルから見てみましょう。
プロダクションの起動
まず初めに、プロダクションを起動できます。 ネームスペースあたり 1 つのプロダクションのみを同時に実行でき、一般に(作業内容や過程を十分に理解していない限り)、ネームスペースごとに実行するプロダクションは 1 つ限りです。 1 つのネームスペース内で 2 つ以上の異なるプロダクションを切り替えるのは推奨されません。 プロダクションを起動すると、そのプロダクションに定義されたすべての有効なビジネスホストが起動します。 一部のビジネスホストが起動に失敗してもプロダクションの起動には影響しません。
ヒント:
- システム管理ポータルから、または
##class(Ens.Director).StartProduction("ProductionName")を呼び出してプロダクションを起動します。 OnStartメソッドを実装して、プロダクションの起動時に(ビジネスホストジョブが起動する前に)任意のコードを実行します。- プロダクションの起動は監査可能なイベントです。 誰がいつ起動したかを必ず監査ログで確認できます。
プロダクションの更新
プロダクションが起動した後、Ens.Director は実行中のプロダクションを継続的に監視します。 プロダクションの状態は 2 つあります。プロダクションクラスとシステムデフォルト設定に定義された target state と、ジョブが作成されたときに適用された設定で現在実行しているジョブの running state です。 希望する状態と現在の状態が同一である場合はすべて良好ですが、異なる場合にはプロダクションを更新する必要があります。 通常、この場合には、システム管理ポータルのプロダクション構成ページに赤い「更新」ボタンが表示されます。
プロダクションを更新すると、現在のプロダクションの状態をターゲットプロダクションの状態に一致させることになります。
プロダクションを更新するために ##class(Ens.Director).UpdateProduction(timeout=10, force=0) を実行すると、各ビジネスホストにおいて以下が行われます。
- アクティブな設定をプロダクション/SDS/クラスの設定と比較します。
- (1) で不一致が生じた場合に限り、ビジネスホストは古いものとしてマークされ、更新が必要となります。
これを各ビジネスホストに対して実行した後、UpdateProduction は一連の変更をビルドします。
- ビジネスホストを停止
- ビジネスホストを起動
- プロダクション設定を更新
そしてその後で適用します。
このようにして、何も変更せずに設定を「更新」するとプロダクションが停止しません。
ヒント:
- システム管理ポータルから、または
##class(Ens.Director).UpdateProduction(timeout=10, force=0)を呼び出してプロダクションを更新します。 システム管理ポータルのデフォルトの更新タイムアウトは 10 秒です。 メッセージの処理がそれ以上かかることが分かっている場合は、タイムアウト時間がより長いEns.Director:UpdateProductionを呼び出します。 - 更新タイムアウトはプロダクションの設定であり、より長い時間に値を変更できます。 この設定はシステム管理ポータルに適用されます。
コードの更新
UpdateProduction は古いコードで BH を更新することはありません。 これは安全を優先した動作ではありますが、根底にあるコードが変更した場合にすべての実行中の BH を自動的に更新する場合は、以下のように行います。
まず、以下のように読み込んでコンパイルします。
do $system.OBJ.LoadDir(dir, "", .err, 1, .load)
do $system.OBJ.CompileList(load, "curk", .errCompile, .listCompiled)
listCompiled には、u フラグにより、実際にコンパイルされたすべての項目が含まれます(読み込まれるセットを最小限に抑えるには、git diffs を使用します)。 この listCompiled を使用して、コンパイルされたすべてのクラスの $lb を取得します。
set classList = ""
set class = $o(listCompiled(""))
while class'="" {
set classList = classList _ $lb($p(class, ".", 1, *-1))
set class=$o(listCompiled(class))
}
その後で、再起動が必要な BH のリストを計算します。
SELECT %DLIST(Name) bhList
FROM Ens_Config.Item
WHERE 1=1
AND Enabled = 1
AND Production = :production
AND ClassName %INLIST :classList
最後に、bhList を取得した後に、影響のあるホストを停止して起動します。
for stop = 1, 0 {
for i=1:1:$ll(bhList) {
set host = $lg(bhList, i)
set sc = ##class(Ens.Director).TempStopConfigItem(host, stop, 0)
}
set sc = ##class(Ens.Director).UpdateProduction()
}
プロダクションの停止
プロダクションは停止可能です。つまり、すべての美自演すホストジョブにシャットダウンのリクエストを送信します(アクティブなメッセージがある場合はその処理が完了してから安全にシャットダウンされます)。
ヒント:
- システム管理ポータルから、または
##class(Ens.Director).StopProduction(timeout=10, force=0)を呼び出してプロダクションを停止します。 システム管理ポータルのデフォルトの停止タイムアウトは 120 秒です。 メッセージの処理がそれ以上かかることが分かっている場合は、タイムアウト時間がより長いEns.Director:StopProductionを呼び出します。 - シャットダウンタイムアウトはプロダクションの設定です。 より長い時間に値を変更できます。 この設定はシステム管理ポータルに適用されます。
OnStopメソッドを実装して、プロダクションの停止時に任意のコードを実行します。- プロダクションの停止は監査可能なイベントであり、誰がいつ停止したのかを必ず監査ログで確認できます。
ここで重要なのは、プロダクションはビジネスホストの合計であることです。
- プロダクションを起動するとすべての有効なビジネスホストが起動します。
- プロダクションを停止するとすべての実行中のビジネスホストが停止します。
- プロダクションを更新すると、古いセットのビジネスホストが計算され、これらが先に停止されてからその直後にもう一度起動されます。 また、新たに追加されたビジネスホストは開始のみされ、プロダクションから削除されたビジネスホストは単に停止されます。
この流れで、ビジネスホストのライフサイクルに進みましょう。
ビジネスホストの起動
ビジネスホストは(プールサイズの設定値に応じて)同一のビジネスホストジョブで構成されています。 ビジネスホストを起動すると、すべてのビジネスホストジョブが起動します。 これらは並列して起動します。
個別のビジネスホストジョブは以下のように起動します。
- 相互運用性はビジネスホストジョブになる新しいプロセスを JOB で起動します。
- 新しいプロセスは相互運用性ジョブとして登録されます。
- ビジネスホストコードとアダプターコードがプロセスメモリに読み込まれます。
- ビジネスホストとアダプターに関連する設定がメモリに読み込まれます。 以下はその順序です。 a. プロダクション設定(システムデフォルトとクラス設定をオーバーライドします)。 b. システムデフォルト設定(クラス設定をオーバーライドします)。 c. クラス設定。
- ジョブの準備が整い、メッセージを受け入れ始めます。
(4)が完了すると、ジョブは設定またはコードを変更できないため、新しい/同じコードと新しい/同じシステムデフォルト設定をインポートしても、現在実行中の相互運用性ジョブに影響しません。
ビジネスホストの停止
ビジネスホストジョブを停止すると、以下のようになります。
- 相互運用性はジョブにメッセージ/入力の受け入れ停止を要求します。
- アクティブなメッセージがある場合、ビジネスホストジョブにはそれを処理するための数秒のタイムアウトがあります(ジョブを完了、つまり BO の場合は
OnMessageメソッド、BS の場合はOnProcessInputメソッド、BPL BP の場合は状態のS<int>メソッド、BP の場合はOn*メソッドを終了します)。 - アクティブなメッセージがタイムアウトと
force=0までに処理されていない場合、そのビジネスホストのプロダクションの更新は失敗します(SMP に赤い更新ボタンが表示されます)。 - 以下のいずれかを満たす場合は停止します。
- アクティブなメッセージがない
- アクティブなメッセージは
timeoutの前に処理された - アクティブなメッセージはタイムアウト前に処理されなかったが
force=1で処理された
- ジョブは 相互運用性から登録解除され停止します。
ビジネスホストの更新
ビジネスホストの更新では、ビジネスホストの現在実行中のジョブを停止し、新しいジョブを起動します。
ビジネスルール、ルーティングルール、DTL
すべてのビジネスホストは、新しいバージョンのビジネスルール、ルーティングルール、および DTL が利用できるようになると直ちにそれらを使用し始めます。 この状況では、ビジネスホストの再起動は不要です。
オフラインでの更新
とは言え、時にはプロダクションの更新には個別のビジネスホストの停止が必要です。
ルールは新しいコードで決まる
この状況を考えてみてください。 任意の基準に基づいてメッセージをビジネスプロセス A または B にルーティングする現在のルーティングルール X があるとします。 新しいコミットにおいて、以下を同時に追加します。
- ビジネスプロセス C
- メッセージを A、B、または C にルーティングする新しいバージョンのルーティングルール X
このシナリオでは、先にルールを読み込んでからプロダクションを更新することはできません。 新しくコンパイルされるルールは、InterSystems IRIS がまだコンパイルしていないかもしれないビジネスプロセス C に直ちにルーティングし始めるか、相互運用性がまだ使用できるように更新されていないためです。 この場合、ルーティングルールでビジネスホストを無効に子、コードを更新し、プロダクションを更新して、ビジネスホストをもう一度有効にする必要がります。
注意:
- プロダクションデプロイファイルを使用してプロダクションを更新する場合、影響されるすべての BH は自動的に無効化/有効化されます。
- InProc によって呼び出されたホストの場合、コンパイルによって呼び出し元が保持する特定のホストのキャッシュが無効になります。
ビジネスホスト間の依存関係
ビジネスホスト間の依存関係は重要です。 ビジネスプロセス A と B があり、A が B にメッセージを送信するとします。 新しいコミットにおいて、以下を同時に追加します。
- リクエストの新しいプロパティX を B に設定する新しいバージョンのプロセス A
- 新しいプロパティ X を処理できる新しいバージョンのプロセス B
このシナリオでは、プロセス B を先に更新してから A を更新する必要があります。 これは次のいずれかの方法で行えます。
- 更新の間、ビジネスホストを無効化する
- 更新を 2 つに分ける: まずプロセス B のみを更新してから、その後の別の更新で、プロセス A から B にメッセージを送信し始める。
新しいバージョンのプロセス A と B に古いバージョンとの互換性がない場合のより困難な状況では、ビジネスホストの停止が必要となります。
キュー
更新後に、ビジネスホストが古いメッセージを処理できないことが分かっている場合、ビジネスホストキューが更新前に空になっていることを確認する必要があります。 これを行うには、ビジネスホストにメッセージを送信するすべてのビジネスホストを無効化し、そのキューが空になるのを待ちます。
BPL ビジネスプロセスにおける状態の変更
はじめに、BPL BP の仕組みから簡単に説明します。 BPL BP をコンパイルした後、2 つのクラスが完全な BPL クラス名と同じ名前でパッケージに作成されます。
Thread1クラスはメソッド S1, S2, ... Sn を含み、BPL 内のアクティビティに対応します。Contextクラスにはすべてのコンテキスト変数があり、BPL が実行する次の状態もあります(S5)・
また BPL クラスは永続であり、現在処理中のリクエストを格納します。
BPL は Thread クラスで S メソッドを実行し、それにおうじて BPL クラステーブル、Context テーブル、および Thread1テーブルを更新することによって機能します。ここで、「処理中」の 1 つのメッセージは BPL テーブル内の 1 行になります。 リクエストが処理されると、BPL は BPL、Context、および Thread エントリを削除します。 BPL BP は非同期であるため、1 つの BPL ジョブは S 呼び出しの間に情報を保存して異なるリクエストを切り替えることで、同時に多数のリクエストを処理できます。
たとえば、BPL は 1 つのリクエストが sync アクティビティに到達するまで処理し、BO からの応答を待ちます。 現在のコンテキストを %NextState プロパティ(Thread1 クラス)をレスポンスアクティビティ S メソッドに設定してディスクに保存し、BO が応答するまで他のリクエストを処理します。 BO が応答したら、BPL はコンテキストをメモリに読み込んで、%NextState プロパティに保存された状態に対応するメソッドを実行します。
では、BPL を更新したらどうなるでしょうか? まず、少なくとも以下の 2 つの条件の 1 つを満たしていることをチェックする必要があります。
- 更新中に、Context テーブルが空である。つまり、処理中のアクティブなメッセージがない。
- 新しい状態は古い状態と同じであるか、新しい状態は古い状態の後に追加されている。
少なくとも 1 つの条件を満たしている場合は、問題ありません。 更新後 BPL が処理する更新前リクエストが存在しないか、状態が最後に追加される、つまり古いリクエストもそこに含まれることが可能です(更新前リクエストが更新後 BPL アクティビティと処理に互換していることが前提です)。
ただし、処理中のアクティブなリクエストが存在し、BPL が状態の順序を変更した場合はどうでしょうか? 理想的には、待機できるのであれば、BPL 呼び出し元を無効にしてキューが空になるまで待機します。 Context テーブルが空であることも確認します。 キューには未処理のリクエストのみが表示され、Context テーブルは処理中のリクエストを格納することを覚えておきましょう。つまり、非常にビジーな BPL のキューサイズが 0 となる場合があり、それは正常です。 その後、BPL を無効にし、更新を実行して、以前に無効にしたすべてのビジネスホストを有効にします。
それが可能でない場合は(通常、非常に時間のかかる BPL がある場合。リクエストの処理に約 1 週間かかった BPL の更新ケースや更新ウィンドウが短すぎたケースを覚えています)、BPL バージョン管理を使用します。
または、更新スクリプトを書くことができます。 この更新スクリプトでは、更新された BPL が古いリクエストを処理できるように、古い次の状態を新しい次の状態にマッピングして、Thread1 テーブルで実行します。 もちろん更新期間中は BPL を無効にしておく必要があります。
とは言え、これは非常にまれな状況であり、通常は行う必要はありませんが、その必要性が生じた場合にはこれを使用することができます。
まとめ
相互運用性は、根底にあるコードが変更した後でプロダクションを最新状態にするために必要となるアクションを最小限に抑えるために、洗練されたアルゴリズムを実装します。 SDS の更新ごとに安全なタイムアウトで UpdateProduction を呼び出せます。 それぞれのコードの更新については更新ストラテジーを決定する必要があります。
git diffs を使ってコンパイルされるコードの量を最小限に抑えると、コンパイル時間の軽減に役立ちますが、コードをそのコード自体で「更新」して再コンパイルするか、同じ値で設定を「更新」するとプロダクションの更新はトリガーされないか不要となります。
ビジネスルール、ルーティングルール、および DTL を更新してコンパイルすると、プロダクションを更新せずにすぐにアクセス可能になります。
最後に、プロダクションの更新は安全な操作であり、通常、停止は必要ありません。
リンク
この記事の執筆において貴重な支援を提供してくださった @James MacKeith、@Dmitry Zasypkin、および @Regilo Regilio Guedes de Souza に感謝申し上げます。