pgbus 0.8.4 → 0.9.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.
@@ -1,6 +1,35 @@
1
1
  ---
2
2
  nb:
3
3
  pgbus:
4
+ batches:
5
+ index:
6
+ description: Jobbgrupper med fremdriftssporing og tilbakekall
7
+ empty: Ingen grupper funnet
8
+ headers:
9
+ batch_id: Gruppe-ID
10
+ created: Opprettet
11
+ description: Beskrivelse
12
+ jobs: Jobber
13
+ progress: Fremdrift
14
+ status: Status
15
+ title: Grupper
16
+ show:
17
+ back: Tilbake til grupper
18
+ batch_id: Gruppe-ID
19
+ completed: Fullført
20
+ created_at: Opprettet den
21
+ details: Detaljer
22
+ discarded: Forkastet
23
+ finished_at: Avsluttet den
24
+ not_found: Gruppe ikke funnet
25
+ on_discard: Ved forkastning
26
+ on_finish: Ved avslutning
27
+ on_success: Ved suksess
28
+ progress: Fremdrift
29
+ properties: Egenskaper
30
+ remaining: Gjenstående
31
+ title: Gruppe
32
+ total_jobs: Totalt antall jobber
4
33
  dashboard:
5
34
  processes_table:
6
35
  empty: Ingen prosesser kjører
@@ -189,6 +218,10 @@ nb:
189
218
  not_found: Hendelse ikke funnet
190
219
  title: Hendelse %{event_id}
191
220
  helpers:
221
+ batch_status:
222
+ finished: Fullført
223
+ pending: Avventer
224
+ processing: Behandler
192
225
  bulk_select_all: Velg alle
193
226
  bulk_select_row: Velg %{id}
194
227
  bulk_selected: valgt
@@ -346,6 +379,7 @@ nb:
346
379
  layout:
347
380
  brand: Pgbus
348
381
  nav:
382
+ batches: Grupper
349
383
  dashboard: Dashbord
350
384
  dlq: DLQ
351
385
  events: Hendelser
@@ -1,6 +1,35 @@
1
1
  ---
2
2
  nl:
3
3
  pgbus:
4
+ batches:
5
+ index:
6
+ description: Taakgroepen met voortgangsregistratie en callbacks
7
+ empty: Geen groepen gevonden
8
+ headers:
9
+ batch_id: Groep-ID
10
+ created: Aangemaakt
11
+ description: Beschrijving
12
+ jobs: Taken
13
+ progress: Voortgang
14
+ status: Status
15
+ title: Groepen
16
+ show:
17
+ back: Terug naar groepen
18
+ batch_id: Groep-ID
19
+ completed: Voltooid
20
+ created_at: Aangemaakt op
21
+ details: Details
22
+ discarded: Verworpen
23
+ finished_at: Voltooid op
24
+ not_found: Groep niet gevonden
25
+ on_discard: Bij verwerping
26
+ on_finish: Bij voltooiing
27
+ on_success: Bij succes
28
+ progress: Voortgang
29
+ properties: Eigenschappen
30
+ remaining: Resterend
31
+ title: Groep
32
+ total_jobs: Totaal taken
4
33
  dashboard:
5
34
  processes_table:
6
35
  empty: Geen processen actief
@@ -189,6 +218,10 @@ nl:
189
218
  not_found: Gebeurtenis niet gevonden
190
219
  title: Gebeurtenis %{event_id}
191
220
  helpers:
221
+ batch_status:
222
+ finished: Voltooid
223
+ pending: In afwachting
224
+ processing: Bezig
192
225
  bulk_select_all: Alles selecteren
193
226
  bulk_select_row: Selecteer %{id}
194
227
  bulk_selected: geselecteerd
@@ -346,6 +379,7 @@ nl:
346
379
  layout:
347
380
  brand: Pgbus
348
381
  nav:
382
+ batches: Groepen
349
383
  dashboard: Dashboard
350
384
  dlq: DLQ
351
385
  events: Evenementen
@@ -1,6 +1,35 @@
1
1
  ---
