Job Classes
For most Ruby applications the preferred way to write jobs in Zizq will by
including the Zizq::Job module in your Ruby classes. This module adds
instance and class methods to the class, which enable dispatching work to
instances if that class.
Zizq::Job
In Zizq, jobs have a number of required attributes, such as a named queue, a
job type and a payload. A number of optional attributes also exist.
Zizq::Job provides convenience methods to derive and set these attributes,
allowing you to think only in terms of these simple job classes when writing
code for your application.
Defining a Job Class
Any named class can include Zizq::Job. This can be a top-level class, or a
class namespaced within a module hierarchy. Classes including Zizq::Job
must implement a #perform method; the arguments to which are arbitrary.
Caution
You cannot make an anonymous class a
Zizq::Job. The worker would have no way of finding it and instantiating it.
class MyApp::MyJob
include Zizq::Job
def perform(arg1, arg2)
puts "Hello, #{arg1} and #{arg2}"
end
end
We would enqueue an instance of this job by calling Zizq.enqueue with the
class and the job arguments.
Zizq.enqueue(MyApp::MyJob, "Bill", "Ben")
# #<Zizq::Resources::Job @data={"id"=>"03ftfjjuhc59aliu4wadzi06j", ... }>
This job isn’t very interesting but it is a valid Zizq::Job implementation
and allows us to highlight some concepts.
Tip
The examples in this documentation use positional arguments in the
#performmethod. This is perfectly valid but experience shows such methods are more difficult to evolve over time than those that use keyword arguments with optional defaults.
Job Attributes
Zizq will derive the job type from the class name "MyApp::MyJob".
Because the job class does not specify a queue explicitly, jobs of this type
will be placed onto the default queue.
MyApp::MyJob.zizq_queue
# "default"
There are a number of class methods added to the job class—all prefixed with
zizq_—which allow getting or setting the various attributes. These can all be
overridden at enqueue-time too.
Specifying the Queue
Use zizq_queue to set or get the queue. Queue names must be valid UTF-8 and
cannot contain any of the following reserved characters: ,, *, ?, [,
], {, }, \
The default queue is "default".
class MyApp::MyJob
include Zizq::Job
zizq_queue 'example'
end
MyApp::MyJob.zizq_queue
# "example"
Setting the Priority
Use zizq_priority to set or get the priority of the job. Valid values range
between 0 and 65536. The default priority is not specified by the client,
but by the Zizq server (generally 32768).
class MyApp::MyJob
include Zizq::Job
zizq_priority 500
end
MyApp::MyJob.zizq_priority
# 500
Setting the Backoff Policy
Use zizq_retry_limit to set the maximum number of retries before a job is
marked "dead", and zizq_backoff to set the backoff formula parameters. The
defaults are managed by the server (generally the default retry_limit is 25
and the backoff parameters are base: 10, exponent: 4, jitter: 30).
Backoff parameters are in seconds. Floating point values are acceptable. All
three backoff arguments must be provided together.
The values are used in the following formula:
delay = base + (attempts ** exponent) + (rand(0.0..jitter) * attempts)
The randomness in the jitter component is designed to avoid situations where a cascade of failures all retry at the same time. They naturally spread out.
class MyApp::MyJob
include Zizq::Job
zizq_retry_limit 50
zizq_backoff base: 5, exponent: 2, jitter: 10
end
MyApp::MyJob.zizq_retry_limit
# 50
MyApp::MyJob.zizq_backoff
# {:exponent=>2.0, :base=>5.0, :jitter=>10.0}
Setting the Retention Policy
Use zizq_retention to set or get the number of seconds for which dead and
completed jobs are retained by the server before being reaped (hard deleted).
Values are in seconds and floating points are acceptable. Thd defaults are
managed by the server but are generally set at 7 days for dead jobs, and zero
for completed jobs, meaning only dead jobs are kept.
Both arguments are optional.
class MyApp::MyJob
include Zizq::Job
zizq_retention dead: 86_400 * 30, completed: 86_400 * 2
end
MyApp::MyJob.zizq_retention
# {:completed=>172800.0, :dead=>2592000.0}
Specifying Job Uniqueness
Tip
This section of the documentation deals mostly with how to define unique jobs. See Unique Jobs for more detailed documentation on using this feature.
This requires a pro license on the server. Zizq is able to prevent duplicate
enqueues of the same job within a specified job lifecycle scope. Use
zizq_unique enable or disable uniqueness for a job.
The scope: argument specifies for which part of the job’s lifecycle it is
considered unique. Options are:
| Scope | Description |
|---|---|
:queued |
Prevent duplicate enqueues while this job is still in the
"scheduled" or "ready" statuses (i.e.
until a worker takes the job)
|
:active |
Prevent duplicate enqueues while this job is still in the
"scheduled", "ready" or
"in_flight" statuses (i.e. until this job
successfully completes)
|
:exists |
Prevent duplicate enqueues for as long as the Zizq server still has a record of this job (according to its retention policy) |
The default scope is :queued. Zizq does not force you to select an arbitrary
expiry deadline for unique jobs. The implementation is purely lifecycle
based.
class MyApp::MyJob
include Zizq::Job
zizq_unique true, scope: :active
end
MyApp::MyJob.zizq_unique
# true
MyApp::MyJob.zizq_unique_scope
# :active
Unique Keys
Identity is based on the generation of a unique_key for a job, which is
handled through the zizq_unique_key class method. This method takes the same
arguments as the #perform method and returns a string for the unique key.
The default implementation is a function of the job type and its arguments.
MyApp::MyJob.zizq_unique_key("Bill", "Ben", example: 42)
# "MyApp::MyJob:0b9ca7f07581994caa848878576fed30e09e7177611c01aeafe7113921090c29"
MyApp::MyJob.zizq_unique_key("Bill", "Ben", example: 42)
# "MyApp::MyJob:0b9ca7f07581994caa848878576fed30e09e7177611c01aeafe7113921090c29"
MyApp::MyJob.zizq_unique_key("Bill", "Ben", example: 99)
# "MyApp::MyJob:3be19cc482f366dcd538c22b8536d7947672071b8c8fb3a2486ebfd04b2216b6"
If, for example, you need uniqueness only on a subset of the arguments you may override this method in your class:
class MyApp::MyJob
include Zizq::Job
zizq_unique true, scope: :active
def self.zizq_unique_key(arg1, arg2, example:)
super(arg1, arg2)
end
end
MyApp::MyJob.zizq_unique_key("Bill", "Ben", example: 42)
# "MyApp::MyJob:bcd08012e829243d82e953a8140ffb58aeeb839e545ee1547a894bb2c9ba1b8f"
MyApp::MyJob.zizq_unique_key("Bill", "Ben", example: 99)
# "MyApp::MyJob:bcd08012e829243d82e953a8140ffb58aeeb839e545ee1547a894bb2c9ba1b8f"
Dynamic Job Configuration
When the client generates parameters to send to the Zizq server, it does this
by calling zizq_enqueue_request(*args, **kwargs) on your job class, passing
the job arguments. The return value is a Zizq::EnqueueRequest instance,
exactly the same as the one yielded to the caller in Zizq.enqueue.
If you need to do any kind of dynamic configuration in your job classes, such as assigning a different priority based on time of day, or based on some of the arguments, you can override this method.
class MyApp::MyJob
include Zizq::Job
zizq_priority 500
def self.zizq_enqueue_request(arg1, arg2, example:)
req = super
req.priority -= 50 if arg1 == "Bill"
req
end
end
Payload Serialization & Deserialization
When jobs are enqueued, Zizq produces the payload from the JSON serialized
representation of the job arguments. This means by default all
jobs arguments must be JSON serializable, though it is possible to write your
own argument serialization and deserialization implementation.
The default serialization implementation is done through the zizq_serialize
class method, which takes the same input arguments as the #perform method and
returns a JSON-serializable value encoding those arguments (i.e. a Hash).
MyApp::MyJob.zizq_serialize("Bill", "Ben", example: 42)
# {"args"=>["Bill", "Ben"], "kwargs"=>{"example"=>42}}
A corresponding zizq_deserialize exists. This method returns two values:
an args array for the positional arguments and a kwargs hash for any
keyword arguments.
args, kwargs = MyApp::MyJob.zizq_deserialize({"args"=>["Bill", "Ben"], "kwargs"=>{"example"=>42}})
args
# ["Bill", "Ben"]
kwargs
# {:example=>42}
Your classes may override these methods if you need custom serialization logic.
Performing & Error Handling
The Zizq worker will instantiate your job class and call its #perform method
with the same arguments that were used to enqueue the job. There is no maximum
runtime imposed on jobs. They can run for as long as necessary. Provided the
#perform method returns successfully and no errors are raised, Zizq will
acknowledge (mark completed) this job. If any errors are raised from the
#perform method, Zizq will report that error to the Zizq server and either
backoff and retry, or kill the job, depending on the retry limit and the
backoff policy.
If you do not want errors to trigger retries, you have two options:
- Allow the error and configure
zizq_retry_limitto0. - Rescue the error inside the
#performmethod so the Zizq worker does not see it as a failure.
ActiveJob
If your application is a Rails app, you can also use Active Job to manage your
jobs with the :zizq queue adapter. See
Using Active Job for full documentation on this
feature.