perf(msgqueue): optimistic insert + heal-on-23503 — drop the per-publish parent-row lock#5
Closed
hydeta wants to merge 1 commit into
Closed
perf(msgqueue): optimistic insert + heal-on-23503 — drop the per-publish parent-row lock#5hydeta wants to merge 1 commit into
hydeta wants to merge 1 commit into
Conversation
…arent upsert AddMessageEnsuringQueue now tries the plain MessageQueueItem insert first (KEY SHARE on the parent only, so publishers don't serialize) and runs the atomic ensure-queue CTE only when the insert fails with a foreign-key violation, i.e. when the parent was actually reaped. The heal refreshes lastActive, so a healed queue is not reap-eligible for another hour and subsequent publishes stay on the fast path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
|
Closing: the optimistic-insert design lets an actively-published (publisher-only) queue be reaped hourly, and any items enqueued in the ≤5min before the reap are orphaned (queueId = NULL via ON DELETE SET NULL) and become invisible to ReadMessages. The deployed per-publish lastActive refresh prevents that reap entirely, so this PR would regress delivery of recent fanout items to late-attaching consumers. A follow-up should keep the queue alive with a throttled lastActive touch instead of a per-publish exclusive lock. |
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Description
Follow-up to #2/#3. The self-heal closed the
MessageQueueItem_queueId_fkey(23503) race, but it runsON CONFLICT ("name") DO UPDATE SET "lastActive" = NOW()on the parentMessageQueuerow for every publish to an auto-deleted queue.DO UPDATEtakes an exclusive row lock on the conflicting tuple, so all concurrent publishers to the same queue (tenant exchanges, the dispatcher queue after #3 widened the gate) serialize on one row, and every message rewrites it (dead-tuple churn, WAL, vacuum pressure).Production impact (Cloud SQL Query Insights,
hatchet-production, 1h window): ~68,300s of lock waits against ~80,650s total execution time — 85% of all DB time is lock waiting, and 99% of it is this one query (55,600s of its 59,600s runtime, 93%, is lock wait;hwtuple locks dominate).Fix — same guarantee, off the hot path
AddMessageEnsuringQueuenow tries the plainMessageQueueIteminsert first. The FK check takes only a KEY SHARE lock on the parent (shared — publishers never block each other), and it fails with 23503 exactly when the parent has been reaped. On that error — and only then — it runs the existing atomic ensure-queue CTE.Why this still closes the #2 race, with no cache-TTL coupling:
lastActive = NOW()andCleanupMessageQueue's threshold is 1h idle, so the retried path cannot lose the race again and the queue pays at most one heal per idle-hour, not one exclusive lock per message.Non-auto-deleted queues still propagate 23503 unchanged — they are never reaped, so a missing parent there is a real bug that must not be masked.
What's Changed
AddMessageEnsuringQueueis now optimistic-insert + heal-on-FK-violation; the CTE query itself is untouched, all fix(msgqueue): self-heal auto-deleted queues on enqueue to prevent FK violation (alt to #1) #2/fix(msgqueue): self-heal exclusive auto-deleted queues too (closes FK gap in #2) #3 call-site routing unchangedisForeignKeyViolationhelper (matchespgerrcode.ForeignKeyViolation, mirroring the existingisLockNotAvailablepattern intask.go)lastActivenot rewritten); a reaped queue self-heals on publish with bind attrs +lastActivepreserved and no 23503 escaping; non-auto-deleted queues still surface the FK violationType of change
Checklist
go build ./pkg/repository/...andgo vet ./pkg/repository/cleango test ./pkg/repository/ -run 'AddMessageEnsuringQueue|BindRefreshesLastActive'— all 7 pass (4 existing + 3 new) against testcontainers PostgresPost-deploy verification
insights/perquery/lock_timeforosiris-app-production:hatchet-productionshould drop from ~68,000s/hr to near zero and theensure_queuequerystring should leave the top-N by lock wait entirely.🤖 Generated with Claude Code