Skip to content

Use advisory locks to guard against concurrent pgslice invocations#16

Merged
mthadley merged 12 commits intomasterfrom
advisory-locks
Feb 5, 2026
Merged

Use advisory locks to guard against concurrent pgslice invocations#16
mthadley merged 12 commits intomasterfrom
advisory-locks

Conversation

@mthadley
Copy link
Collaborator

@mthadley mthadley commented Feb 5, 2026

Wraps all of the core Pgslice operations (prep, fill, swap, etc.) in advisory locks to minimize the risks in cases where multiple pgslice operations were accidentally executed against the same table.

Note that this required refactoring some of the connection handling, as operations like fill were not ensuring they had checked out a single connection for the lifetime of their operation. Now .start passes a connection instead of a transaction, which means that many operations now need to start the transaction themselves, hence all of the indentation.

override async perform(pgslice: Pgslice): Promise<void> {
await pgslice.start(async (tx) => {
await this.context.pgslice.disableMirroring(tx, { table: this.table });
await pgslice.disableMirroring(tx, { table: this.table });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/consistency change?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I should actually clean this up: There's two ways in a command to get access to the Pgslice instance (context or the perform parameter) but we should probably just have one.

I can look at that later.

): Promise<boolean> {
const result = await connection.one(
sql.type(z.object({ acquired: z.boolean() }))`
SELECT pg_try_advisory_lock(${key}) AS acquired
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative would be row based locks on a dedicated table which might have easier debugging, but I can see why session-level advisory locks make more sense given fill's batch processing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, and if we end up having issues with this advisory locking strategy, maybe we even do that. But it's nice to have pgslice not depend on a specific schema in that way.

src/pgslice.ts Outdated

async #acquireLock(
connection: CommonQueryMethods,
table: string,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Worth taking a Table arg directly for consistency with withLock?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! I'll make that change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base automatically changed from port-to-typescript to master February 5, 2026 19:26
mthadley and others added 11 commits February 5, 2026 11:31
Use PostgreSQL session-level advisory locks (pg_try_advisory_lock) to prevent
concurrent operations on the same table. Lock keys are generated from the
combination of table name and operation name via hashtext(). Locks are
non-blocking and fail immediately with AdvisoryLockError if unavailable.

Adds advisoryLocks option to PgsliceOptions (default: true) to allow disabling
locks for testing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
They were broken previously because we were not explicitly checking out
and reusing the same connection for the duration of the operation,
leading to a "Attempted to release lock that was never held." error.
@mthadley mthadley merged commit aa56e4f into master Feb 5, 2026
7 checks passed
@mthadley mthadley deleted the advisory-locks branch February 5, 2026 20:12
@mthadley mthadley mentioned this pull request Feb 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants