トランザクション処理

トランザクション処理(TP) は、単一のイベント ( すなわちトランザクション内ですべてのデータベースの更新が発生するか、または、それらのいずれも発生しないかのどちらか) として発生する論理グループにデータベースの更新を編成するMプログラム用の方法を提供します。適切に構築されたトランザクションでは、他のアクターやプロセスは、まるで中間的な状態を観測したかのように動作しません。トランザクション処理はスループットを向上させ、「ライブロック」条件の可能性と影響を最小限に抑えるように設計されています。

TPの定義

Mでは、トランザクションは、 TSTARTコマンドで始まり、TCOMMITコマンドで終了するコマンドのシーケンスで、そして、スコープは別のトランザクションにはありません。アプリケーションは TSTART / TCOMMIT コマンドをネストしてサブ-トランザクションを作成できますが、サブ-トランザクションは最も外側のTCOMMITでのみコミットできます。$TLEVEL が1より大きい場合、サブ-トランザクションのネスティング(入れ子)が行われます。

トランザクションの成功は、トランザクションの終了時にTCOMMITコマンドによってトリガされるCOMMITで終了します。COMMITは、他のプロセスが利用できるようになるまでトランザクション内で実行されているすべてのデータベース更新が発生します。

失敗したトランザクションは、ROLLBACKで終了します。ROLLBACKは、TROLLBACKコマンドによって明示的に、または、トランザクションが進行している間に発生するプロセスの終了時に暗黙的に、呼び出されます。トランザクション内のエラーは、暗黙のROLLBACKが引き起こすことはありません。ROLLBACKは、それらが他のプロセスに対して利用可能となる前に、トランザクション内で実行されたすべてのデータベース更新を削除します。ROLLBACKは、トランザクションの開始の時点から、ロックされているすべてのリソースを解放し、そして、ネイキッド参照(Naked References)が未定義(undefined)になります。

RESTARTは、トランザクションの開始時にTSTARTへ制御を移すことです。RESTARTは暗黙的にROLLBACKが含んでいて、そして最初のTSTARTが最初に実行された時に、それらが持っていた値へローカル変数をオプションで復元することがあります。RESTARTは、最初のTSTARTが実行された時に、それらが持っていた値に、$TESTとネイキッド参照 (Naked References) を常に復元します。RESTARTは、デバイスの状態の情報を管理しません。もし、トランザクションが他のデータベース更新と競合していると、それが判断された場合は、RESTARTは、TRESTARTコマンドまたはMによって呼び出されます。もし最初のTSTARTがRESTARTを可能にする引数を含んでいる場合に、RESTARTだけは正常に発生できます。

TPの特徴

ほとんどのトランザクション処理システムでは、「ACID」テスト(原子性、一貫性、分離性、耐久性)を満たすトランザクションが試行されます。

ACIDトランザクションを提供するために、GT.Mは楽観的並行性制御と呼ばれる手法を使用します。各ブロックには、ブロックを更新するときにGT.Mが現在のデータベーストランザクション番号に設定するトランザクション番号があります。アプリケーションロジック、TSTARTコマンドとTCOMMITコマンドを使用したトランザクションを角カッコで囲みます。一旦トランザクション内に入ると、GT.Mプロセスは読み取る各データベースブロックを追跡し(更新を予定している既存のデータを含むデータベースブロックを最初に読み込む必要があります)、処理中のプライベートメモリは、適用する予定の更新のリストを保持します - プロセス内のアプリケーション・ロジックは、更新でデータベースを表示します; 他のプロセスのアプリケーション・ロジックは、トランザクションの内部状態を認識しません。TCOMMIT 時間に、プロセスはブロックを読み込んだ後に変更されたブロックがあるかどうかをチェックし、そして、それが変更されていない場合は、トランザクションをコミットし、その変更を他のプロセスが独立性と一貫性を持って原子的に見えるようにします(耐久性はコミット時に書かれたジャーナル記録から来ます)。楽観的同時実行は、2つのプロセスが同じリソースに同時にアクセスする必要があるという確率的な可能性で利用しようとします。もしチャンスが小さい場合は、特に複数のCPUを持つシステムで、多くのプロセスを並行して動作させることができます。もし変更が小さい場合、ペナルティは同じトランザクションロジックの実行を繰り返します。

