楽観的ロックが機能しない場合はどうすればよいですか?


11

私は次のシナリオを持っています:

  1. ユーザーがETagに対してGETリクエストを/projects/1行い、ETagを受け取ります。
  2. ユーザーは、ステップ1のETag を使用してPUTリクエストを/projects/1行います。
  3. ユーザーは/projects/1、ステップ1のETag を使用して別のPUTリクエストを行います。

ETagが古くなっているため、通常、2番目のPUTリクエストは412応答を受け取ります。最初のPUTリクエストがリソースを変更したため、ETagは一致しなくなりました。

しかし、2つのPUT要求が同時に(またはちょうど1つずつ)送信された場合はどうなりますか?最初のPUT要求には、PUT#2が到着する前にリソースを処理および更新する時間がないため、PUT#2がPUT#1を上書きします。楽観的ロックの要点は、それが起こらないようにすることです...


3
Esbenが以下で説明するように、ビジネスレベルのトランザクションで操作を原子化します。
ロバートハーヴェイ

トランザクションを使用して操作をアトマイズするとどうなりますか?PUT#2は、PUT#1が完全に処理されるまで処理されませんか?
maximedupre

7
悲観論者になりますか?
jpmc26 2018年

これがロックの目的です。
Fattie

もちろん、Put#2は処理すべきではありません-それらは一意であることになっています。
Fattie

回答:


21

ETagメカニズムは、楽観的ロックの通信プロトコルのみを指定します。同時更新を検出して楽観的ロックを実施するメカニズムを実装するのは、アプリケーションサービスの責任です。

データベースを使用する一般的なアプリケーションでは、通常、PUTリクエストを処理するときにトランザクションを開いてこれを行います。通常、そのトランザクション内のデータベースの既存の状態を読み取り(読み取りロックを取得するため)、Etagの有効性を確認し、データを上書きします(互換性のない並行トランザクションがある場合に書き込みの競合が発生するような方法で)。次にコミットします。トランザクションを正しく設定すると、両方のコミットが同じデータを同時に更新しようとするため、コミットの1つが失敗するはずです。その後、このトランザクションの失敗を使用して、412を返すか、アプリケーションにとって意味がある場合は、要求を再試行できます。


サーバーが現在並行更新を検出するメカニズムを実装する方法は、リソースのハッシュを比較することです。サーバーもすべての操作にトランザクションを使用しますが、ロックを取得していないため、問題が発生する可能性があります。しかし、あなたの例では、トランザクションがロックを使用している場合、コミットの1つでエラーが発生する可能性がありますか?2番目のトランザクションは、最初のトランザクションが解決されるまで、状態を読み取るときに保留されている必要があります。
maximedupre

1
@maximedupre:トランザクションを使用している場合、暗黙的なロックである可能性がありますが、何らかのロックがあります(ロックは、明示的に要求されるのではなく、フィールドの読み取り/更新時に自動的に取得されます)。上記で説明したメカニズムは、暗黙のロックだけを使用して実装できます。他の質問として、それは使用しているデータベースに依存しますが、最近の多くのデータベースはMVCC(マルチバージョン同時実行制御)を使用して、複数のリーダーとライターが不必要にお互いをブロックせずに同じフィールドで作業できるようにします。
ライライアン

1
警告:多くのDBMS(PostgreSQLのは、Oracle、SQL Serverの、など)には、デフォルトのトランザクション分離レベルは、「読みコミット」され、あなたのアプローチがありません OPの競合状態を防止するのに十分なほど。このようなDMBSではAND ETag = ...UPDATEステートメントのWHERE句に含め、後で更新された行数をチェックすることで修正できます。(または、より厳密なトランザクション分離レベルを使用しますが、実際にはお勧めしません。)
ruakh

1
@ruakh:クエリの記述方法によって異なります。デフォルトの分離レベルでは、このような動作がすべてのクエリに自動的に提供されるわけではありませんが、楽観的ロックを実装するのに十分な方法でトランザクションを構造化することは可能です。ほとんどの場合、アプリケーションでトランザクションの一貫性が重要であれば、とにかくデフォルトの分離レベルとして繰り返し可能な読み取りをお勧めします。MVCCを使用するデータベースでは、反復可能読み取りのオーバーヘッドはかなり少なく、アプリケーションが大幅に簡素化されます。
リーライアン

1
@ruakh:反復可能な読み取りの主な欠点は、同時トランザクションがある場合、再試行または失敗する準備が必要になることです。これは通常問題ですが、並行性戦略として楽観的ロックを提供するアプリケーションでは、とにかくこの処理が必要になるため、繰り返し可能な読み取りの失敗は楽観的ロックの失敗に自然に対応し、実際には新しい欠点は追加されません。
リーライアン

