blog: remove em dashes from SKIP LOCKED post - #8080
Conversation
Punctuation-only pass per author preference. The demo's log separator changed from an em dash to a hyphen; the expected-output block is a fresh captured run with the new separator.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThe PostgreSQL job queue article received editorial revisions covering queue concepts, worker behavior, retry and crash recovery, dedicated queue guidance, FAQs, recap text, and matching demo output. ChangesPostgreSQL job queue article
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/blog/content/blog/you-dont-need-a-job-queue-postgres-already-has-skip-locked/index.mdx`:
- Line 421: In the pg-boss paragraph, update the phrase “needs outgrow” to
“needs to outgrow,” preserving the surrounding sentence and link unchanged.
- Line 401: The blog’s email deduplication guidance must not imply that writing
an emails_sent row after sending is sufficient. Update the discussion around the
queue’s at-least-once semantics to prefer a provider-enforced idempotency key,
or describe a durable send/retry state machine that explicitly handles crashes
between sending and recording success.
- Line 92: Update the partial-index discussion in the blog post to limit index
churn to updates that remove or rewrite entries matching the status = 'pending'
predicate, rather than claiming every status flip creates a dead index entry.
Retain the point that PostgreSQL relies on autovacuum/vacuum to clean obsolete
index entries.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5b3c5c72-47dc-4183-b5f6-c59899da397a
📒 Files selected for processing (1)
apps/blog/content/blog/you-dont-need-a-job-queue-postgres-already-has-skip-locked/index.mdx
| ``` | ||
|
|
||
| The partial index only covers pending rows, so the claim query doesn't wade through the pile of completed jobs. It still relies on autovacuum keeping up — every status flip leaves a dead index entry behind, a limit we'll come back to. | ||
| The partial index only covers pending rows, so the claim query doesn't wade through the pile of completed jobs. It still relies on autovacuum keeping up: every status flip leaves a dead index entry behind, a limit we'll come back to. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Scope the partial-index churn claim to pending transitions.
“Every status flip” is too broad: a running → done transition does not remove a row from the pending-only index. Describe churn as occurring when updates remove or rewrite entries covered by the status = 'pending' predicate. PostgreSQL cleans obsolete index entries through vacuum. (postgresql.org)
Proposed wording
- It still relies on autovacuum keeping up: every status flip leaves a dead index entry behind, a limit we'll come back to.
+ It still relies on autovacuum keeping up: updates that remove a row from the pending predicate or rewrite its indexed values leave dead index entries behind, a limit we'll come back to.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| The partial index only covers pending rows, so the claim query doesn't wade through the pile of completed jobs. It still relies on autovacuum keeping up: every status flip leaves a dead index entry behind, a limit we'll come back to. | |
| The partial index only covers pending rows, so the claim query doesn't wade through the pile of completed jobs. It still relies on autovacuum keeping up: updates that remove a row from the pending predicate or rewrite its indexed values leave dead index entries behind, a limit we'll come back to. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apps/blog/content/blog/you-dont-need-a-job-queue-postgres-already-has-skip-locked/index.mdx`
at line 92, Update the partial-index discussion in the blog post to limit index
churn to updates that remove or rewrite entries matching the status = 'pending'
predicate, rather than claiming every status flip creates a dead index entry.
Retain the point that PostgreSQL relies on autovacuum/vacuum to clean obsolete
index entries.
Source: MCP tools
| The alternative holds the claim inside an open transaction for the length of the job, so a crash rolls the row back automatically. That costs a pinned connection per job, undoes the `attempts` increment (a job that crashes its worker retries forever), and holds back vacuum on a busy queue. | ||
|
|
||
| Either way, this queue delivers at least once, not exactly once — no recovery scheme escapes that. A crash after the work but before the `done` update, a janitor reset of a slow worker, even a connection blip between finishing a job and recording it: each can run a job's side effect twice. Write handlers so that running them twice is safe. For the welcome email that opened this post, that means a dedupe key: an `emails_sent` row written on success and checked before sending, or an idempotency key passed to the email provider, so a repeat run finds the work already done and skips it. The same move covers any side effect — give it a key, check the key first. | ||
| Either way, this queue delivers at least once, not exactly once; no recovery scheme escapes that. A crash after the work but before the `done` update, a janitor reset of a slow worker, even a connection blip between finishing a job and recording it: each can run a job's side effect twice. Write handlers so that running them twice is safe. For the welcome email that opened this post, that means a dedupe key: an `emails_sent` row written on success and checked before sending, or an idempotency key passed to the email provider, so a repeat run finds the work already done and skips it. The same move covers any side effect: give it a key, check the key first. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Do not present a post-send success row as sufficient deduplication.
If the email is sent and the process crashes before emails_sent is written, the retry finds no marker and sends the email again. Prefer a provider-enforced idempotency key, or describe a durable state machine whose send/retry protocol explicitly handles this crash window.
Proposed wording
- that means a dedupe key: an `emails_sent` row written on success and checked before sending, or an idempotency key passed to the email provider, so a repeat run finds the work already done and skips it.
+ that means an idempotency key enforced by the email provider, or a durable deduplication state machine designed for crash recovery. A success row written after sending alone does not prevent duplicates.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Either way, this queue delivers at least once, not exactly once; no recovery scheme escapes that. A crash after the work but before the `done` update, a janitor reset of a slow worker, even a connection blip between finishing a job and recording it: each can run a job's side effect twice. Write handlers so that running them twice is safe. For the welcome email that opened this post, that means a dedupe key: an `emails_sent` row written on success and checked before sending, or an idempotency key passed to the email provider, so a repeat run finds the work already done and skips it. The same move covers any side effect: give it a key, check the key first. | |
| Either way, this queue delivers at least once, not exactly once; no recovery scheme escapes that. A crash after the work but before the `done` update, a janitor reset of a slow worker, even a connection blip between finishing a job and recording it: each can run a job's side effect twice. Write handlers so that running them twice is safe. For the welcome email that opened this post, that means an idempotency key enforced by the email provider, or a durable deduplication state machine designed for crash recovery. A success row written after sending alone does not prevent duplicates. The same move covers any side effect: give it a key, check the key first. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apps/blog/content/blog/you-dont-need-a-job-queue-postgres-already-has-skip-locked/index.mdx`
at line 401, The blog’s email deduplication guidance must not imply that writing
an emails_sent row after sending is sufficient. Update the discussion around the
queue’s at-least-once semantics to prefer a provider-enforced idempotency key,
or describe a durable send/retry state machine that explicitly handles crashes
between sending and recording success.
| - **Queue features as a product.** Rate limiting, per-queue concurrency caps, job priorities, repeatable jobs: [BullMQ](https://bullmq.io) ships all of it. Rebuilding it on a jobs table is possible and rarely worth it. | ||
|
|
||
| There's also a middle path: [pg-boss](https://github.com/timgit/pg-boss) is this exact pattern — `SKIP LOCKED` on a Postgres table — packaged as a library with retries, scheduling, and retention policies already written. If your queue needs outgrow a hand-rolled table but your throughput still fits in Postgres, reach for it before reaching for a broker. | ||
| There's also a middle path: [pg-boss](https://github.com/timgit/pg-boss) is this exact pattern, `SKIP LOCKED` on a Postgres table, packaged as a library with retries, scheduling, and retention policies already written. If your queue needs outgrow a hand-rolled table but your throughput still fits in Postgres, reach for it before reaching for a broker. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the missing infinitive.
Change “needs outgrow” to “needs to outgrow.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apps/blog/content/blog/you-dont-need-a-job-queue-postgres-already-has-skip-locked/index.mdx`
at line 421, In the pg-boss paragraph, update the phrase “needs outgrow” to
“needs to outgrow,” preserving the surrounding sentence and link unchanged.
Punctuation-only follow-up to #8079, which merged while this pass was in flight. Replaces every em dash in the post with a comma, colon, period, or parentheses per author style. The demo's log separator changed from an em dash to a hyphen, and the expected-output block is a fresh captured run with the new separator, so the shown output stays a real run. No content changes.
Summary by CodeRabbit