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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +82 -11
  3. data/lib/active_job/queue_adapters/zizq_adapter.rb +4 -23
  4. data/lib/zizq/bulk_enqueue.rb +61 -14
  5. data/lib/zizq/client.rb +580 -77
  6. data/lib/zizq/crontab.rb +36 -3
  7. data/lib/zizq/crontab_builder.rb +10 -1
  8. data/lib/zizq/crontab_entry_builder.rb +66 -14
  9. data/lib/zizq/enqueue_request.rb +22 -21
  10. data/lib/zizq/enqueue_with.rb +65 -8
  11. data/lib/zizq/error.rb +10 -0
  12. data/lib/zizq/job_config.rb +95 -12
  13. data/lib/zizq/query.rb +111 -39
  14. data/lib/zizq/resources/budget.rb +109 -0
  15. data/lib/zizq/resources/cron_group.rb +4 -0
  16. data/lib/zizq/resources/job.rb +108 -2
  17. data/lib/zizq/resources/job_template.rb +15 -0
  18. data/lib/zizq/resources.rb +1 -0
  19. data/lib/zizq/test/client.rb +6 -3
  20. data/lib/zizq/version.rb +1 -1
  21. data/lib/zizq.rb +191 -14
  22. data/sig/generated/zizq/bulk_enqueue.rbs +4 -16
  23. data/sig/generated/zizq/client.rbs +377 -8
  24. data/sig/generated/zizq/crontab.rbs +17 -1
  25. data/sig/generated/zizq/crontab_builder.rbs +9 -0
  26. data/sig/generated/zizq/crontab_entry_builder.rbs +4 -17
  27. data/sig/generated/zizq/enqueue_request.rbs +11 -4
  28. data/sig/generated/zizq/enqueue_with.rbs +4 -9
  29. data/sig/generated/zizq/error.rbs +10 -0
  30. data/sig/generated/zizq/job_config.rbs +65 -0
  31. data/sig/generated/zizq/query.rbs +79 -2
  32. data/sig/generated/zizq/resources/budget.rbs +97 -0
  33. data/sig/generated/zizq/resources/cron_group.rbs +3 -0
  34. data/sig/generated/zizq/resources/job.rbs +94 -0
  35. data/sig/generated/zizq/resources/job_template.rbs +11 -0
  36. data/sig/generated/zizq.rbs +111 -33
  37. data/sig/zizq.rbs +100 -7
  38. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 650d5af8c85dfe12fdf6280a3cf483ba1edd7f7d3d0536965a220c9f556221ef
4
- data.tar.gz: ec340ce8d30065b6afeb9dc9404651bd769e8a604dec326aca97c332a11bc7ba
3
+ metadata.gz: ae77b1f8501b8eae45ac668e2af45a5c69be600173c33d50ebc06d5520a04a05
4
+ data.tar.gz: 72afdb1b19ab3fb3fb34f4dba1e9bb19db41d8125bfbd2f278151482aaa0098f
5
5
  SHA512:
6
- metadata.gz: a7a239bed97e29977c9863f7e56d1ef94b1a1c1cd05bd4b74dde441910bd83f480b4d0453e58b32a3d623ad59bac8b771da5397794e1a05e614e4654cb744606
7
- data.tar.gz: c60eae8cf1142ecb8e04218d551b5595554ebf5a085076111051c4a658518608715aae729d968d98f33aa7bd3f0f2a181fecbf97a031d6c21bb6a2472d8b291c
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
- This is the official Zizq client library for Ruby.
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
- Zizq is a simple, zero dependency, single binary job queue system that is both
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
  [![CI](https://github.com/zizq-labs/zizq-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/zizq-labs/zizq-ruby/actions/workflows/ci.yml)
10
10
  [![Gem Version](https://img.shields.io/gem/v/zizq.svg)](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.6.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.6.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 `enqueue_raw` enqueues can
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` via `Zizq.enqueue_raw`
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.enqueue_raw(
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 by `Zizq.enqueue_raw`), `Zizq::Router` maps `type` strings to
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 klass.respond_to?(:zizq_unique) && klass.zizq_unique
103
- req.unique_key = klass.zizq_unique_key(*job.arguments)
104
- req.unique_while = klass.zizq_unique_scope
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
@@ -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 enqueue(job_class, *args, **kwargs, &block)
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 queue: String
43
- # @rbs type: String
44
- # @rbs payload: untyped
45
- # @rbs priority: Integer?
46
- # @rbs ready_at: Zizq::to_f?
47
- # @rbs retry_limit: Integer?
48
- # @rbs backoff: Zizq::backoff?
49
- # @rbs retention: Zizq::retention?
50
- # @rbs unique_key: String?
51
- # @rbs unique_while: Zizq::unique_scope?
52
- # @rbs batch: Zizq::batch?
53
- # @rbs return: void
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
- @requests << EnqueueRequest.new(queue:, type:, payload:, **opts)
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