Module: Wurk::API::Queues
- Defined in:
- lib/wurk/api/queues.rb
Overview
The observe plane: how deep the queues are, what is waiting to retry, what died, and the counters over the lot.
Every route reads through a canonical inspector — Stats,
Queue, RetrySet, ScheduledSet, DeadSet,
Batch::Status — the same objects the dashboard reads. Not to save
code: a second reader over the same Redis keys is a second place for the
size-descending queue order, the latency math, the paused set and the
ActiveJob unwrapping to drift, and a dashboard and an API that disagree
about how deep a queue is are worse than either on its own.
Pause and unpause are the only writes, and they go through
Queue#pause! / Queue#unpause!, which expire this
process's fetcher cache of the paused set. A bare SADD here would leave
the swarm fetching from a queue the API had just reported paused.
Constant Summary collapse
- TABLE =
Verb, pattern, the scope a caller must hold, handler. A table rather than nine
router.getcalls because the scope column is the part that has to be read at a glance: the two writes take :admin, not :read. Pausingdefaultstops the fleet from working without enqueueing or deleting anything, so it belongs with the destructive routes rather than with the listings it sits beside. [ [:get, '/stats', :read, :stats], [:get, '/queues', :read, :index], [:get, '/queues/:name', :read, :show], [:get, '/retries', :read, :retries], [:get, '/scheduled', :read, :scheduled], [:get, '/dead', :read, :dead], [:get, '/batches/:bid', :read, :batch], [:post, '/queues/:name/pause', :admin, :pause], [:post, '/queues/:name/unpause', :admin, :unpause] ].freeze
Class Method Summary collapse
- .batch(request) ⇒ Object
- .batch_not_found(request, bid) ⇒ Object
- .dead(request) ⇒ Object
-
.draw(router) ⇒ Object
The refusal rendering wraps every handler here rather than sitting in each one: they all validate something that came off the network, and a rescue repeated per route is one a new route gets written without.
-
.index(_request) ⇒ Object
Stats#queue_summaries rather than Queue.all: one pipeline instead of an LLEN round trip per queue, and it comes back in the size-descending order every other Wurk surface lists queues in.
- .pause(request) ⇒ Object
- .retries(request) ⇒ Object
- .scheduled(request) ⇒ Object
-
.show(request) ⇒ Object
No 404 for a name nothing was ever enqueued under.
-
.sorted_set(request, set) ⇒ Object
totalis read before the page is walked, so it describes the set the page was taken from rather than the one left after a poller drained it. - .stats(_request) ⇒ Object
-
.toggle(request, paused:) ⇒ Object
Idempotent both ways (SADD/SREM), and the resulting state comes back so a client does not need a second request to confirm the toggle.
- .unpause(request) ⇒ Object
Class Method Details
.batch(request) ⇒ Object
107 108 109 110 111 112 113 |
# File 'lib/wurk/api/queues.rb', line 107 def batch(request) bid = Validation.bid!(request.path_params[:bid]) status = ::Wurk::Batch::Status.new(bid) return batch_not_found(request, bid) unless status.exists? Response.json(200, status.data) end |
.batch_not_found(request, bid) ⇒ Object
115 116 117 118 119 120 121 122 123 |
# File 'lib/wurk/api/queues.rb', line 115 def batch_not_found(request, bid) Problem.render( Problem::BATCH_NOT_FOUND, status: 404, detail: "No batch has bid #{bid}; it was never created, or it has expired.", instance: request.path, bid: bid ) end |
.dead(request) ⇒ Object
85 |
# File 'lib/wurk/api/queues.rb', line 85 def dead(request) = sorted_set(request, ::Wurk::DeadSet.new) |
.draw(router) ⇒ Object
The refusal rendering wraps every handler here rather than sitting in each one: they all validate something that came off the network, and a rescue repeated per route is one a new route gets written without.
50 51 52 53 54 55 56 57 58 |
# File 'lib/wurk/api/queues.rb', line 50 def draw(router) TABLE.each do |verb, pattern, scope, handler| router.public_send(verb, pattern, scope: scope) do |request| public_send(handler, request) rescue Validation::Invalid => e Problem.from(e, instance: request.path) end end end |
.index(_request) ⇒ Object
Stats#queue_summaries rather than Queue.all: one pipeline instead of an LLEN round trip per queue, and it comes back in the size-descending order every other Wurk surface lists queues in.
67 68 69 |
# File 'lib/wurk/api/queues.rb', line 67 def index(_request) Response.json(200, queues: ::Wurk::Stats.new.queue_summaries.map { |q| Serializers.queue_summary(q) }) end |
.pause(request) ⇒ Object
87 |
# File 'lib/wurk/api/queues.rb', line 87 def pause(request) = toggle(request, paused: true) |
.retries(request) ⇒ Object
83 |
# File 'lib/wurk/api/queues.rb', line 83 def retries(request) = sorted_set(request, ::Wurk::RetrySet.new) |
.scheduled(request) ⇒ Object
84 |
# File 'lib/wurk/api/queues.rb', line 84 def scheduled(request) = sorted_set(request, ::Wurk::ScheduledSet.new) |
.show(request) ⇒ Object
No 404 for a name nothing was ever enqueued under. A queue is not an
entity in the Sidekiq schema — it is a LIST that exists only while it
has members — so "never used" and "drained" are the same state in
Redis, and size: 0 is the honest reading of both. Pausing one before
its first job is a legitimate pre-deploy move for the same reason.
76 77 78 79 80 81 |
# File 'lib/wurk/api/queues.rb', line 76 def show(request) window = Page.window!(request) queue = ::Wurk::Queue.new(Validation.queue_name!(request.path_params[:name])) jobs = Page.slice(queue, window) { |record| Serializers.job_record(record) } Response.json(200, Serializers.queue_gauges(queue).merge(page: window.page, count: window.count, jobs: jobs)) end |
.sorted_set(request, set) ⇒ Object
total is read before the page is walked, so it describes the set the
page was taken from rather than the one left after a poller drained it.
100 101 102 103 104 105 |
# File 'lib/wurk/api/queues.rb', line 100 def sorted_set(request, set) window = Page.window!(request) total = set.size jobs = Page.slice(set, window) { |entry| Serializers.sorted_entry(entry) } Response.json(200, name: set.name, total: total, page: window.page, count: window.count, jobs: jobs) end |
.stats(_request) ⇒ Object
60 61 62 |
# File 'lib/wurk/api/queues.rb', line 60 def stats(_request) Response.json(200, Serializers.stats(::Wurk::Stats.new)) end |
.toggle(request, paused:) ⇒ Object
Idempotent both ways (SADD/SREM), and the resulting state comes back so a client does not need a second request to confirm the toggle.
92 93 94 95 96 |
# File 'lib/wurk/api/queues.rb', line 92 def toggle(request, paused:) queue = ::Wurk::Queue.new(Validation.queue_name!(request.path_params[:name])) paused ? queue.pause! : queue.unpause! Response.json(200, name: queue.name, paused: paused) end |
.unpause(request) ⇒ Object
88 |
# File 'lib/wurk/api/queues.rb', line 88 def unpause(request) = toggle(request, paused: false) |