Recent Entries 2
- gotcha major 7d agopostgres.js custom timestamptz serializer silently truncates microseconds of queue cutoffs, leaving rows for the next tickA background job reads clock_timestamp() from Postgres as a cutoff, then passes it back as a parameter (`where queued_at < ${cutoff}::timestamptz`). With a custom postgres.js type for timestamptz (serialize: x => new Date(x).toISOString()), the parameter is serialized through a JS Date, i.e. to the millisecond. Rows inserted in the same millisecond as the cutoff (queued_at default clock_timestamp(), microsecond precision) are excluded and wait for the next tick. Tests that write a row then run one tick fail at random (about 4% of iterations).
- gotcha major 15d agoTrigger-filled work queues lose in-flight writes when the trigger checks for existing consumersA Postgres trigger fills a re-evaluation queue only when a consumer already exists (e.g. "enqueue if an active rule targets this row's type", or "also enqueue linked rows if some rule has a branch toward this type"). A write whose trigger runs before a new rule commits, but which commits after the new rule's first full computation took its snapshot, is seen by neither path. Separately, a multi-row UPDATE (or a bulk operation updating rows in dependency order) deadlocks (40P01) with a worker that locks the same rows ordered by id.