Build1 distinct publisher3 min readPublished
Collapsing notification, scheduling and retry into one Postgres job table cut roughly 4,100 lines to about 600 without losing a capability, and it deleted a dual write that could drop a receipt in silence.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
`for update skip locked` is the line the whole design rests on. Postgres takes the rows the claim query matched, and when another transaction already holds one it skips that row instead of waiting behind it [13]. Twenty workers can issue the same claim statement concurrently and no two of them get the same job, with no leader, no lease service and no Redis [14]. Postgres has shipped that behaviour since 9.5 in 2016, which is the basis for the author's remark that most teams buy a broker to get something their database already does [15].
Two implementation details are load-bearing rather than decorative. The claiming transaction ends as soon as the rows are marked running, and the sending happens outside it, because a transaction held open for the length of a job pins a connection and blocks vacuum [16]. And `locked_until` is a lease, so a periodic statement has to move expired leases back, or a worker that dies mid-job strands its rows in running forever [17].
The arithmetic on the diff: 1,850 plus 1,340 plus 910 is 4,100 lines of service code [1][3], against 41 lines of table plus 560 lines of worker, which is 601 [2]. That is 3,499 lines removed, about 85 percent [1]. The three responsibilities did not go anywhere. They became columns, with `run_at` doing scheduling, `attempts` and `max_attempts` doing retry, and `kind` and `payload` doing notification [10].
The dev.to writeup reports no throughput number at all: no jobs per second, no claim latency, no vacuum measurement [2]. So the line count transfers only if a few conditions hold on your side. The ready set has to stay small, because the partial index is cheap only while a few thousand ready rows sit inside a table of millions [11]. The claim statement writes to every row it takes, incrementing `attempts` and setting a lease [12], so that write volume on top of ordinary traffic has to fit one instance's autovacuum budget. And every handler has to be idempotent, since reclaiming an expired lease can re-run a job that already reached the provider [17].
The seam is the actual finding. Each service owned something nameable, namely templates and provider delivery, timing, and backoff with dead-lettering [6], and by the author's account none of them was badly built [5]. The defect lived between them: insert the order, publish the message, and a publish that fails after the commit leaves an order that nothing downstream knows about, with no exception and no alert [7]. The queued fix was an outbox table, a relay process and consumer-side dedupe, three more parts to cover one seam [9]. Eighteen months of green dashboards across three deployments [4][5] surfaced none of it, because a boundary error does not page anyone. It shows up as a customer with no receipt and a row that looks completely fine [7].
Ranked by verification strength, evidence, and original report placement.
The pull request deleted three services: notification-svc at 1,850 lines, scheduler-svc at 1,340 lines and retry-svc at 910 lines.
The replacement is a job table defined in 41 lines of SQL plus one worker of 560 lines.
The author puts the three services at roughly 4,100 lines, not counting infrastructure.
Between the three services sat two RabbitMQ queues, a Redis for scheduled sets, three deployments, three sets of dashboards and three on-call runbooks.
notification-svc owned templates and delivery to the email, push and SMS providers; scheduler-svc owned when a message went out, including timezone-aware sends; retry-svc owned backoff, jitter, dead-letter and the alert when the dead-letter queue got deep.
Every path into the system did a database insert followed by a separate queue publish; if the publish fails after the insert commits, there is an order in the database that nothing knows about, with no exception in the logs and no alert, only a customer who never got a receipt and a row that looks completely fine.
Distinct publishers with included, body-backed reporting in this cluster.
1 article · September 5, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
Three services you can delete: queue, cache and search in one Postgres1 distinct publisher
build
The 680 MB database that was really a 17 GB disk: self-hosted support platforms fail at month six1 distinct publisher
build
Four control planes, one Postgres: a team's case against polyglot persistence1 distinct publisher
build
Edge KV puts the permission check an hour behind the Postgres row1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Checkable mechanics, unverifiable numbers
The SQL is on the page, and a reader can confirm the locking behaviour it depends on without trusting the author at all — that half of the story stands on its own. Everything about this particular team travels the other way: the 1,850 and 1,340 and 910 line counts, the few-thousand-row ready set against a million-row table, the eighteen months it took to see the seam. All of it comes from the engineer who made the change, with no repository, no diff and no dashboard behind it.
One team, told by the person who did it
A single production cutover, disclosed first-hand, with no volume, no duration and no second team reporting the same pattern. The technique has been available in Postgres for a decade, which is a fact about the database rather than evidence of who is using it this way.
Design argument outruns the reporting on it
The subtraction is honest and the durability point is genuinely stronger than the line count: committing the order and its receipt job together removes a class of silent loss rather than mitigating it. The overreach is in the framing of correctness. 'Nothing was wrong with any of them' and 'we deleted the seam' are conclusions a reader is asked to accept from the engineer being vindicated, and a post arguing that a database beats a broker offers not one number about how the database performs under load.
Credit, not commerce
There is no vendor here and nothing for sale: no hosted queue, no license, no consultancy pitch at the end. What the post buys its author is standing for a contrarian call, on a platform where the microservices-were-a-mistake genre performs well, and the engineer grading the decision is the engineer who made it. That biases the framing without giving anyone a reason to invent the SQL.
Confident about the pattern, thin on the case study
Two different levels of certainty are stacked in one post. The queueing mechanism, the lease, the reaper and the single-transaction write are verifiable from the code and from documented Postgres behaviour. The story wrapped around them — three good services, eighteen months, four thousand lines gone with no capability lost — has one witness and no artifacts.