2
2
  pt:
3
3
  pgbus:
4
+ batches:
5
+ index:
6
+ description: Lotes de trabalhos com acompanhamento de progresso e callbacks
7
+ empty: Nenhum lote encontrado
8
+ headers:
9
+ batch_id: ID do lote
10
+ created: Criado
11
+ description: Descrição
12
+ jobs: Trabalhos
13
+ progress: Progresso
14
+ status: Status
15
+ title: Lotes
16
+ show:
17
+ back: Voltar para lotes
18
+ batch_id: ID do lote
19
+ completed: Concluídos
20
+ created_at: Criado em
21
+ details: Detalhes
22
+ discarded: Descartados
23
+ finished_at: Finalizado em
24
+ not_found: Lote não encontrado
25
+ on_discard: Ao descartar
26
+ on_finish: Ao finalizar
27
+ on_success: Ao ter sucesso
28
+ progress: Progresso
29
+ properties: Propriedades
30
+ remaining: Restantes
31
+ title: Lote
32
+ total_jobs: Total de trabalhos
4
33
  dashboard:
5
34
  processes_table:
6
35
  empty: Nenhum processo em execução
@@ -189,6 +218,10 @@ pt:
189
218
  not_found: Evento não encontrado
190
219
  title: Evento %{event_id}
191
220
  helpers:
221
+ batch_status:
222
+ finished: Finalizado
223
+ pending: Pendente
224
+ processing: Processando
192
225
  bulk_select_all: Selecionar todos
193
226
  bulk_select_row: Selecionar %{id}
194
227
  bulk_selected: selecionado
@@ -346,6 +379,7 @@ pt:
346
379
  layout:
347
380
  brand: Pgbus
348
381
  nav:
382
+ batches: Lotes
349
383
  dashboard: Painel
350
384
  dlq: DLQ
351
385
  events: Eventos
@@ -1,6 +1,35 @@
1
1
  ---
2
2
  sv:
3
3
  pgbus:
4
+ batches:
5
+ index:
6
+ description: Jobbgrupper med framstegsspårning och callbacks
7
+ empty: Inga grupper hittades
8
+ headers:
9
+ batch_id: Grupp-ID
10
+ created: Skapad
11
+ description: Beskrivning
12
+ jobs: Jobb
13
+ progress: Framsteg
14
+ status: Status
15
+ title: Grupper
16
+ show:
17
+ back: Tillbaka till grupper
18
+ batch_id: Grupp-ID
19
+ completed: Slutförda
20
+ created_at: Skapad den
21
+ details: Detaljer
22
+ discarded: Kasserade
23
+ finished_at: Avslutad den
24
+ not_found: Grupp hittades inte
25
+ on_discard: Vid kassering
26
+ on_finish: Vid avslut
27
+ on_success: Vid framgång
28
+ progress: Framsteg
29
+ properties: Egenskaper
30
+ remaining: Återstående
31
+ title: Grupp
32
+ total_jobs: Totalt antal jobb
4
33
  dashboard:
5
34
  processes_table:
6
35
  empty: Inga processer körs
@@ -189,6 +218,10 @@ sv:
189
218
  not_found: Händelse hittades inte
190
219
  title: Händelse %{event_id}
191
220
  helpers:
221
+ batch_status:
222
+ finished: Slutförd
223
+ pending: Väntande
224
+ processing: Bearbetas
192
225
  bulk_select_all: Markera alla
193
226
  bulk_select_row: Markera %{id}
194
227
  bulk_selected: valda
@@ -346,6 +379,7 @@ sv:
346
379
  layout:
347
380
  brand: Pgbus
348
381
  nav:
382
+ batches: Grupper
349
383
  dashboard: Instrumentpanel
350
384
  dlq: DLQ
351
385
  events: Händelser
data/config/routes.rb CHANGED
@@ -45,6 +45,7 @@ Pgbus::Engine.routes.draw do
45
45
  end
46
46
  end
47
47
 
48
+ resources :batches, only: %i[index show]
48
49
  resources :processes, only: [:index]
49
50
 
