Zizq — Official Ruby Client
Zizq is a simple, zero dependency, single binary job queue system that is both fast and durable. It is designed to work in any stack through a simple HTTP API.
This is the official Zizq client library for Ruby.
Features
- Multi-thread and/or multi-fiber concurrent worker (via
async) Zizq::Jobbased job classes, Active Job support, or completely custom- Enqueue and process jobs from one language to another
- Arbitrary named queues
- Granular job priorities
- Scheduled jobs
- Configurable backoff policies
- Configurable job retention policies
- Recurring jobs (cron)
- Job introspection and management APIs, with support for
jqquery filters - Unique jobs
Installation
[!NOTE] If you have not yet installed the Zizq server, follow the Getting Started guide first.
Add it to your application's Gemfile:
gem 'zizq', '~> 0.3.1'
Or install it manually:
$ gem install zizq -v 0.3.1
Ruby 3.2.8 or newer is required. Client and server share version numbers — keep the client's major/minor at or below the server's.
Configuration
Out of the box, the client talks to a server at http://localhost:7890 —
fine for local development. For anything else, configure it with
Zizq.configure in your application's bootstrap (e.g. a Rails initializer):
require 'zizq'
Zizq.configure do |c|
c.url = 'https://zizq.your.network:7890'
c.tls = { ca: '/path/to/server-ca-cert.pem' }
c.logger = Logger.new('log/zizq.log')
end
For mutual TLS, add client_cert: and client_key: to the tls hash.
[!CAUTION] If your server is exposed directly to the internet, it should require mutual TLS — otherwise anybody can talk to it.
Usage
[!TIP] This README is an overview. The full documentation covers each feature in depth — middleware, custom dispatchers, Active Job, job querying, and more.
Defining a job
In most Ruby applications, a job is a plain class that includes Zizq::Job.
The class declares its defaults with the zizq_* DSL and implements
#perform:
class SendEmailJob
include Zizq::Job
zizq_queue 'emails'
zizq_priority 100
zizq_retry_limit 5
def perform(user_id, template:)
user = User.find(user_id)
Mailer.deliver(user, template)
end
end
Every default — zizq_queue, zizq_priority, zizq_retry_limit,
zizq_backoff, zizq_retention, zizq_unique — can be overridden per
enqueue. The job's class name ("SendEmailJob") becomes the API-level job
type, so keep it stable once jobs are in flight.
Enqueuing jobs
Enqueue a job by passing the class and the arguments your #perform method
expects:
job = Zizq.enqueue(SendEmailJob, 42, template: 'welcome')
job.id # => "03fu0wm75gxgmfyfplwvazhex"
Override defaults for a single call with Zizq.enqueue_with, or with a block
that mutates the request:
# Don't retry this one.
Zizq.enqueue_with(retry_limit: 0).enqueue(SendEmailJob, 42, template: 'welcome')
# Bump the priority via the block form.
Zizq.enqueue(SendEmailJob, 42, template: 'welcome') do |req|
req.priority = 1000
end
Schedule a job for later with delay (seconds from now) or an absolute
ready_at:
Zizq.enqueue_with(delay: 3600).enqueue(SendEmailJob, 42, template: 'welcome')
Zizq.enqueue_with(ready_at: Time.new(2027, 3, 15, 14, 30)).enqueue(SendEmailJob, 42, template: 'welcome')
To enqueue many jobs efficiently, Zizq.enqueue_bulk sends them in a single
atomic request — across queues and job types, and enqueue_raw enqueues can
be mixed in too:
Zizq.enqueue_bulk do |b|
signups.each { |user_id| b.enqueue(SendEmailJob, user_id, template: 'welcome') }
end
[!NOTE] Jobs can also be enqueued without
Zizq::JobviaZizq.enqueue_raw— designed for cross-language workflows where, for example, a Ruby app enqueues jobs consumed by a Go service.
Running a worker
Jobs are processed by a worker, typically in a separate process. The simplest
way is the zizq-worker executable bundled with the gem — pass your
application's entrypoint so it can load your job classes:
$ bundle exec zizq-worker --threads 5 --fibers 2 config/environment.rb
I, [...] INFO -- : Zizq worker starting: 5 threads, 2 fibers, prefetch=20
I, [...] INFO -- : Connected. Listening for jobs.
A worker runs threads × fibers handlers concurrently. Leave --fibers 1
if your application isn't fiber-safe — no Async context is loaded in that
case. Restrict to specific queues with --queue. INT / TERM trigger a
graceful shutdown (drains in-flight jobs up to --shutdown-timeout, default
30s).
For more control — for example running the worker in-process alongside a
Rack app — construct Zizq::Worker directly:
require 'zizq'
worker = Zizq::Worker.new(
thread_count: 5,
fiber_count: 2,
queues: ['emails', 'payments'],
)
Signal.trap('INT') { worker.stop }
worker.run # blocks until the worker stops
#run blocks until the worker terminates; #stop drains in-flight jobs
gracefully, #kill forces an immediate stop. On any unclean shutdown the
server returns unfinished jobs to the queue — no job is lost.
Recurring jobs (cron)
Define a cron schedule in your application's startup code. Definitions are idempotent — every process can safely define the same schedule, and Zizq keeps the server in sync by adding, replacing, and removing entries as the definition changes. Cron requires a Pro license on the server.
Zizq.define_crontab('maintenance', timezone: 'Europe/London') do |cron|
# Every 15 minutes.
cron.define_entry('refresh_warehouse', '*/15 * * * *').enqueue(
RefreshWarehouseJob, incremental: true
)
# 9am London time, every day.
cron.define_entry('daily_digest', '0 9 * * *').enqueue(SendDailyDigestJob)
end
Once defined, schedules can be inspected and managed via
Zizq.crontab('maintenance') — paused/resumed at the schedule level or per
entry, and deleted entirely when no longer needed.
Resources
Support & Feedback
If you need help using Zizq, create an issue on the zizq-ruby repo. Feedback is very welcome.
License
MIT — see LICENSE.