2

PostgreSQL で以下のようなクエリがあります。

UPDATE 
     queue 
SET 
  queue.status   = 'PROCESSING' 
WHERE 
    queue.status   = 'WAITING' AND
    queue.id       = (SELECT id FROM queue WHERE STATUS = 'WAITING' LIMIT 1 )
RETURNING 
        queue.id

多くのワーカーは一度に 1 つの作業を処理しようとします (そのため、制限 1 のサブクエリがあります)。今回の更新以降、ワーカーごとにidの情報を取得して処理を行っていますが、同じ作業を2回以上取得して処理する場合があります。分離レベルは Read Committed です。

私の質問は、1 つの作品が 1 回処理されることを保証するにはどうすればよいですか? 非常に多くの投稿があることは知っていますが、それらのほとんどを試してみましたが、役に立ちませんでした () ;

  • SELECT FOR UPDATE を試しましたが、デッドロック状態になりました。
  • pg_try_advisory_xact_lock を試しましたが、共有メモリが不足しました
  • AND pg_try_advisory_xact_lock(queue.id)外側のクエリのWHERE句に追記してみたのですが… [?]

どんな助けでも大歓迎です。

4

2 に答える 2

6

あなたが説明した状況では、更新が失われることはありませんが、正しく機能しません。

上記の例で何が起こるかというと、(たとえば) 10 個のワーカーが同時に開始された場合、10 個すべてのワーカーがサブクエリを実行し、同じ IDを取得します。それらはすべてその ID をロックしようとします。そのうちの 1 つが成功します。他のものは最初のもののロックでブロックします。最初のバックエンドがコミットまたはロールバックすると、他の 9 つのバックエンドがロックを求めて競合します。それを取得し、 WHERE 句を再チェックして、queue.statusテストが一致しなくなったことを確認し、行を変更せずに戻ります。同じことが他の 8 つでも起こります。したがって、1 つのクエリの作業を行うために 10 のクエリを使用しました。

結果を明示的にチェックせず、UPDATE更新された行がゼロであることがわかった場合、更新が失われたと考えるかもしれませんが、そうではありません。実行順序と分離ルールの誤解が原因で、アプリケーションに同時実行バグが発生しただけです。実際に起こっていることは、一度に 1 つだけが実際に前進するように、バックエンドを効果的にシリアライズしていることだけです。

PostgreSQL がそれらすべてに同じキュー アイテム ID を取得させないようにする唯一の方法は、それらをシリアル化することです。そのため、クエリ #1 が完了するまでクエリ #2 の実行を開始しませんでした。LOCK必要に応じて、キュー テーブルを ing することでこれを行うことができます。

とにかく簡単ではありませんが、アドバイザリロックでこれを回避することはできません。最初のロック可能なアイテムを取得するまで、ノンブロッキング ロック試行を使用してキューを反復処理するハックは機能しますが、遅くてぎこちないでしょう。

RDBMS を使用してワーク キューを実装しようとしています。これではうまくいきません。それは遅く、苦痛であり、正確かつ迅速に行うことは非常に困難です。自分で巻かないでください。代わりに、十分に確立され、十分にテストされたシステムを使用して、信頼性の高いタスク キューイングを行います。RabbitMQ、ZeroMQ、Apache ActiveMQ、Celery などを見てください。PostgreSQL ベースのソリューションである Skytools のPGQもあります。

関連している:

于 2013-01-25T06:19:00.193 に答える
0

SKIP LOCKEDPostgreSql でキューを実装するために使用できます。見る

于 2021-06-16T08:28:50.160 に答える