50
51
  resources :events, only: %i[index show] do
data/lib/pgbus/client.rb CHANGED
@@ -351,6 +351,87 @@ module Pgbus
351
351
  total
352
352
  end
353
353
 
354
+ # --- Grouped reads (PGMQ v1.11.0+) ---
355
+
356
+ def read_grouped(queue_name, qty:, vt: nil)
357
+ full_name = config.queue_name(queue_name)
358
+ Instrumentation.instrument("pgbus.client.read_grouped", queue: full_name, qty: qty) do
359
+ with_stale_connection_retry do
360
+ synchronized { @pgmq.read_grouped(full_name, vt: vt || config.visibility_timeout, qty: qty) }
361
+ end
362
+ end
363
+ end
364
+
365
+ def read_grouped_rr(queue_name, qty:, vt: nil)
366
+ full_name = config.queue_name(queue_name)
367
+ Instrumentation.instrument("pgbus.client.read_grouped_rr", queue: full_name, qty: qty) do
368
+ with_stale_connection_retry do
369
+ synchronized { @pgmq.read_grouped_rr(full_name, vt: vt || config.visibility_timeout, qty: qty) }
370
+ end
371
+ end
372
+ end
373
+
374
+ def read_grouped_head(queue_name, qty:, vt: nil)
375
+ full_name = config.queue_name(queue_name)
376
+ with_stale_connection_retry do
377
+ synchronized { @pgmq.read_grouped_head(full_name, vt: vt || config.visibility_timeout, qty: qty) }
378
+ end
379
+ end
380
+
381
+ # --- FIFO index management (PGMQ v1.11.0+) ---
382
+
383
+ def create_fifo_index(queue_name)
384
+ full_name = config.queue_name(queue_name)
385
+ with_stale_connection_retry do
386
+ synchronized { @pgmq.create_fifo_index(full_name) }
387
+ end
388
+ end
389
+
390
+ def create_fifo_indexes_all
391
+ with_stale_connection_retry do
392
+ synchronized { @pgmq.create_fifo_indexes_all }
393
+ end
394
+ end
395
+
396
+ # --- LISTEN/NOTIFY management (PGMQ v1.11.0+) ---
397
+
398
+ def wait_for_notify(queue_name, timeout: nil, &block)
399
+ full_name = config.queue_name(queue_name)
400
+ with_stale_connection_retry do
401
+ synchronized { @pgmq.wait_for_notify(full_name, timeout: timeout, &block) }
402
+ end
403
+ end
404
+
405
+ def update_notify_insert(queue_name, throttle_interval_ms:)
406
+ full_name = config.queue_name(queue_name)
407
+ with_stale_connection_retry do
408
+ synchronized { @pgmq.update_notify_insert(full_name, throttle_interval_ms: throttle_interval_ms) }
409
+ end
410
+ end
411
+
412
+ def list_notify_insert_throttles
413
+ with_stale_connection_retry do
414
+ synchronized { @pgmq.list_notify_insert_throttles }
415
+ end
416
+ end
417
+
418
+ # --- Archive partitioning (requires pg_partman extension) ---
419
+
420
+ def convert_archive_partitioned(queue_name, partition_interval: "10000", retention_interval: "100000",
421
+ leading_partition: 10)
422
+ full_name = config.queue_name(queue_name)
423
+ with_stale_connection_retry do
424
+ synchronized do
425
+ @pgmq.convert_archive_partitioned(
426
+ full_name,
427
+ partition_interval: partition_interval,
428
+ retention_interval: retention_interval,
429
+ leading_partition: leading_partition
430
+ )
431
+ end
432
+ end
433
+ end
434
+
354
435
  # Topic routing
355
436
  def bind_topic(pattern, queue_name)
356
437
  full_name = config.queue_name(queue_name)
@@ -503,6 +584,7 @@ module Pgbus
503
584
  @pgmq.create(full_name)
504
585
  tune_autovacuum(full_name)
505
586
  enable_notify_if_needed(full_name, NOTIFY_THROTTLE_MS)
