VPN切断後にSQL ServerからOracleへのロードを復旧する
VPNが切れると、呼び出し側はOracleのcommitが完了したか分からない。永続的なバッチIDを使えば、抽出・適用・照合の問いに一つずつ答えられる。
VPNが切れるとデータベース間の転送は止まり、「もう一度実行する」は復旧計画ではない。ソースの抽出は完了しているかもしれず、ターゲットがすでにcommitしているかもしれず、単に応答が戻り道で失われただけかもしれない。この三つの状態はそれぞれ異なる対応を必要とし、「エラー」しか知らないクライアントには区別できない。
修正はアーキテクチャにある:すべての実行に永続的なバッチIDを与え、クライアントに推測させるのではなくデータベースに答えさせる。
データベースが実際に保証するもの
SQL ServerもOracleも、トランザクション内の原子性は提供するが、二つにまたがる原子性は提供しない。接続が失われてもデータベースは壊れず、手動ロールバックも強制されない。サーバーが自分でトランザクションを解決するからだ。壊れるのは、結果についての呼び出し側の知識である。
別々にcommitしたものは別々にcommitしたものだ。両方の接続を運ぶVPNが、二つのcommitを一つの作業単位に融合することはない。
あなたが聞かなかったcommit
一つの障害モードが安易な再試行ロジックを罰する:サーバーはcommitし、確認応答が呼び出し側に届かない。Oracleはこれを不明な結果の問題として文書化し、トランザクションのドキュメントでTransaction Guardを提供している。通信エラーは、送信したcommitが失敗した証拠ではない、それを証拠として扱うことがバッチを二重に投稿する道である。この区別が最も重要なのは再試行の経路であり、そこではオペレーターはエラーメッセージだけを持ち、他に行動の手がかりがない。
だから復旧の手順は、エラー文から推測するのではなく、ターゲットから結果を読む必要がある。
転送にバッチIDを与える
すべての実行は不変のバッチIDを持ち、ターゲットはそのIDで何をしたかを記録する:ライフサイクルは四つのステップで、ベンダーに関係なく同じ形をしている。
- 抽出 記録されたスキーマと境界を持つ、識別された不変のソースバッチ。
- 転送 ターゲットのステージング領域へ移し、準備完了と宣言する前に不完全または矛盾する内容を検出する。
- 適用 ターゲット自身の一意性またはロックの規則の下で実行し、同じローカルトランザクションで完了を記録する。
- 照合 サポートされたプロトコルを通じて、確認応答や再試行の前に永続的な結果を確認する。
再試行は同じIDを再利用する。試行ごとに新しいIDを作ると、今作った重複防止が台無しになり、それは再試行ラッパーが自動的にIDを生成するまで自明に思える。
IDはまた、クライアントプロセスが消えたずっと後で照合するための手がかりにもなる。
バッチを確保してから適用する
適用のステップは、バッチIDを確保し、業務上の変更を行い、完了を一つのローカルトランザクションで記録しなければならない。チェックと挿入を別々の文として行うと、取り除こうとした競合が戻ってくる。二人のワーカーが競合すれば、ちょうど一つの確保が勝ち、もう一方にはそう伝えられる。
完了記録がないことは、進行中のトランザクションが決してcommitしない証拠ではない。確保は競合する試行を直列化しなければならないし、そうでなければプロトコルが何かを決める前に永続的な結果を読まなければならない。
その直列化には、アプリケーションコードで確認して繰り返すループではなく、データベースがサポートする仕組みを使う。
設計がまだ約束できないこと
ステージングは作業を再開可能にし、一つの長寿命セッションへの依存を減らす。しかし、SQL Server、Oracle、そしてジョブが行うあらゆる外部の副作用にまたがる単一の原子的commitを作るものではない。業務が本当にそれを必要とするなら、不確かな結果の作業を誰が所有するかも含めて、サポートされた分散トランザクションや照合プロセスを設計し、適格性を確認する。それより弱いものは転送コードではなく、照合のランブックに属する。
中断を三か所で受け入れる:転送中、適用中、そしてcommit後だが確認応答の前である。そのうえでクライアントのメッセージではなく業務の結果を確認し、バッチ台帳に作業が未完了か、commit済みか、まだ未解決かを語らせる。