もし1つ以上のブロックが変更された場合、プロセスはその状態をTSTARTに戻し、トランザクションのアプリケーションコードを再実行します。2回目のコミットに失敗した場合は、もう一度試行します。もし3回目の試行でコミットに失敗すると、他のプロセスをデータベースからロックし、トランザクションを単独プロセスとして実行します(つまり、4回目の試行では、楽観的アプローチから悲観的アプローチに切り替わります)。

このテクニックは通常正常に動作し、GT.Mがトランザクション処理のスループットに優れていることを可能にする要因の1つです。

病的に異常な事例は、プロセスが他のプロセスが読み込んだブロック(「衝突 "collisions"」と呼ばれる)をルーチン的に変更して頻繁にトランザクションを再開させる場合に発生します。衝突 "collisions"は合法的でも偶発的でもあります。重要なことは、トランザクションが「オープン」(アプリケーション・ロジックがTSTARTとTCOMMITの間にあるときは「コリジョン・ウィンドウ"collision window"」)が長くなるほど、衝突でトランザクションが再開される確率が高くなることです。

合法的な衝突は、例えば、2人の共同口座保有者が共同口座から同時にATMを引き落とした場合など、通常の業務活動に起因する可能性があります。アプリケーションが各トランザクションを処理するのにかかる時間が非常に短い場合、衝突の可能性は非常に低く、まれに1つが発生する場合、再起動メカニズムがそれをうまく処理します。衝突の可能性が高い例は、大企業が数十から数百の勘定を持ち、個々の取引が複数の勘定科目を襲うことがあり、営業日に多くの人がその勘定に対して取引を実行する商業勘定に由来します。ここでも小さなコリジョンウィンドウは、コリジョンがまれであることを意味し、再起動メカニズムはコリジョンが発生したときにそれらをうまく処理します。

合法的な(GT.Mの視点からの)衝突は、アプリケーション設計の結果として発生することもあります。たとえば、アプリケーションにアプリケーションレベルのトランザクションジャーナルがある場合、そのプロセスに追加されるすべてのプロセスは、その設計では衝突率が高くなり、すべてのトランザクションが3回失敗し、残りのすべてのプロセスがロックアウトされた状態で4回目の試行でコミットするという病的に異常なケースを作成します。これらを回避する方法は、アプリケーションの設計を調整することです.M LOCKを使用してそのような「ホットスポット」をゲートするか、各プロセスに独自の更新領域を与え、場合によっては単一のプロセスを統合します。

偶発的な衝突は、2つのプロセスが同じデータブロックに存在する無関係なデータにアクセスした場合に発生します(例えば、姓によってインデックスされたいくつかのグローバルは、姓が同じ文字で始まる2つのアカウント保有者が同じブロックに存在する場合、偶発的な衝突を招く可能性があります)。多くのデータブロックへのパスは通常、1つのインデックスブロックを通過するため、データの追加によってインデックスブロックが変更され、偶発的な衝突が発生する可能性があります。偶発的な衝突(特にインデックスブロックなどのメタデータを含むブロック)を回避することはできませんが、まれであり、時折の衝突は再起動メカニズムによってうまく処理されます。

長期間トランザクションを開いたままにするアプリケーション設計は、偶発的な衝突の病的な異常な速度を引き起こす可能性があります。プロセスが1秒未満のトランザクションではなくトランザクション内のレポート全体を実行しようとすると(トランザクションがアトミックになることを覚えておいてください)、レポートには数秒か数分かかり、効果的に衝突や再起動が確実に行われます。さらに、衝突の可能性が高いため、これらの長時間実行されるトランザクションが4回目のリトライ(他のプロセスがシャットアウトされた状態)を実行する確率が高くなり、その場合、システムは不安定に応答するか、一時的にハングアップするように見えます。