587
+ create_fifo_index_if_needed(full_name)
506
588
  end
507
589
  true
508
590
  end
@@ -515,6 +597,12 @@ module Pgbus
515
597
  @pgmq.enable_notify_insert(full_name, throttle_interval_ms: throttle_ms)
516
598
  end
517
599
 
600
+ def create_fifo_index_if_needed(full_name)
601
+ return unless config.group_mode
602
+
603
+ @pgmq.create_fifo_index(full_name)
604
+ end
605
+
518
606
  # Check whether the NOTIFY trigger already exists on this queue with the
519
607
  # expected throttle interval. When it does, we can skip the destructive
520
608
  # DROP TRIGGER + CREATE TRIGGER cycle that causes deadlocks when multiple
@@ -544,11 +632,21 @@ module Pgbus
544
632
  false
545
633
  end
546
634
 
635
+ # Apply PGMQ-tuned autovacuum + storage parameters to a queue's tables.
636
+ #
637
+ # Delegates to pgmq-ruby's tune_autovacuum (v0.7+), which sets the same
638
+ # queue/archive parameters pgbus used to apply by hand — vacuum scale
639
+ # factor 0.01/0.05, cost_delay 2/5, analyze scale factor 0.05, and
640
+ # fillfactor 70 on the queue table — plus a vacuum_threshold floor of 50.
641
+ # It quotes/lowercases the table name and runs both ALTER TABLEs in one
642
+ # pooled checkout. Tuning is best-effort: a failure here never blocks a
643
+ # queue from being usable, so we log and move on.
644
+ #
645
+ # Pgbus::AutovacuumTuning is still the source for the migration generators
646
+ # (sql_for_all_queues, sql_for_high_churn_tables) which tune pgbus-owned
647
+ # metadata tables the gem doesn't know about.
547
648
  def tune_autovacuum(queue_name)
548
- with_raw_connection do |conn|
549
- conn.exec(AutovacuumTuning.sql_for_queue(queue_name))
550
- conn.exec(TableMaintenance.fillfactor_sql_for_queue(queue_name))
551
- end
649
+ @pgmq.tune_autovacuum(queue_name)
552
650
  rescue StandardError => e
553
651
  Pgbus.logger.debug { "[Pgbus::Client] Autovacuum tuning failed for #{queue_name}: #{e.message}" }
554
652
  end
@@ -44,6 +44,12 @@ module Pgbus
44
44
  # Priority queues
45
45
  attr_accessor :priority_levels, :default_priority
46
46
 
47
+ # Grouped reads (PGMQ v1.11.0+ FIFO grouping).
48
+ # nil = disabled (default read_batch behavior).
49
+ # :fifo = use read_grouped (drains oldest group first, throughput-optimized).
50
+ # :round_robin = use read_grouped_rr (fair round-robin across groups).
51
+ attr_reader :group_mode
52
+
47
53
  # Archive compaction. Only the user-facing retention window is configurable;
48
54
  # the loop interval and batch size are tuned via constants on
49
55
  # Pgbus::Process::Dispatcher.
@@ -146,6 +152,7 @@ module Pgbus
146
152
 
147
153
  @priority_levels = nil
148
154
  @default_priority = 1
155
+ @group_mode = nil
149
156
 
150
157
  @archive_retention = 7 * 24 * 3600 # 7 days
151
158
 
@@ -300,6 +307,24 @@ module Pgbus
300
307
  @streams_default_broadcast_mode = mode
301
308
  end
302
309
 
310
+ VALID_GROUP_MODES = [nil, :fifo, :round_robin].freeze
311
+
312
+ def group_mode=(mode)
313
+ coerced = case mode
314
+ when nil then nil
315
+ when Symbol then mode
316
+ when String then mode.to_sym
317
+ else
318
+ raise ArgumentError,
319
+ "Invalid group_mode type: #{mode.class}. Must be nil, String, or Symbol"
320
+ end
321
+ unless VALID_GROUP_MODES.include?(coerced)
322
+ raise ArgumentError, "Invalid group_mode: #{coerced.inspect}. Must be nil, :fifo, or :round_robin"
323
+ end
324
+
325
+ @group_mode = coerced
326
+ end
327
+
303
328
  VALID_PGMQ_SCHEMA_MODES = %i[auto extension embedded].freeze
