Skip to content

How Salieno Core's Job Queue Survives Server Restarts Without Redis

A look at the SQLite-backed queue that powers Salieno Core's background tasks — and why we skipped Redis for a single-binary deployment.

Written by AISali·July 29, 2026·5 min read
How Salieno Core's Job Queue Survives Server Restarts Without Redis

Why a hosting control panel needs a job queue at all#

If you've ever watched a reseller account get provisioned in cPanel/WHM, you know the visible "loading" step is just the tip of the iceberg. Behind the scenes there's DNS zone creation, package assignment, email configuration, billing record generation, and sometimes a dozen other micro-tasks that need to happen in a specific order — and any one of them can fail, timeout, or need to be retried.

Salieno Core faces the same reality. When a customer orders a hosting plan through the billing portal, that single click fans out into a chain of asynchronous work: creating the hosting account on the target server, issuing an SSL certificate, setting up DNS records, generating an invoice, sending a welcome email, and updating the license. Some of those steps are fast. Others depend on external APIs with unpredictable latency.

Running all of that synchronously inside an HTTP request is a recipe for timeouts and frustrated users. So from very early on, Salieno Core needed a durable job queue — something that could accept work, execute it in the background, retry on failure, and survive if the process itself went down.

The obvious answer — and why we didn't take it#

The standard playbook for job queues in modern web applications is Redis, often paired with something like Sidekiq (Ruby), Bull (Node.js), or Laravel's built-in queue driver. Redis is fast, battle-tested, and has excellent pub/sub support. If you're running a SaaS platform with a dedicated ops team, it's a great choice.

But Salieno Core isn't a SaaS. It's self-hosted software that a reseller installs on their own VPS or dedicated server — often a modest 2-core, 4 GB RAM box running alongside cPanel, WHMCS, or other services. Every additional daemon we require is another thing that can crash, consume memory, need security patching, and confuse a non-technical installer.

Adding Redis as a hard dependency would mean:

  • Another ~30–50 MB of resident memory on a box that's already shared with a web server, database, and mail stack.
  • A separate service to monitor, restart, and secure (including configuring authentication and binding to localhost).
  • A potential conflict with existing Redis instances the operator may already be running for other applications.
  • More moving parts in the install script, more things to go wrong during upgrades.

For a single-binary deployment model where simplicity is a core design goal, that trade-off didn't make sense.

What we built instead: a SQLite-backed persistent queue#

Salieno Core's job queue runs on SQLite — the same embedded database that backs the rest of the application's state. Jobs are rows in a jobs table with a status column (pending, running, completed, failed), a payload (JSON), a priority integer, a retry count, and timestamps for scheduling.

A lightweight worker loop polls the table every few seconds, claims the highest-priority pending job using a transaction (so two workers can't grab the same job), executes it, and updates the status. If the job fails, the retry count increments and the job gets rescheduled with exponential backoff. After a configurable number of retries (default: 5), it's marked as permanently failed and flagged for manual review in the admin dashboard.

The key properties we needed:

  • Durability. Jobs survive process restarts because they're on disk, not in memory. If the server reboots mid-provisioning, the queue picks up where it left off.
  • Atomicity. SQLite's write-ahead logging (WAL) mode gives us transactional guarantees without a separate database server. A job is either claimed or it isn't — no half-claimed duplicates.
  • Simplicity. No extra daemons. The queue is just a table in the same .db file that already holds accounts, invoices, and settings. Backups are a single file copy.
  • Concurrency control. A locked_until timestamp prevents a stalled worker from blocking a job forever. If a worker crashes while running a job, the lock expires and another worker (or the same one on restart) reclaims it.

Where this works well — and where it doesn't#

For the workload Salieno Core handles — maybe a few hundred jobs per hour on a busy reseller server, with individual jobs taking anywhere from 200ms (sending an email) to 30 seconds (provisioning an account via API) — SQLite is more than adequate. It handles concurrent reads and sequential writes efficiently, and WAL mode keeps readers from blocking writers.

The design also plays nicely with Salieno Core's single-process architecture. Because everything runs in one binary, there's no need for cross-process coordination. The worker loop is a goroutine (Salieno Core is written in Go), and it communicates with the HTTP handlers through shared in-memory state and the SQLite database.

The honest limitations:

  • High-throughput fan-out. If you needed to enqueue 10,000 jobs per second, SQLite's write throughput would become a bottleneck. That's not a realistic scenario for a reseller hosting panel, but it's worth naming.
  • Distributed workers. SQLite doesn't support network access. If Salieno Core ever needed to distribute work across multiple nodes, the queue would need to graduate to something like PostgreSQL or NATS. Today, the single-node assumption holds.
  • Priority inversion under load. If a long-running job (say, a full server migration) blocks the worker, lower-priority quick jobs can pile up. We mitigate this with a configurable concurrency setting — the worker loop can claim multiple jobs in parallel up to a limit.

Why this matters for operators#

The practical takeaway is that Salieno Core's background processing is invisible in the best way: you don't have to configure it, monitor a separate service, or worry about it after a reboot. Jobs that were in-flight when the server went down resume automatically. Failed jobs surface in the admin UI with clear error messages and a one-click retry.

For resellers running their infrastructure on tight margins and modest hardware, fewer dependencies means fewer 3 AM alerts. The queue just works — and when it doesn't, the failure mode is a row in a database you can inspect with any SQLite client, not a cryptic crash in a daemon you've never looked at.

That's the kind of boring reliability that makes self-hosted software actually usable in production.

Share

0 comments

Loading comments…

More from the blog