Distributed locking using PostgreSQL session level advisory locks
This repo is a port and modification of github.com/allisson/go-pglock and per its MIT license, has a similar MIT license with the copyright included.
pglock provides a database-backed solution to implement distributed locks across multiple processes using PostgreSQL's advisory lock mechanism. You should use pglock when you're trying to coordinate access to shared resources and do not want to use a distributed consistency mechanism. The point of failure is the PostgreSQL database, but so long as it remains up and stable, locking should be reliable.
go get go.rtnl.ai/pglockRequirements:
- Go 1.25 or higher
- PostgreSQL 15 or higher
package main
import (
"context"
"database/sql"
"fmt"
"log"
"go.rtnl.ai/pglock"
_ "github.com/lib/pq"
)
func main() {
// Connect to PostgreSQL
db, err := sql.Open("postgres", "postgres://user:pass@localhost:5432/mydb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ctx := context.Background()
// Create a lock with a unique ID
lock, err := pglock.New(ctx, pglock.Hash("mylock"), db)
if err != nil {
log.Fatal(err)
}
// Try to acquire the lock
acquired, err := log.TryLock(ctx)
if err != nil {
log.Fatal(err)
}
if acquired {
fmt.Println("Lock acquired! Doing work ...")
// Release the lock
if err := lock.Unlock(ctx); err != nil {
log.Fatal(err)
}
fmt.Println("Lock released!")
} else {
fmt.Println("Could not acquire lock - another process has it")
}
}PostgreSQL advisory locks enable powerful features for application defined distributed locking:
- Session-level locks: Locks are held until explicitly released or the database connection closes.
- Application-defined: Locks are identified by a numeric identifier that is specific to your application and use case.
- Fast and efficient: No table bloat, faster than row-level locks
- Lock stacking: A session can acquire the same lock multiple times (requires equal number of unlocks).
- Automatic cleanup: PostgreSQL automatically releases locks when the session (database connection) ends.
From the PostgreSQL Documentation:
PostgreSQL provides a means for creating locks that have application-defined meanings. These are called advisory locks, because the system does not enforce their use — it is up to the application to use them correctly. Advisory locks can be useful for locking strategies that are an awkward fit for the MVCC model.
Creates a new lock instance with a dedicated database connection.
Generates a 64-bit FNV-1a hash of the input string, allowing you to create integer IDs from more memorable human-readable strings that are easier to share between processes.
Attempts to acquire an exclusive lock blocking until either the context is canceled or the lock can be acquired from the database.
Attempts to acquire a shared lock and blocks until any exclusive locks are released or the context is canceled. Multiple sessions can acquire shared locks concurrently.
Attempts to acquire an exclusive lock without waiting. Returns immediately with true if acquired, false otherwise.
Attempts to acquire a shared lock without waiting. Returns immediately with true if acquired, false otherwise. Multiple sessions can acquire shared locks concurrently.
Releases one level of exclusive lock ownership. Must be called equal to the number of Lock/TryLock calls that succeed.
Releases one level of shared lock ownership. Must be called equal to the number of RLock/TryRLock calls that succeed.
Closes the underlying database connection (returning it to the pool) and ending the database session which has the effect of releasing all held locks (both exclusive and shared).