spoolctl and huey

Research date: 2026-09-21 spoolctl source baseline: main, commit f95600b huey source baseline: 3.4.0, commit ffa956f

Conclusion

huey and spoolctl can both keep a queue in one SQLite file. They solve different problems.

Use huey when the work is Python functions inside an application. It has retries with backoff, priorities, delays, periodic tasks, pipelines, locks, and result storage. spoolctl has no periodic tasks, pipelines, or result store.

Use spoolctl when the work is shell commands, or when a job must run again after its runner is killed. huey removes a task from storage when a consumer takes it. Its documentation says a task interrupted by SIGKILL is lost. spoolctl keeps the job row until the job ends, and reclaims it from a dead worker.

Scope and architecture

huey has three parts: the application that enqueues tasks, a storage backend, and a consumer process that runs tasks with thread, process, or greenlet workers. Backends include Redis, SQLite, files, and memory.

In the SQLite backend, dequeue selects the next task and deletes its row in one transaction. See the pinned SqliteStorage.dequeue. From that moment the task exists only in the consumer’s memory. The consumer docs state: “Huey does not guarantee at-least-once delivery of messages.” A graceful shutdown lets running tasks finish. A SIGKILL or power loss does not. huey emits SIGNAL_INTERRUPTED on an interrupted shutdown, and its docs give a recipe to re-enqueue from that signal.

spoolctl keeps the job row and marks it claimed. A worker writes a heartbeat while the job runs. If the heartbeat goes stale, another worker checks that the owner process is gone, then runs the job again. See Guarantees.

Capability comparison

Area huey 3.4.0 spoolctl at f95600b
Unit of work A Python function in your codebase. A shell command.
Storage Redis, SQLite, file, or memory. SQLite only.
Running task after runner SIGKILL Lost. Row was deleted at dequeue. Reclaimed and run again after the owner is confirmed dead.
Delivery Not at-least-once, per its docs. At-least-once.
Retry retries, retry_delay, retry_backoff multiplier. Exponential backoff, capped at 60 s, then dead.
Failed task record Exception stored as the task result, if a result store is on. failed and dead states, attempt history, error codes.
Timeout Hard with process and greenlet workers; cooperative with threads. Hard. Default 300 s. Kills the process group.
Priorities Yes. Yes.
Delayed start Yes, eta or delay. Yes: --after, --at.
Periodic tasks Yes, crontab style. No.
Pipelines, locks Yes. No.
Submit from a shell Needs Python code. spoolctl add.
Install Python package. Python 3 standard library only.
Platforms Python; no process workers on Windows. macOS, Linux.

What huey does well

  • It is small for a full task queue, and its SQLite backend needs no server.
  • Retries, backoff, priorities, delays, and periodic tasks are all built in.
  • Its docs are honest about delivery. They name the SIGKILL case and give a recovery recipe.
  • The docs recommend idempotent tasks. That advice applies to spoolctl too.

The pinned task API docs document these options.

What spoolctl does differently

huey trusts its consumer. spoolctl assumes the runner will die at the worst time. That is the difference between delete-on-dequeue and a claimed row with a heartbeat.

spoolctl runs any command. A job can be a Python script, a shell pipeline, or a compiled binary. No application code must import spoolctl.

spoolctl kills the whole process group on timeout. A task that spawned children does not leave them running.

Composition

A huey task can call spoolctl add to hand a shell command to a durable queue. A spoolctl job can run a script that enqueues huey tasks. Each tool then owns its own retries.

Limits and claims not to make

  • Do not say huey has no retry or no backoff. It has both.
  • Do not say huey has no timeout. It has one; with threads it is cooperative.
  • Do not say every huey setup loses tasks on any crash. Queued tasks stay in storage. The loss is for a task that was running.
  • Do not claim spoolctl has periodic tasks. Use cron or systemd timers to call spoolctl add.

Sources