304
329
 
305
330
  def pgmq_schema_mode=(mode)
@@ -69,6 +69,7 @@ module Pgbus
69
69
  single_active = worker_config[:single_active_consumer] || worker_config["single_active_consumer"] || false
70
70
  priority = worker_config[:consumer_priority] || worker_config["consumer_priority"] || 0
71
71
  exec_mode = config.execution_mode_for(worker_config)
72
+ grp_mode = worker_config[:group_mode] || worker_config["group_mode"] || config.group_mode
72
73
 
73
74
  pid = fork do
74
75
  restore_signals
@@ -78,7 +79,7 @@ module Pgbus
78
79
  worker = Worker.new(
79
80
  queues: queues, threads: threads, config: config,
80
81
  single_active_consumer: single_active, consumer_priority: priority,
81
- execution_mode: exec_mode
82
+ execution_mode: exec_mode, group_mode: grp_mode
82
83
  )
83
84
  worker.run
84
85
  end
@@ -11,12 +11,24 @@ module Pgbus
11
11
 
12
12
  def initialize(queues:, threads: 5, config: Pgbus.configuration,
13
13
  single_active_consumer: false, consumer_priority: 0,
14
- execution_mode: :threads)
14
+ execution_mode: :threads, group_mode: nil)
15
15
  @queues = Array(queues)
16
16
  @wildcard = @queues.include?("*")
17
17
  @threads = threads
18
18
  @config = config
19
19
  @execution_mode = ExecutionPools.normalize_mode(execution_mode)
20
+ @group_mode = case group_mode
21
+ when nil then nil
22
+ when Symbol then group_mode
23
+ when String then group_mode.to_sym
24
+ else
25
+ raise ArgumentError,
26
+ "Invalid group_mode type: #{group_mode.class}. Must be nil, String, or Symbol"
27
+ end
28
+ unless Pgbus::Configuration::VALID_GROUP_MODES.include?(@group_mode)
29
+ raise ArgumentError,
30
+ "Invalid group_mode: #{@group_mode.inspect}. Must be nil, :fifo, or :round_robin"
31
+ end
20
32
  @single_active_consumer = single_active_consumer
21
33
  @consumer_priority = consumer_priority
22
34
  @lifecycle = Lifecycle.new
@@ -141,6 +153,8 @@ module Pgbus
141
153
 
142
154
  if priority_enabled?
143
155
  fetch_prioritized(active_queues, qty)
156
+ elsif @group_mode
157
+ fetch_grouped(active_queues, qty)
144
158
  elsif active_queues.size == 1
145
159
  queue = active_queues.first
146
160
  messages = Pgbus.client.read_batch(queue, qty: qty) || []
@@ -210,6 +224,29 @@ module Pgbus
210
224
  end
211
225
  end
212
226
 
227
+ # Use grouped reads for fair or throughput-optimized multi-tenant processing.
228
+ # Each queue is read independently with the configured group strategy.
229
+ def fetch_grouped(active_queues, qty)
230
+ remaining = qty
231
+ results = []
232
+
233
+ active_queues.each do |queue|
234
+ break if remaining <= 0
235
+
236
+ messages = case @group_mode
237
+ when :round_robin
238
+ Pgbus.client.read_grouped_rr(queue, qty: remaining) || []
239
+ else # :fifo
240
+ Pgbus.client.read_grouped(queue, qty: remaining) || []
241
+ end
242
+
243
+ messages.each { |m| results << [queue, m] }
244
+ remaining -= messages.size
245
+ end
246
+
247
+ results
248
+ end
249
+
213
250
  def priority_enabled?
214
251
  config.priority_levels && config.priority_levels > 1
215
252
  end
@@ -58,7 +58,7 @@ module Pgbus
58
58
  raise ArgumentError, "Invalid GlobalID: #{gid_string.inspect}" unless gid