非孤立したアクションは健全な取引の設計におけるもう一つの考慮事項です。Mはトランザクションですべての言語機能を許可するため、アプリケーションはトランザクション外のアクターと対話するアクションを使用することがあります; そのようなアクションは、独立性のACIDプリンシパルに違反します。このACIDプリンシパルは、健全であると述べるトランザクションは、コミットするまで他のエージェントまたはプロセスと対話してはいけません。アイソレーションの違反を正当化する大きなアプリケーションモデルから引き出される理由があるかもしれませんが、そうすることはリスクを伴います。1つの問題は時間であり、外部の相互作用は通常より長い持続時間を有し、最悪の場合には不定期を有する可能性があります。JOB、LOCK、OPEN、READコマンドにはオプションのタイムアウトがあり、外部の対話に時間制限を設定できます。さらに、BREAK、WRITE、ZSYSTEM、外部呼び出しには、外部からの相互作用も含まれます。WRITEと外部呼び出しを除き、GT.Mは、これらの分離されていないコマンドを使用するトランザクションのデータベースロックの期間を制限しますが、時間制限(gtm_tpnotacidtime環境変数を使用して管理)は処理の中断を許すほど長くなる可能性があります。さらに、長いロックを拒否されたプロセスは、繰り返し失敗した試行でシステムリソースを完了して消費することができない場合があります。外部呼び出しは、より洗練されたデザインのドメインであり、実際に分離されている可能性があるため、この保護から除外されます。ほとんどのWRITEコマンドは非ブロッキングであるため、WRITEは現在除外されていますが、アプリケーションはトランザクション内でWRITEをブロックしないでください。持続時間の問題以外にも、エラーやアプリケーションロジックのためにアプリケーションが再起動やロールバックによって繰り返されるため、非分離アクションでは管理が外部対話を適切に管理する必要があります。これについては後で詳しく説明します。要約すると、トランザクションの前または後に、トランザクション内ではなく外部のやりとりを配置します。アプリケーションがトランザクション内で分離されていないアクションを必要とする場合は、リスク、設計、実装、およびテストを非常に慎重に考慮する必要があります。

GT.Mは、システムへの影響を制限するために長時間実行されるトランザクションを中断させるトランザクションタイムアウト機能を提供し、その結果、システムの不安定な応答時間と一時的なハングをユーザが認識します。外部ライブラリが呼び出し可能なシステムコールを使用している場合、Webサービスにアクセスするために外部ライブラリを呼び出すと、タイムアウトメカニズムを無効にすることができます。このようなWebサービスがすぐに応答する隣接サーバーを使用する場合、Webサービスは健全です。しかし、Webサービスが短い応答時間を保証せずにリモートサーバーにアクセスすると、衝突が頻繁に発生し、4回目の再試行のプロセスが応答しないWebサービスを待機すると、アプリケーション全体が停止します。

[Tip] 安全にWebサービスを実装する

トランザクション内でWebサービスを安全に実装するためには、アプリケーションがサービスの所要時間に保証された上限を実装する必要があります。各状況のストーリーまたはユースケースによって、対応するトランザクションのタイムアウトが決まります。たとえば、Webサービスがトランザクションを承認する場合、承認サービスがその時間内に応答しない場合、承認が拒否されて500ミリ秒のタイムアウトが発生する可能性があります。

タイムアウトのあるWebサービスを実装するには、2つの方法があります。

  1. Cコードを呼び出す(call out)アプリケーションの場合、Cコードは必要に応じてラッパーを使用して制限時間内にリターンを保証します。GT.Mは、外部Cコードがタイマを実装するために使用できる関数を提供します。呼び出しが未知のライブラリ、またはタイムアウトを保証する方法がない場合、外部CコードはGT.Mにタイムアウトを与えることができる中間プロキシを作成する必要があります。

  2. Webサービスは通常、TCP / IP上に階層化された既知のプロトコルによって実装され、GT.MはTCP / IP接続用のSOCKETデバイスを提供するため、GT.M SOCKETデバイスを使用してWebサービスに呼び出しを実装します。GT.Mは、外部呼び出し、特にライブラリを介して呼び出され、無停止のOSサービスに呼び出されるTPタイムアウトメカニズムを強制することができます。

最大限の柔軟性と、可能な限り、標準の旧バージョンとの下位互換性を提供しているMのアプローチに適合するには、MのトランザクションプロセッシングがACIDテストを満たしているプログラミング規則の使用が必要です。

