Add PostgreSQL backend for distributed locks #104
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Implements PostgreSQL as a first-class backend for lease-based distributed locks, consistent with existing backends (Firestore, etcd, DynamoDB, memory).
Changes
New
locker-postgresmodulePostgresLockServicewith atomic SQL operations usingINSERT ... ON CONFLICT,UPDATE,DELETEwithRETURNING(namespace, lock_name)— no syntheticlock_idEXTRACT(EPOCH FROM now())::bigintfor database-evaluated timeSchema
Build integration
postgresprofile in root and api POMseverythingprofile for full buildspostgresvariantConfiguration
@Profile("postgres")conditional activationlocker.postgres.{host,port,database,schema,username,password,ssl,tableName}Dependencies
Original prompt
Add Postgres backend to LockServiceCentral
Implement a new Postgres backend for lease based distributed locks in UnitVectorY-Labs/LockServiceCentral, consistent with the existing backend modules (Firestore, etcd, DynamoDB, memory). The goal is to add Postgres as a first class backend option with the same external API behavior and the same conventions for module layout, config, build profiles, docs, and logging.
Scope and shape
• Add a new Maven module named something like locker-postgres/ following the same structure and conventions used by other backend modules.
• Provide a Postgres LockService implementation wired through Spring configuration, mirroring how other backends are enabled.
• Add a Postgres Maven profile at the root (and include it in any “build all” profile if one exists), following the pattern used by other backends.
• Add the appropriate conditional to the API pom when the postgres profile is active
• Ensure the main application can be built and packaged with Postgres selected the same way other backends are selected (Maven profile plus any existing Docker build-arg pattern if applicable).
Database design and atomic lock semantics
Design the table and queries so that all lock correctness is enforced by Postgres, using atomic SQL statements and database evaluated conditions. Do not implement lock edge case handling by doing reads and then deciding what to write in application code.
Key properties that must be handled by SQL conditions, atomically:
• Acquire lock when no row exists.
• Acquire lock when an existing lock is expired.
• Acquire lock when the request is from the same owner and instance and should be treated as a safe re-acquire (mirror behavior from other backends).
• Renew lock only when the lock exists, is not expired at the time of renewal, and is owned by the same owner and instance.
• Release lock only when owned by the same owner and instance.
• Treat “release of a lock that does not exist” as a successful release if that matches current cross-backend behavior.
Implementation guidance:
• Avoid “read then write” patterns that allow races. Use one statement per operation where the WHERE or ON CONFLICT conditions encode all required edge cases and ownership checks.
• Prefer Postgres-native patterns like INSERT ... ON CONFLICT ... DO UPDATE ... WHERE ... RETURNING ... for acquire, and UPDATE ... WHERE ... RETURNING ... for renew, and DELETE ... WHERE ... RETURNING ... for release. Use RETURNING to distinguish conflict vs not-found outcomes without extra queries.
• Use database time consistently (for example Postgres now() or transaction_timestamp()), so comparisons against expiry are evaluated inside the statement, not computed client side.
• Make the lock identifier and uniqueness match existing backends (for example a single primary key derived from {namespace}:{lockName}) so behavior stays aligned.
Configuration
• Add Spring configuration properties for Postgres similar in style to other backends (host, port, database, schema, username/password, SSL options if applicable, table name if configurable).
• Follow existing conventions for how configuration is documented and how defaults are handled.
• Ensure credentials can be supplied via environment variables in the same spirit as other backends.
Canonical logging and outcomes
Use the repository’s canonical logging approach consistently:
• Expected outcomes like acquire conflict, renew conflict, release conflict should not emit extra ad hoc debug log lines.
• Record outcomes in the canonical log context in the same way other backends do so each request produces one structured canonical log line and conflicts are represented as outcomes rather than extra logs. This is accomplished by using the logging library defined in this project, the intent is to avoid directly logging log lines.
• Only unexpected errors should be logged as errors, consistent with other implementations.
Make the repo build and CI aware of the new backend:
• Update the GitHub Actions workflow matrix that builds backend variants so Postgres is included alongside existing variants. 
• Update the api app POM (and any other required Maven wiring) so the Postgres backend is included only when the Postgres profile is selected, matching how other backends are conditionally included.
• If Docker build args are used to select backends, update the Docker build logic and any docs/examples so Postgres is selectable the same way.
Documentation
• Update the root README “Supported Backends” and build instructions to include Postgres as an option.
• Add a module README under locker-postgres/ describing:
• Required table schema and indexes
• Configuration properties and examples
• How expiry is represented and interpreted
• A quick local development setup suggestion (for example a simple Postgres container setup)
Testing approach
Focus on correctness of the implementation and ...
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.