59
59
 
60
60
  allowed = Pgbus.configuration.allowed_global_id_models
61
- if allowed&.empty?
61
+ if allowed && allowed.empty?
62
62
  raise ArgumentError,
63
63
  "GlobalID deserialization is disabled (allowed_global_id_models is empty). " \
64
64
  "Set to nil to allow all models, or add permitted classes."
data/lib/pgbus/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pgbus
4
- VERSION = "0.8.4"
4
+ VERSION = "0.9.0"
5
5
  end
@@ -590,6 +590,43 @@ module Pgbus
590
590
  []
591
591
  end
592
592
 
593
+ # Batches
594
+ def batches(limit: 100)
595
+ BatchEntry.order(created_at: :desc).limit(limit).map { |r| format_batch(r) }
596
+ rescue StandardError => e
597
+ Pgbus.logger.debug { "[Pgbus::Web] Error fetching batches: #{e.message}" }
598
+ []
599
+ end
600
+
601
+ def batch_detail(batch_id)
602
+ record = BatchEntry.find_by(batch_id: batch_id)
603
+ return nil unless record
604
+
605
+ format_batch(record).merge(
606
+ properties: record.properties,
607
+ on_finish_class: record.on_finish_class,
608
+ on_success_class: record.on_success_class,
609
+ on_discard_class: record.on_discard_class
610
+ )
611
+ rescue StandardError => e
612
+ Pgbus.logger.debug { "[Pgbus::Web] Error fetching batch #{batch_id}: #{e.message}" }
613
+ nil
614
+ end
615
+
616
+ def batches_count
617
+ BatchEntry.count
618
+ rescue StandardError => e
619
+ Pgbus.logger.debug { "[Pgbus::Web] Error counting batches: #{e.message}" }
620
+ 0
621
+ end
622
+
623
+ def active_batches_count
624
+ BatchEntry.where.not(status: "finished").count
625
+ rescue StandardError => e
626
+ Pgbus.logger.debug { "[Pgbus::Web] Error counting active batches: #{e.message}" }
627
+ 0
628
+ end
629
+
593
630
  # Job stats
594
631
  def job_stats_summary(minutes: 60)
595
632
  JobStat.summary(minutes: minutes)
@@ -874,6 +911,33 @@ module Pgbus
874
911
  []
875
912
  end
876
913
 
914
+ # NOTIFY throttle status for all queues with notifications enabled.
915
+ # Returns an array of hashes: { queue_name:, throttle_interval_ms:, last_notified_at: }
916
+ def notify_throttles
917
+ @client.list_notify_insert_throttles.map do |throttle|
918
+ {
919
+ queue_name: throttle.queue_name,
920
+ throttle_interval_ms: throttle.throttle_interval_ms,
921
+ last_notified_at: throttle.last_notified_at
922
+ }
923
+ end
924
+ rescue StandardError => e
925
+ Pgbus.logger.debug { "[Pgbus::Web] Error fetching notify throttles: #{e.message}" }
926
+ []
927
+ end
928
+
929
+ # FIFO group head sampling for a specific queue.
930
+ # Returns the oldest visible message from each distinct group (up to qty).
931
+ # Useful for detecting head-of-line stalls in multi-tenant queues.
932
+ def queue_group_heads(queue_name, qty: 20)
933
+ logical = logical_queue_name(queue_name)
934
+ messages = @client.read_grouped_head(logical, qty: qty) || []
935
+ messages.map { |m| format_pgmq_message(m, queue_name) }
936
+ rescue StandardError => e
937
+ Pgbus.logger.debug { "[Pgbus::Web] Error fetching group heads for #{queue_name}: #{e.message}" }
938
+ []
939
+ end
940
+
877
941
  private
878
942
 
879
943
  def connection
@@ -1114,6 +1178,19 @@ module Pgbus
1114
1178
  }
1115
1179
  end
1116
1180
 