13

次のペアをアトミックに実行する必要があります。

  • タグの有効性のチェック(つまり、最新)
  • リソースの更新(タグの更新を含む)

他の人はこれをトランザクションと呼んでいます—しかし、基本的には、これら2つの操作のアトミックな実行が、一方が他方のタイミングを誤って上書きするのを防ぎます。これがないと、気づいているように、競合状態になります。

全体像を見ると、これは依然として楽観的ロックと見なされています。リソース自体は、更新の目的であるかどうかに関係なく、データを見ているユーザーまたはユーザーによる最初の読み取り(GET)によってロックされていません。

いくつかのアトミックな動作が必要ですが、これは、複数のネットワーク対話でロックを保持しようとするのではなく、単一の要求(PUT)内で発生します。これは楽観的ロックです。オブジェクトはGETによってロックされていませんが、PUTによって安全に更新できます。

これら2つの操作のアトミックな実行を実現する方法も数多くあります。リソースをロックすることだけが選択肢ではありません。たとえば、軽量スレッドまたはオブジェクトロックで十分な場合があり、アプリケーションのアーキテクチャと実行コンテキストに依存します。


4
重要なのはアトミックであることに注意してください。更新される基になるリソースに応じて、これはトランザクションやロックなしで実行できます。たとえば、メモリ内リソースのアトミックな比較とスワップ、永続化されたデータのイベントソーシングなどです。
アーロンM.エシュバッハ2018年

@ AaronM.Eshbach、同意しました。
Erik Eidt、2018年

1

実際にE-Tagをチェックしてそのロジックを提供するのは、アプリケーション開発者の責任です。E-Tag静的コンテンツのヘッダーを計算する方法しか知らないため、Webサーバーがあなたのために行うのは魔法ではありません。上記のシナリオを取り上げて、相互作用がどのように発生するかを分析してみましょう。

GET /projects/1

サーバーはリクエストを受信し、このバージョンのレコードのEタグを決定して、実際のコンテンツとともにそれを返します。

200 - OK
E-Tag: "412"
Content-Type: application/json
{modified: false}

クライアントにはE-Tag値が含まれているため、PUTリクエストに含めることができます。

PUT /projects/1
If-Match: "412"
Content-Type: application/json
{modified: true}

この時点で、アプリケーションは次のことを行う必要があります。

  • E-Tagがまだ正しいことを確認します: "412" == "412"?
  • その場合は、更新して新しいEタグを計算します

成功の応答を送信します。

204 No Content
E-Tag: "543"

別のリクエストが来PUTて、上記のリクエストと同様のリクエストを実行しようとした場合、サーバーコードが2回目に評価するときに、エラーメッセージを提供する必要があります。

  • Eタグがまだ正しいことを確認します: "412"!= "543"

失敗したら、失敗応答を送信します。

412 Precondition Failed

これはあなたが実際に書かなければならないコードです。Eタグは実際には任意のテキスト(HTTP仕様で定義された制限内)にすることができます。数字である必要はありません。ハッシュ値にすることもできます。


これは、ここで使用している標準のHTTP表記ではありません。標準準拠のHTTPでは、応答ヘッダーでのみETagを使用します。リクエストヘッダーでETagを送信するのではなく、以前に取得したETag値をリクエストヘッダーのIf-MatchまたはIf-None-Matchヘッダーで使用します。
ライライアン

-2

他の回答を補足するものとして、根本的な問題を忠実に説明しているZeroMQのドキュメントで最高の引用の1つを投稿します。

完全に完璧なMTプログラムを作成するには(つまり、文字通りそれを意味します)、ZeroMQソケットを介して送信されるメッセージを除いて、ミューテックス、ロック、または他の形式のスレッド間通信は必要ありません。

「完璧なMTプログラム」とは、記述と理解が簡単で、どのプログラミング言語でも、どのオペレーティングシステムでも同じ設計アプローチで機能し、待機状態がゼロでポイントのないCPUをいくつでもスケーリングできるコードを意味します収益の減少。

ロックとセマフォ、クリティカルセクションを使用して、MTコードをすばやく機能させるために、何年もトリックを学ぶのに費やしてきたとしたら、何もかもが無駄であることに気付くとうんざりします。30年以上の並行プログラミングから学んだ教訓が1つあれば、それは単に状態を共有しないことです。ビールを共有しようとする2人の酔っ払いのようなものです。彼らが良い友達かどうかは関係ありません。遅かれ早かれ、彼らは戦いに入るでしょう。そして、あなたがテーブルに追加する酔っぱらいが増えるほど、彼らはビールをめぐって互いに戦います。MTアプリケーションの悲劇的な大部分は酒に酔った棒の戦いのように見えます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.