例えば、BREAK、CLOSE、JOB、OPEN、READ、USE WRITE、ZSYSTEMコマンドのいくつかの効果は、システムの関係者によって観察されるでしょう。これらのコマンドの効果により、観察プロセスまたは人は、それらを実行しているトランザクションが進行中であり、おそらく終了していると結論づける可能性があるため、理論上、分離の原則に違反します。

LOCKコマンドは、別の例です。別のプロセスに進行中のトランザクションがあるかどうかを判断するために、プログラムはLOCKを使用する試みをします。応答(answer)は、実装固有であるのトランザクション内でロックの管理に依存します。したがって、これは明確に分離の原則に違反することになります。LOCKコマンドは、このセクションで後述します。

ACIDテストを満たすトランザクションを構築する最も簡単な方法は、影響を与えるトランザクション外ですぐに "visible" かもしれないそのトランザクション内で、任意のコマンドを使用することはありません。残念なことに、Mアプリケーションは非常にインタラクティブなので、これは全く簡単なことではありません。ユーザーとの対話(interaction)がデータベースの情報に依存する時には、解決策の1つは、ローカル変数で、結果に影響を与える可能性のあるグローバル値の初期値をプログラムによって保存することです。その後、いったん相互作用が上にあがり、トランザクションが開始されたならば、プログラムは、一致するグローバル変数に対して保存された値をチェックします。それらが同じなら、それは進行します。もしそれらがが異なっている場合、他のいくつかの更新は情報が変更され、そして、プログラムがTROLLBACKを発行し、交換として別の相互作用を開始する必要があります。

"visible" コマンドはトランザクション内に表示される時でさえも、Mアプリケーションは、追加のプログラミングまたはオペレーティング規則に依存することによって健全なオペレーションを提供することがあります。

直列化可能性(serializability)を達成するためにLOCKを使用しているプログラムは、データベースオペレーションに関連して分離(アイソレーション)を実現するために、適切に設計され、そして、ロッキング規則に普遍的に従うことに依存しています。【SERIALIZABLE ( 直列化可能 )】複数の並行に動作するトランザクションそれぞれの結果が、いかなる場合でも、それらのトランザクションを時間的重なりなく逐次実行した場合と同じ結果となる.このような性質を直列化可能性(Serializability)と呼ぶ.SERIALIZABLEは最も強い分離レベルであり、最も安全にデータを操作できるが、相対的に性能は低い。ただし同じ結果とされる逐次実行の順はトランザクション処理のレベルでは保証されない。トランザクション(通常は、TSTARTの直前にLOCKをして、TCOMMITの直後にロック解除)の外側に配置されたLOCKは、実際にはだいたいおおよその同時トランザクションをシリアライズすることで、直列化可能性(serializability) を達成します。トランザクション(頻繁に、TSTART直後にLOCKし、TCOMMIT直前にロック解除)の内側に配置されているLOCKは、同じ複数のロックリソースのオーバーラップを使用して何も操作していないことを保証するために、Mにシグナルを送ります。トランザクション内で、Mの実装は、直列化可能性のそのゴールを達成するためにロックとアンロックの両方を延期することができる。SERIAL keyword で TSTARTsを使用するプログラムは、トランザクションのすべてのデータベースアクティビティがデータベースアクティビティに対するアイソレーションのテストを満たしているそのMからの保証で規則を置き換えます。