1181
+ def format_pgmq_message(msg, queue_name)
1182
+ {
1183
+ msg_id: msg.msg_id.to_i,
1184
+ read_ct: msg.read_ct.to_i,
1185
+ enqueued_at: msg.enqueued_at,
1186
+ last_read_at: msg.respond_to?(:last_read_at) ? msg.last_read_at : nil,
1187
+ vt: msg.respond_to?(:vt) ? msg.vt : nil,
1188
+ message: msg.message,
1189
+ headers: msg.headers,
1190
+ queue_name: queue_name
1191
+ }
1192
+ end
1193
+
1117
1194
  def format_process(row)
1118
1195
  heartbeat = row["last_heartbeat_at"]
1119
1196
  heartbeat_time = heartbeat.is_a?(String) ? Time.parse(heartbeat) : heartbeat
@@ -1131,6 +1208,25 @@ module Pgbus
1131
1208
  }
1132
1209
  end
1133
1210
 
1211
+ def format_batch(record)
1212
+ total = record.total_jobs
1213
+ done = record.completed_jobs + record.discarded_jobs
1214
+ pct = total.positive? ? ((done * 100) / total) : 100
1215
+
1216
+ {
1217
+ batch_id: record.batch_id,
1218
+ description: record.description,
1219
+ status: record.status,
1220
+ total_jobs: total,
1221
+ completed_jobs: record.completed_jobs,
1222
+ discarded_jobs: record.discarded_jobs,
1223
+ failed_jobs: record.failed_jobs,
1224
+ progress_pct: pct,
1225
+ created_at: record.created_at,
1226
+ finished_at: record.finished_at
1227
+ }
1228
+ end
1229
+
1134
1230
  def sanitize_name(name)
1135
1231
  QueueNameValidator.sanitize!(name)
1136
1232
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :pgbus do
4
+ namespace :queues do
5
+ desc "Create FIFO indexes on all pgbus queues (required for grouped reads)"
6
+ task fifo_indexes: :environment do
7
+ puts "Creating FIFO indexes on all pgbus queues..."
8
+ Pgbus.client.create_fifo_indexes_all
9
+ puts "Done. FIFO indexes created on all queues."
10
+ end
11
+
12
+ desc "Create FIFO index on a specific queue (QUEUE=name)"
13
+ task fifo_index: :environment do
14
+ queue = ENV.fetch("QUEUE") do
15
+ abort "Usage: rake pgbus:queues:fifo_index QUEUE=<queue_name>"
16
+ end
17
+ puts "Creating FIFO index on queue '#{queue}'..."
18
+ Pgbus.client.create_fifo_index(queue)
19
+ puts "Done."
20
+ end
21
+ end
22
+
23
+ namespace :archives do
24
+ desc "Convert a queue's archive table to pg_partman-managed partitions (QUEUE=name)"
25
+ task partition: :environment do
26
+ queue = ENV.fetch("QUEUE") do
27
+ abort "Usage: rake pgbus:archives:partition QUEUE=<queue_name> " \
28
+ "[INTERVAL=10000] [RETENTION=100000] [LEADING_PARTITION=10]"
29
+ end
30
+ interval = ENV.fetch("INTERVAL", "10000")
31
+ retention = ENV.fetch("RETENTION", "100000")
32
+ leading_raw = ENV.fetch("LEADING_PARTITION", "10")
33
+ leading = begin
34
+ Integer(leading_raw, 10)
35
+ rescue ArgumentError, TypeError
36
+ abort "LEADING_PARTITION must be a positive integer, got #{leading_raw.inspect}"
37
+ end
38
+ abort "LEADING_PARTITION must be a positive integer" if leading <= 0
39
+
40
+ puts "Converting archive table for queue '#{queue}' to partitioned..."
41
+ puts " Partition interval: #{interval}"
42
+ puts " Retention interval: #{retention}"
43
+ puts " Leading partitions: #{leading}"
44
+
45
+ Pgbus.client.convert_archive_partitioned(
46
+ queue,
47
+ partition_interval: interval,
48
+ retention_interval: retention,
49
+ leading_partition: leading
50
+ )
51
+ puts "Done. Archive table is now partitioned."
52
+ end
53
+ end
54
+ end