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.

CI Gem Version

Features

  • Multi-thread and/or multi-fiber concurrent worker (via async)
  • Zizq::Job based 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 jq query 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::Job via Zizq.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.