zizq 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +82 -11
- data/lib/active_job/queue_adapters/zizq_adapter.rb +4 -23
- data/lib/zizq/bulk_enqueue.rb +61 -14
- data/lib/zizq/client.rb +580 -77
- data/lib/zizq/crontab.rb +36 -3
- data/lib/zizq/crontab_builder.rb +10 -1
- data/lib/zizq/crontab_entry_builder.rb +66 -14
- data/lib/zizq/enqueue_request.rb +22 -21
- data/lib/zizq/enqueue_with.rb +65 -8
- data/lib/zizq/error.rb +10 -0
- data/lib/zizq/job_config.rb +95 -12
- data/lib/zizq/query.rb +111 -39
- data/lib/zizq/resources/budget.rb +109 -0
- data/lib/zizq/resources/cron_group.rb +4 -0
- data/lib/zizq/resources/job.rb +108 -2
- data/lib/zizq/resources/job_template.rb +15 -0
- data/lib/zizq/resources.rb +1 -0
- data/lib/zizq/test/client.rb +6 -3
- data/lib/zizq/version.rb +1 -1
- data/lib/zizq.rb +191 -14
- data/sig/generated/zizq/bulk_enqueue.rbs +4 -16
- data/sig/generated/zizq/client.rbs +377 -8
- data/sig/generated/zizq/crontab.rbs +17 -1
- data/sig/generated/zizq/crontab_builder.rbs +9 -0
- data/sig/generated/zizq/crontab_entry_builder.rbs +4 -17
- data/sig/generated/zizq/enqueue_request.rbs +11 -4
- data/sig/generated/zizq/enqueue_with.rbs +4 -9
- data/sig/generated/zizq/error.rbs +10 -0
- data/sig/generated/zizq/job_config.rbs +65 -0
- data/sig/generated/zizq/query.rbs +79 -2
- data/sig/generated/zizq/resources/budget.rbs +97 -0
- data/sig/generated/zizq/resources/cron_group.rbs +3 -0
- data/sig/generated/zizq/resources/job.rbs +94 -0
- data/sig/generated/zizq/resources/job_template.rbs +11 -0
- data/sig/generated/zizq.rbs +111 -33
- data/sig/zizq.rbs +100 -7
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae77b1f8501b8eae45ac668e2af45a5c69be600173c33d50ebc06d5520a04a05
|
|
4
|
+
data.tar.gz: 72afdb1b19ab3fb3fb34f4dba1e9bb19db41d8125bfbd2f278151482aaa0098f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5b5177e0bf70576bc7632b35bbb9b32f4e85fb3324146b70420b1416ed1d312856eba49be52324c37154af3004f230e6667b3f403f123255af42e6059510367d
|
|
7
|
+
data.tar.gz: d8cbc6f97e3b4bcec66735f9089b3b4e6b201dfedd7e6210fe832a6b8afd718094a1ae1e9b50cb23bc4996f009f4f654c3587d334348431a7648d0628d153b65
|
data/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Zizq — Official Ruby Client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Zizq (**/zɪsk/**) is a fast and durable job queue packed into a single native
|
|
4
|
+
binary, built on an embedded LSM database — not on Redis, and not on your
|
|
5
|
+
RDBMS. It works in any stack, crossing programming language boundaries.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
fast and durable. It is designed to work in any stack through a simple HTTP
|
|
7
|
-
API.
|
|
7
|
+
This is the official Zizq client library for Ruby.
|
|
8
8
|
|
|
9
9
|
[](https://github.com/zizq-labs/zizq-ruby/actions/workflows/ci.yml)
|
|
10
10
|
[](https://rubygems.org/gems/zizq)
|
|
@@ -21,6 +21,8 @@ API.
|
|
|
21
21
|
* Configurable job retention policies
|
|
22
22
|
* Recurring jobs (cron)
|
|
23
23
|
* Job introspection and management APIs, with support for `jq` query filters
|
|
24
|
+
* Concurrency control that doesn't impact other jobs
|
|
25
|
+
* Rate limiting with configurable burst
|
|
24
26
|
* Unique jobs (deduplicated)
|
|
25
27
|
* Batched jobs (folded/merged)
|
|
26
28
|
* Testing helpers
|
|
@@ -34,13 +36,13 @@ API.
|
|
|
34
36
|
Add it to your application's `Gemfile`:
|
|
35
37
|
|
|
36
38
|
```ruby
|
|
37
|
-
gem 'zizq', '~> 0.
|
|
39
|
+
gem 'zizq', '~> 0.7.0'
|
|
38
40
|
```
|
|
39
41
|
|
|
40
42
|
Or install it manually:
|
|
41
43
|
|
|
42
44
|
```shell
|
|
43
|
-
$ gem install zizq -v 0.
|
|
45
|
+
$ gem install zizq -v 0.7.0
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
Ruby **3.2.8 or newer** is required. Client and server share version
|
|
@@ -141,8 +143,8 @@ Zizq.enqueue_with(ready_at: Time.new(2027, 3, 15, 14, 30)).enqueue(SendEmailJob,
|
|
|
141
143
|
```
|
|
142
144
|
|
|
143
145
|
To enqueue many jobs efficiently, `Zizq.enqueue_bulk` sends them in a single
|
|
144
|
-
atomic request — across queues and job types, and
|
|
145
|
-
be mixed in too:
|
|
146
|
+
atomic request — across queues and job types, and job classes vs raw enqueues
|
|
147
|
+
can be mixed in too:
|
|
146
148
|
|
|
147
149
|
```ruby
|
|
148
150
|
Zizq.enqueue_bulk do |b|
|
|
@@ -150,22 +152,91 @@ Zizq.enqueue_bulk do |b|
|
|
|
150
152
|
end
|
|
151
153
|
```
|
|
152
154
|
|
|
153
|
-
Jobs can also be enqueued without `Zizq::Job`
|
|
155
|
+
Jobs can also be enqueued without `Zizq::Job` by providing the named fields —
|
|
154
156
|
designed for lower-level code style, and for cross-language workflows where,
|
|
155
157
|
for example, a Ruby app enqueues jobs consumed by a Go service.
|
|
156
158
|
|
|
157
159
|
```ruby
|
|
158
|
-
Zizq.
|
|
160
|
+
Zizq.enqueue(
|
|
159
161
|
type: "send_email",
|
|
160
162
|
queue: "comms",
|
|
161
163
|
payload: { user_id: 42, template: "welcome" }
|
|
162
164
|
)
|
|
163
165
|
```
|
|
164
166
|
|
|
167
|
+
### Concurrency control
|
|
168
|
+
|
|
169
|
+
The Zizq server supports constraining the number of `in_flight` set of jobs for
|
|
170
|
+
specific job types by binding a named _budget_ to those jobs.
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
# Define a named budget called "notifications" that jobs can reference
|
|
174
|
+
# (generally somewhere in your application startup code).
|
|
175
|
+
Zizq.define_budget(
|
|
176
|
+
"notifications",
|
|
177
|
+
allocation: 10,
|
|
178
|
+
strategy: { type: :while_in_flight }
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Specify the budget on the Job class (or at enqueue time).
|
|
182
|
+
class SendNotificationJob
|
|
183
|
+
include Zizq::Job
|
|
184
|
+
|
|
185
|
+
zizq_budget "notifications"
|
|
186
|
+
|
|
187
|
+
def perform(id)
|
|
188
|
+
# ...
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Those jobs execute no more than 10 at any given time. Other jobs continue to
|
|
193
|
+
# flow normally.
|
|
194
|
+
100.times do |n|
|
|
195
|
+
Zizq.enqueue(SendNotificationJob, n)
|
|
196
|
+
end
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Rate limiting
|
|
200
|
+
|
|
201
|
+
The Zizq server supports dispatching jobs to workers at a throttled rate by
|
|
202
|
+
binding a named _budget_ to those jobs. Jobs that exceed the limit do not
|
|
203
|
+
block other work. The server is smart enough to "park" them until the moment
|
|
204
|
+
they are ready to dispatch.
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
# Define a named budget called "image-service" that jobs can reference
|
|
208
|
+
# (generally somewhere in your application startup code).
|
|
209
|
+
Zizq.define_budget(
|
|
210
|
+
"image-service",
|
|
211
|
+
allocation: 1000,
|
|
212
|
+
strategy: {
|
|
213
|
+
type: :time_based,
|
|
214
|
+
duration: 1.minute # or just 60 without ActiveSupport
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Specify the budget on the Job class (or at enqueue time).
|
|
219
|
+
class NormalizeImageJob
|
|
220
|
+
include Zizq::Job
|
|
221
|
+
|
|
222
|
+
zizq_budget "image-service"
|
|
223
|
+
|
|
224
|
+
def perform(id)
|
|
225
|
+
# ...
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Those jobs are delivered by the server at a rate of 1000/minute without
|
|
230
|
+
# blocking other jobs. Your workers remain free of such knowledge.
|
|
231
|
+
5000.times do |n|
|
|
232
|
+
Zizq.enqueue(NormalizeImageJob, n)
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
165
236
|
### Cross-language and low-level dispatch
|
|
166
237
|
|
|
167
238
|
When a Ruby app needs to *process* jobs enqueued by another language
|
|
168
|
-
(or
|
|
239
|
+
(or itself using raw enqueue form), `Zizq::Router` maps `type` strings to
|
|
169
240
|
handler blocks operating on plain JSON payloads:
|
|
170
241
|
|
|
171
242
|
```ruby
|
|
@@ -90,6 +90,7 @@ module ActiveJob
|
|
|
90
90
|
def build_enqueue_request(job)
|
|
91
91
|
klass = job.class
|
|
92
92
|
|
|
93
|
+
# Prepare with instance-specific config first.
|
|
93
94
|
req =
|
|
94
95
|
Zizq::EnqueueRequest.new(
|
|
95
96
|
queue: job.queue_name,
|
|
@@ -99,31 +100,11 @@ module ActiveJob
|
|
|
99
100
|
ready_at: job.scheduled_at
|
|
100
101
|
)
|
|
101
102
|
|
|
102
|
-
if
|
|
103
|
-
|
|
104
|
-
req
|
|
103
|
+
# Augment with class-level config if the class extends ActiveJobConfig
|
|
104
|
+
if klass.respond_to?(:zizq_apply_class_config)
|
|
105
|
+
klass.zizq_apply_class_config(req, *job.arguments)
|
|
105
106
|
end
|
|
106
107
|
|
|
107
|
-
if klass.respond_to?(:zizq_batched) && klass.zizq_batched
|
|
108
|
-
expr = klass.zizq_batch_expressions
|
|
109
|
-
if expr
|
|
110
|
-
req.batch = {
|
|
111
|
-
key: klass.zizq_batch_key(*job.arguments),
|
|
112
|
-
when: expr[:when],
|
|
113
|
-
fold: expr[:fold]
|
|
114
|
-
}
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
req.retry_limit = klass.zizq_retry_limit if klass.respond_to?(
|
|
119
|
-
:zizq_retry_limit
|
|
120
|
-
) && klass.zizq_retry_limit
|
|
121
|
-
req.backoff = klass.zizq_backoff if klass.respond_to?(:zizq_backoff) &&
|
|
122
|
-
klass.zizq_backoff
|
|
123
|
-
req.retention = klass.zizq_retention if klass.respond_to?(
|
|
124
|
-
:zizq_retention
|
|
125
|
-
) && klass.zizq_retention
|
|
126
|
-
|
|
127
108
|
req
|
|
128
109
|
end
|
|
129
110
|
end
|
data/lib/zizq/bulk_enqueue.rb
CHANGED
|
@@ -27,7 +27,7 @@ module Zizq
|
|
|
27
27
|
# @rbs kwargs: Hash[Symbol, untyped]
|
|
28
28
|
# @rbs &block: ?(EnqueueRequest) -> void
|
|
29
29
|
# @rbs return: void
|
|
30
|
-
def
|
|
30
|
+
def enqueue_job_class(job_class, *args, **kwargs, &block)
|
|
31
31
|
@requests << Zizq.build_enqueue_request(
|
|
32
32
|
job_class,
|
|
33
33
|
*args,
|
|
@@ -36,23 +36,70 @@ module Zizq
|
|
|
36
36
|
)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# Collect an enqueue, by class or by raw inputs.
|
|
40
|
+
#
|
|
41
|
+
# Both forms of `Zizq.enqueue` work here too:
|
|
42
|
+
#
|
|
43
|
+
# b.enqueue(SendEmailJob, 42, template: "welcome")
|
|
44
|
+
# b.enqueue(queue: "emails", type: "send_email", payload: {...})
|
|
45
|
+
#
|
|
46
|
+
# @rbs skip
|
|
47
|
+
# @rbs!
|
|
48
|
+
# def enqueue: (
|
|
49
|
+
# Class & Zizq::JobConfig job_class,
|
|
50
|
+
# *untyped args,
|
|
51
|
+
# **untyped kwargs
|
|
52
|
+
# ) ?{ (EnqueueRequest) -> void } -> void
|
|
53
|
+
# | (
|
|
54
|
+
# queue: String,
|
|
55
|
+
# type: String,
|
|
56
|
+
# payload: untyped,
|
|
57
|
+
# ?priority: Integer?,
|
|
58
|
+
# ?delay: Zizq::to_f?,
|
|
59
|
+
# ?ready_at: Zizq::to_f?,
|
|
60
|
+
# ?retry_limit: Integer?,
|
|
61
|
+
# ?backoff: Zizq::backoff?,
|
|
62
|
+
# ?retention: Zizq::retention?,
|
|
63
|
+
# ?unique_key: String?,
|
|
64
|
+
# ?unique_while: Zizq::unique_scope?,
|
|
65
|
+
# ?batch: Zizq::batch?,
|
|
66
|
+
# ?budgets: Array[Zizq::budget_binding_params]?
|
|
67
|
+
# ) ?{ (EnqueueRequest) -> void } -> void
|
|
68
|
+
def enqueue(*args, **kwargs, &block)
|
|
69
|
+
return enqueue_job_class(*args, **kwargs, &block) unless args.empty?
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
# See `Zizq.enqueue` for why only this branch is wrapped.
|
|
73
|
+
enqueue_raw(**kwargs, &block) # steep:ignore InsufficientKeywordArguments
|
|
74
|
+
rescue ArgumentError => e
|
|
75
|
+
raise ArgumentError, e.message, caller(1)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
39
79
|
# Collect a raw enqueue. Accepts the same arguments as
|
|
40
80
|
# `Zizq.enqueue_raw`.
|
|
41
81
|
#
|
|
42
|
-
# @rbs
|
|
43
|
-
# @rbs
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
82
|
+
# @rbs skip
|
|
83
|
+
# @rbs!
|
|
84
|
+
# def enqueue_raw: (
|
|
85
|
+
# queue: String,
|
|
86
|
+
# type: String,
|
|
87
|
+
# payload: untyped,
|
|
88
|
+
# ?priority: Integer?,
|
|
89
|
+
# ?delay: Zizq::to_f?,
|
|
90
|
+
# ?ready_at: Zizq::to_f?,
|
|
91
|
+
# ?retry_limit: Integer?,
|
|
92
|
+
# ?backoff: Zizq::backoff?,
|
|
93
|
+
# ?retention: Zizq::retention?,
|
|
94
|
+
# ?unique_key: String?,
|
|
95
|
+
# ?unique_while: Zizq::unique_scope?,
|
|
96
|
+
# ?batch: Zizq::batch?,
|
|
97
|
+
# ?budgets: Array[Zizq::budget_binding_params]?
|
|
98
|
+
# ) ?{ (EnqueueRequest) -> void } -> void
|
|
54
99
|
def enqueue_raw(queue:, type:, payload:, **opts)
|
|
55
|
-
|
|
100
|
+
req = EnqueueRequest.new(queue:, type:, payload:, **opts)
|
|
101
|
+
yield req if block_given?
|
|
102
|
+
@requests << req
|
|
56
103
|
end
|
|
57
104
|
|
|
58
105
|
# Build a scoped enqueue helper that applies the given overrides to a
|