GT.MにACID特性の耐久性の面では、ジャーナリングの特性に依存しています。ジャーナリングがオンのときに、すべてのトランザクションは、データベース内だけでなくジャーナルファイル内にも記録されます。ジャーナルファイルは、データベースのアクションや状態を連続的な記録を構成します。それは常にデータベース更新前に書き込まれ、もしデータベースが破損されるかどうかを、データベースの復旧を可能にするために設計されています。トランザクションがジャーナルファイルに到達するまでに、デフォルトでは、プロセスがトランザクションをコミットする時、それはアプリケーションコードに制御を返しません。この例外は、TSTARTが TRANSACTIONID="BATCH" を指定した時にプロセスがジャーナルレコードの成功の書き込みを確認するためにファイルシステムを待たずにアプリケーションの実行を再開することです。TRANSACTIONID="BATCH" の考え方は、"batch" プロセッシングとは本質的に何の関係もない - それは、アプリケーションが、その独自のチェックポインティングメカニズム、または、障害発生時にトランザクションを再作成する方法を持っているトランザクションの最大スループットを可能にすることです。トランザクションの実際の耐久性は、ジャーナルファイルの耐久性の機能です。信頼性の高いデバイスにジャーナルファイルを置くこと(UPS保護機能を備えたRAID)と、データベースへのパス(別のドライブ、コントローラ、ケーブル配線)での共通の障害点を解消することは、耐久性を向上させます。レプリケーション機能の使用は、またリアルタイムで別のサイトにデータを移動することで、耐久性を向上させることができます。

そのコードがTCOMMITでまだ一致していなしTSTARTを発行した後に、DO、XECUTE、または外部によって呼び出されたコードからの QUIT(暗黙的または明示的)を試みることは、エラーが発生します。これは、RESTART の能力の結果ですが、その機能が無効になっている場合でさえも、それは確かです。例えば、これは、完全なトランザクションを実行するXECUTEが成功している間、TSTARTだけを含むXECUTEが障害を発生することを、意味します。

TP パフォーマンス

最高のGT.Mパフォーマンスを達成するために、トランザクションをすべきです:

  • 可能な限り短く

  • 可能な限り、唯一のグローバル更新にとどめる

  • 関連のないLOCK付きSERIALにする

  • TSTART引数の再起動の部分により保護されているローカル変数の最小値で有効になっているRESTARTを持つ。

  • TCOMMITを使用して大規模な同時実行のトランザクションは、性能低下の原因と、必要とされる希少なリソースを取り込むために競合プロセスによって、繰り返しと非効率的な試みの結果を生じさせます。

例:

 TSTART ():SERIAL
 SET (ACCT,^M(0))=^M(0)+1
 SET ^M(ACCT)=PREC,^PN(NAM)=ACCT
 TCOMMIT

このトランザクションは、これらの2つのSETをカプセル化します。最初は登録患者の集計数を増加させ、現在のプログラムでアクセスを高速化するために、ローカル変数ACCTとグローバル変数 ^M(0)に数値を格納します。2番目のSETは、アカウント番号(ACCT)によって患者レコードが格納され、3番目のSETは、患者名でクロスリファレンスのアカウント番号が格納されます。単一のトランザクション内でSETの配置は、データベースが常にSETのすべてか、または、それらのどれでもないか、のどちらかを受け取ることを保証します、このように、プロセスやシステムの障害に対してデータベースの整合性を保護します。同様に、別の同時プロセスは、トランザクションを使用するかどうか、また他の1つのものを見つけることがない代わりに、SETのいずれかも見つけることはありません。

例:

 TSTART ():SERIAL
 IF $TRESTART>3 DO QUIT
 .TROLLBACK
 .WRITE !,"Too many RESTARTs"
 .QUIT
 SET (NEXT,^ID(0))=^ID(0)+1
 SET ^ID(NEXT)=RECORD,^XID(ZIP,NEXT)=""
 TCOMMIT

このトランザクションは、もしそれがデータベースへSETをシリアライズできない場合は、自動的に再起動され、もし3回以上のRESTARTが発生した場合は、TROLLBACKで終了します。

GT.Mは、オペレータのロギング機能にそれらを報告することによって、トランザクションの再起動を監視する方法を提供します。環境変数 gtm_tprestart_log_delta が定義されている場合、GT.MはN番目の再起動ごとに報告します。 N は gtm_tprestart_log_delta の値の数値評価です。環境変数gtm_tprestart_log_firstが定義されている場合、gtm_tprestart_log_firstの値で指定された再起動回数の後に再起動レポーティングが開始されます。たとえば、両方の環境変数を値1に定義すると、すべてのTP再始動がログに記録されます。gtm_tprestart_log_deltaが定義されている場合、gtm_tprestart_log_firstを未定義のままにすることは、値1を与えることと同じです。

