Testing Postgres race conditions with synchronization barriers
1–10 of 60 posts
Re: Testing Postgres race conditions with synchronization barriers
#2IMHO you should never write code like that, you can either do UPDATE employees SET salary = salary + 500 WHERE employee_id = 101;
Or if its more complex just use STORED PROCEDURE, there is no point of using database if you gonna do all transactional things in js
Re: Testing Postgres race conditions with synchronization barriers
#3Thats not postgresql problem, thats your code IMHO you should never write code like that, you can either do UPDATE employees SET salary = salary + 500 WHERE employee_id = 101; Or if its more complex just use STORED PROCEDURE, there is no point of using database if you gonna do all transactional things in js
Re: Testing Postgres race conditions with synchronization barriers
#4Thats not postgresql problem, thats your code IMHO you should never write code like that, you can either do UPDATE employees SET salary = salary + 500 WHERE employee_id = 101; Or if its more complex just use STORED PROCEDURE, there is no point of using database if you gonna do all transactional things in js
await db().transaction(async (tx) => { await hooks?.onTxBegin?.();
const [order] = await tx.select().from(orders)
.where(eq(orders.id, input.id))
.for("update");
const [status] = await tx.select().from(orderStatuses)
.where(eq(orderStatuses.orderId, input.id))
.orderBy(desc(orderStatuses.createdAt))
.limit(1);
if (input.status === status.code)
throw new Error("Status already set");
await tx.insert(orderStatuses).values({ ... });
});You need the transaction + SELECT FOR UPDATE because the validation depends on current state, and two concurrent requests could both pass the duplicate check. The hooks parameter is the barrier injection point from the article - that's how you test that the lock actually prevents the race.
Re: Testing Postgres race conditions with synchronization barriers
#5Thats not postgresql problem, thats your code IMHO you should never write code like that, you can either do UPDATE employees SET salary = salary + 500 WHERE employee_id = 101; Or if its more complex just use STORED PROCEDURE, there is no point of using database if you gonna do all transactional things in js
Here's a real-world example where atomic updates aren't an option - an order status transition that reads the current status from one table, validates the transition, and inserts into another: await db().transaction(async (tx) => { await hooks?.onTxBegin?.(); const [order] = await tx.select().from(orders) .where(eq(orders.id, input.id)) .for("update"); const [status] = await tx.select().from(orderStatuses) .where(eq(…
Add a numeric version column to the table being updated, read and increment it in the application layer and use the value you saw as part of the where clause in the update statement. If you see ‘0 rows updated’ it means you were beaten in a race and should replay the operation.
Re: Testing Postgres race conditions with synchronization barriers
#6Earlier quoted context omitted.
Here's a real-world example where atomic updates aren't an option - an order status transition that reads the current status from one table, validates the transition, and inserts into another: await db().transaction(async (tx) => { await hooks?.onTxBegin?.(); const [order] = await tx.select().from(orders) .where(eq(orders.id, input.id)) .for("update"); const [status] = await tx.select().from(orderStatuses) .where(eq(…
The standard pattern to avoid select for update (which can cause poor performance under load) is to use optimistic concurrency control. Add a numeric version column to the table being updated, read and increment it in the application layer and use the value you saw as part of the where clause in the update statement. If you see ‘0 rows updated’ it means you were beaten in a race and should replay the operation.
Re: Testing Postgres race conditions with synchronization barriers
#7Thats not postgresql problem, thats your code IMHO you should never write code like that, you can either do UPDATE employees SET salary = salary + 500 WHERE employee_id = 101; Or if its more complex just use STORED PROCEDURE, there is no point of using database if you gonna do all transactional things in js
Here's a real-world example where atomic updates aren't an option - an order status transition that reads the current status from one table, validates the transition, and inserts into another: await db().transaction(async (tx) => { await hooks?.onTxBegin?.(); const [order] = await tx.select().from(orders) .where(eq(orders.id, input.id)) .for("update"); const [status] = await tx.select().from(orderStatuses) .where(eq(…
WITH
o AS (
SELECT FROM orders
WHERE orders.id = $1
),
os AS (
SELECT FROM orderStatuses
WHERE orderStatuses.orderId = $1
ORDER BY DESC orderStatuses.createdAt
LIMIT 1
)
INSERT INTO orderStatuses ...
WHERE EXISTS (SELECT 1 FROM os WHERE os.code != $2)
RETURNING ...something including the status differ check...
Does something like this work with postgres's default behavior?Re: Testing Postgres race conditions with synchronization barriers
#8Re: Testing Postgres race conditions with synchronization barriers
#9Re: Testing Postgres race conditions with synchronization barriers
#10It'd be interesting to see a version of this that tries all the different interleavings of PostgreSQL operations between the two (or N) tasks. https://crates.io/crates/loom does something like this for Rust code that uses synchronization primitives.