mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 14:00:37 +00:00
The drain locked the audit_log rows it reads, but the rows it writes are the pendings: each consumer owns its queue and deletes its own pendings. Locking the shared table made the consumers wait for each other, and the events consumer had to skip the locked rows with SKIP LOCKED to avoid that wait. The lock moves to audit_log_pending, so every consumer locks its own queue and no consumer waits for another one. The rows of audit_log are only read now, and every delete that takes them away (the drained chunk and the orphan sweep) skips the ones another transaction holds instead of waiting for them. The SKIP LOCKED variant of the chunk query is gone: the events have to be executed in order, like an event log, so a second run (another backend, or a retry overlapping the previous one) waits for the pendings of the run in flight and takes the next events. Skipping the locked ones would hand the newer events to the second run while the older ones were still in flight. The lock is FOR NO KEY UPDATE, not FOR UPDATE: deleting an audit_log row makes the foreign key check take FOR KEY SHARE on the pending rows that reference it, and those belong to the other consumers. FOR KEY SHARE does not conflict with FOR NO KEY UPDATE, so the check never waits for a drain in flight and the two consumers cannot deadlock. It still conflicts with itself, so two runs of the same consumer take turns. AI-assisted-by: minimax-m3.1-flash