kakudooo docs

PostgreSQL | Advisory Lockとは?

An advisory lock is a mutex used to ensure no two processes run some process at the same time. When the advisory lock is powered by your database server, your mutex spans hosts.

参照: https://github.com/ClosureTree/with_advisory_lock

同じ処理が同時に実行されないようにするためのmutexである。advisory lockがデータベースサーバーによって管理されている場合、そのmutexはホストをまたいで機能する。

mutexとは?

alt text

同時に一つのタスクのみがクリティカルセクション(単一の計算資源に対して複数処理が同時期に実行されると整合性がとれなくなる箇所)に入ることを許可する仕組み(ここで言うタスクとは、スレッドまたはプロセスを指す)

参照: https://ja.wikipedia.org/wiki/%E3%83%9F%E3%83%A5%E3%83%BC%E3%83%86%E3%83%83%E3%82%AF%E3%82%B9

PostgreSQLとAdvisory Lock

アプリケーション側で定義可能なロック作成の仕組み。PostgreSQLの他のロックとは異なり、使用を強制するものではないことからAdvisory Lockという名前がつけられている。

flat file型という悲観的ロックの手法(ファイルを作成し存在する間は他のプロセスが同じリソースにアクセスしないように制御する)がある。 この手法を用いたロックの実現において、同じようにテーブルでフラグを管理することがあるが、それと比較して

という利点がある。 Advisory LockはこのようなMVCCアーキテクチャと適合しづらい部分での使用が想定されている。

session levelとtransaction levelのAdvisory Lock

PostgreSQLでは、Advisory Lockを取得するために2つの方法が用意されている。

session level

明示的に解放するかセッションが終了するまでAdvisory Lockが保持される。 また、この時トランザクションの挙動から影響を受けない(トランザクション中に取得されたロックは、そのトランザクションが後でロールバックされたとしても保持され続ける。同じくその後でトランザクションが失敗してもロックを解放することができる)

同一セッションでは、同じロックを複数回取得することができる。この場合、ロックを完全に解放するためには、取得した回数と同じだけロックを解放する必要がある。

transaction level

トランザクションの終了時に自動的に解放され、明示的にアンロックする必要がない。

補足

あるセッションがすでに特定のアドバイザリロックを保持している場合、そのセッションからの追加のロック要求は、他のセッションが待機していたとしても常に成功する。

ロックの保持先

max_locks_per_transactionやmax_connectionsに設定されたサイズの共有メモリに保持される。 共有メモリを使い切らないように注意する必要がある。

基本的な使い方

Advisory Lockを取得するための関数は以下を参照。 Advisory Lock Functions

session level

session(connection) A

SELECT pg_advisory_lock(12345);

session(connection) B

SELECT pg_advisory_lock(12345); -- session Aのロックが解放されるまで待たされる

transaction level

session(connection) A

BEGIN;

SELECT pg_advisory_xact_lock(12345);

-- COMMIT;

session(connection) B

BEGIN;

SELECT pg_advisory_xact_lock(12345); -- session Aのロックが解放されるまで待たされる

COMMIT; -- session AのCOMMIT;後に実行される

session(connection) A

COMMIT; -- ロックが解放され、session Bのtransaction内でロックが取得される

ActiveRecordから使う場合

ActiveRecordにはAdvisory Lock用のAPIは存在しない。with_advisory_lockというgemを使うと、ActiveRecordのモデルにAdvisory Lock用のAPIを追加することができる。

例: session levelのAdvisory Lock (デフォルト)

User.with_advisory_lock(12345) do
  # ...
end

例: transaction levelのAdvisory Lock

User.transaction do
  User.with_advisory_lock("lock_name", transaction: true) do
    # ...
  end
end

参照: https://github.com/ClosureTree/with_advisory_lock?tab=readme-ov-file#transactions-and-advisory-locks

参考