TP の例

ここでは、コンセプトを演習させるようなトランザクションプロセッシングの例です。もしこの例を使用する場合は、関数 "holdit" と "trestart" は、通常はユーザから隠されるトランザクション内の情報へアクセスを許可するためのツールとして含まれていることに、留意してください。これら関数のタイプは、通常、プロダクション(本番用)のコードには表示されません。コメントは、様々なセグメントの機能を説明するコードに挿入されています。

trans
 ;This sets up the program constants
 ;for doit and trestart
 new
 set $piece(peekon,"V",51)=""
 set $piece(peekon,"V",25)="Peeking inside Job "_$job
 set $piece(peekoff,"^",51)=""
 set $piece(peekoff,"^",25)="Leaving peeking Job "_$job
 ;This establishes the main loop
 set CNFLTMSG="Conflict, please reenter"
 for read !,"Name: ",nam quit:'$length(nam) do
 .if "?"=nam do quit
 ..write !,"Current data in ^trans:",!do:$data(^trans) quit
 ...zwrite ^trans
 .for set ok=1 do quit:ok write !,$char(7),CNFLTMSG,$char(7),!
 ..set old=$get(^trans(nam),"?")
 ..if "?"=old write !,"Not on file" do quit
 ...;This is the code to add a new name
 ...for do quit:"?"'=data
 ....read !,"Enter any info using '#' delimiter: ",!,data
 ...if ""=data write !,"No entry made for ",nam quit
 ...TSTART ():SERIAL if $$trestart ;$$trestart for demo
 ...if $data(^trans(nam)) set ok=^trans(nam)=data TROLLBACK quit
 ...set ^trans(nam)=data
 ...TCOMMIT:$$doit ;$$doit for demo
 ..;This is the beginning of the change and delete loop
 ..for do quit:+fld=fld!'$length(fld) write " must be numeric"
 ...write !,"Current data: ",!,old
 ...read !,"Piece no. (negative to delete record) : ",fld
 ..if 'fld write !,"no change made" quit
 ..;This is the code to delete a new name
 ..if fld<0 do quit ; delete record
 ...for do quit:"YyNn"[x
 ....write !,"Ok to delete ",nam," Y(es) or N(o) <N>?"
 ....read x set x=$extract(x)
 ...if "Yy"'[x!'$length(x) write !,"No change made" quit
 ...TSTART ():SERIAL if $$trestart ;$$trestart for demo
 ...if $get(^trans(nam),"?")'=old TROLLBACK set ok=0 quit
 ...kill ^trans(nam)
 ...TCOMMIT:$$doit; $$doit for demo
 ..;This is the code to change a field
 ..for read !,"Data: ",data quit:("?"'=data)&(data'["#") do
 ...write " must not be a single '?' or contain any '#'"
 ..TSTART ():SERIAL if $$trestart ;$$trestart for demo
 ..if '$data(^trans(nam)) set ok=0 TROLLBACK q
 ..if $piece(^trans(nam),"#",fld)=$piece(old,"#",fld) do quit
 ...set ok=$piece(^trans(nam),"#",fld)=data TROLLBACK
 ..set $piece(^trans(nam),"#",fld)=data
 ..TCOMMIT:$$doit; $$doit for demo
 quit
doit()
;This inserts delay and an optional 
;rollback only to show how it works
 write !!,peekon do disp
 for do quit:"CR"[act
 .read !,"C(ommit), R(ollback), or W(ait) <C>?",act
 .set act=$translate($extract(act),"cr","CR")
 .if "?"=act do disp
 if "R"=act TROLLBACK write !,"User requested DISCARD"
 write !,peekoff,!
 quit $TLEVEL
trestart()
;This is only to show what is happening
 if $TRESTART do
 .write !!,peekon,!,">>>RESTART<<<",!do disp write !,peekoff,!
 quit 1
disp
 write !,"Name: ",nam
 write !,"Original data: ",!,old,!,"Current data: "
 write !,$get(^trans(nam),"KILLED!")
 quit

一般的に、プログラムのこのタイプは、同じグローバルに複数セッションからデータを受信されます。

inserted by FC2 system