pgbus 0.19.0 → 0.19.2
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/CHANGELOG.md +329 -147
- data/README.md +65 -27
- data/app/controllers/pgbus/deploy_tasks_controller.rb +38 -0
- data/app/helpers/pgbus/deploy_tasks_helper.rb +77 -0
- data/app/helpers/pgbus/processes_helper.rb +1 -1
- data/app/models/pgbus/deploy_task.rb +104 -0
- data/app/models/pgbus/deploy_task_run.rb +45 -0
- data/app/views/layouts/pgbus/application.html.erb +2 -0
- data/app/views/pgbus/deploy_tasks/_rollover_banner.html.erb +25 -0
- data/app/views/pgbus/deploy_tasks/_tasks_table.html.erb +71 -0
- data/app/views/pgbus/deploy_tasks/index.html.erb +6 -0
- data/app/views/pgbus/deploy_tasks/show.html.erb +134 -0
- data/config/i18n-tasks.yml +2 -0
- data/config/locales/da.yml +97 -0
- data/config/locales/de.yml +97 -0
- data/config/locales/en.yml +98 -1
- data/config/locales/es.yml +97 -0
- data/config/locales/fi.yml +97 -0
- data/config/locales/fr.yml +97 -0
- data/config/locales/it.yml +97 -0
- data/config/locales/ja.yml +97 -0
- data/config/locales/nb.yml +97 -0
- data/config/locales/nl.yml +97 -0
- data/config/locales/pt.yml +97 -0
- data/config/locales/sv.yml +97 -0
- data/config/routes.rb +7 -0
- data/lib/generators/pgbus/add_batch_callback_jobs_generator.rb +4 -1
- data/lib/generators/pgbus/add_deploy_tasks_generator.rb +51 -0
- data/lib/generators/pgbus/templates/add_deploy_tasks_tables.rb.erb +43 -0
- data/lib/generators/pgbus/templates/deploy_tasks.yml.erb +42 -0
- data/lib/generators/pgbus/templates/migration.rb.erb +43 -0
- data/lib/pgbus/active_job/batch_id.rb +10 -4
- data/lib/pgbus/batch/builder.rb +92 -0
- data/lib/pgbus/batch.rb +97 -22
- data/lib/pgbus/configuration/deploy_settings.rb +46 -0
- data/lib/pgbus/configuration.rb +3 -0
- data/lib/pgbus/deploy/revision.rb +59 -0
- data/lib/pgbus/deploy/rollover.rb +65 -0
- data/lib/pgbus/deploy/runner.rb +259 -0
- data/lib/pgbus/deploy/task.rb +105 -0
- data/lib/pgbus/engine.rb +17 -0
- data/lib/pgbus/generators/migration_detector.rb +12 -1
- data/lib/pgbus/instrumentation.rb +14 -0
- data/lib/pgbus/integrations/appsignal/subscriber.rb +15 -0
- data/lib/pgbus/mcp/server.rb +2 -1
- data/lib/pgbus/mcp/tools/deploy_tasks_tool.rb +86 -0
- data/lib/pgbus/mcp.rb +1 -0
- data/lib/pgbus/metrics/subscriber.rb +12 -0
- data/lib/pgbus/outbox/poller.rb +1 -0
- data/lib/pgbus/process/consumer.rb +57 -3
- data/lib/pgbus/process/dispatcher.rb +18 -1
- data/lib/pgbus/process/heartbeat.rb +7 -2
- data/lib/pgbus/process/supervisor.rb +35 -3
- data/lib/pgbus/process/worker.rb +1 -0
- data/lib/pgbus/recurring/scheduler.rb +54 -20
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source/deploy_task_list.rb +114 -0
- data/lib/pgbus/web/data_source/list_counts.rb +2 -1
- data/lib/pgbus/web/data_source.rb +1 -0
- data/lib/pgbus/web/deploy_task_state.rb +51 -0
- data/lib/pgbus.rb +11 -0
- data/sig/pgbus/configuration.rbs +10 -0
- data/sig/pgbus.rbs +9 -0
- metadata +21 -1
data/config/locales/pt.yml
CHANGED
|
@@ -236,6 +236,102 @@ pt:
|
|
|
236
236
|
payload: Carga útil
|
|
237
237
|
retry: Tentar novamente
|
|
238
238
|
title: 'Mensagem DLQ #%{id}'
|
|
239
|
+
deploy_tasks:
|
|
240
|
+
banner:
|
|
241
|
+
heading: Transição
|
|
242
|
+
lagging_html:
|
|
243
|
+
one: 'Aguardando a transição: 1 processo ainda não executa <code>%{revision}</code>.'
|
|
244
|
+
other: 'Aguardando a transição: %{count} processos ainda não executam <code>%{revision}</code>.'
|
|
245
|
+
lagging_more: "…e mais %{count}"
|
|
246
|
+
lagging_process: "%{kind}@%{hostname} pid %{pid} em %{revision}"
|
|
247
|
+
live_count: "%{count} em %{revision}"
|
|
248
|
+
live_heading: 'Processos ativos:'
|
|
249
|
+
no_revision_hint_html: Defina <code>config.app_revision</code>, ou uma de <code>PGBUS_REVISION</code>, <code>DASH_VERSION</code> ou <code>KAMAL_VERSION</code>, ou inclua um arquivo <code>REVISION</code>. Executar agora continua funcionando.
|
|
250
|
+
no_revision_html: 'Revisão da aplicação não configurada: as tarefas de deploy aguardam até que esteja.'
|
|
251
|
+
no_revision_label: sem revisão
|
|
252
|
+
no_scheduler_html: 'Nenhum agendador em execução: as tarefas de deploy só são disparadas pelo agendador. Este veredito usa a revisão do próprio painel.'
|
|
253
|
+
no_workers_html: 'Aguardando a transição: ainda nenhum worker em <code>%{revision}</code>.'
|
|
254
|
+
ready_html: 'Transição concluída: cada processo ativo executa <code>%{revision}</code>. Tarefas armadas executam no próximo tick do agendador.'
|
|
255
|
+
unavailable_html: Não foi possível ler o estado da transição.
|
|
256
|
+
default_queue: default
|
|
257
|
+
disable: Desativar
|
|
258
|
+
enable: Ativar
|
|
259
|
+
enqueue:
|
|
260
|
+
enqueued: Tarefa de deploy enfileirada
|
|
261
|
+
failed: 'A tarefa de deploy não foi enfileirada: veja o log para o motivo'
|
|
262
|
+
every_deploy: Em todo deploy
|
|
263
|
+
index:
|
|
264
|
+
task_count:
|
|
265
|
+
one: "%{count} tarefa declarada"
|
|
266
|
+
other: "%{count} tarefas declaradas"
|
|
267
|
+
title: Tarefas de deploy
|
|
268
|
+
not_found: Tarefa de deploy não encontrada
|
|
269
|
+
reasons:
|
|
270
|
+
disabled_armed_html: 'Desativada com uma execução de transição pendente: ative-a para deixá-la executar.'
|
|
271
|
+
disabled_html: 'Desativada: o agendador a ignora.'
|
|
272
|
+
due_html: 'Pronta: executa no próximo tick do agendador.'
|
|
273
|
+
manual_html: "· executada %{ago} (manual)"
|
|
274
|
+
never_html: Nunca executada após uma transição.
|
|
275
|
+
ran_html: Executada %{ago} em <code>%{revision}</code> (transição).
|
|
276
|
+
waiting_lagging_html:
|
|
277
|
+
one: 'Aguardando a transição: 1 processo ainda não executa <code>%{revision}</code>.'
|
|
278
|
+
other: 'Aguardando a transição: %{count} processos ainda não executam <code>%{revision}</code>.'
|
|
279
|
+
waiting_no_revision_html: 'Aguardando: a revisão da aplicação não está configurada.'
|
|
280
|
+
waiting_no_scheduler_html: 'Aguardando: nenhum agendador em execução.'
|
|
281
|
+
waiting_no_workers_html: Aguardando um worker em <code>%{revision}</code>.
|
|
282
|
+
waiting_unknown_html: 'Aguardando: não foi possível ler o estado da transição.'
|
|
283
|
+
run_now: Executar agora
|
|
284
|
+
show:
|
|
285
|
+
armed_html: "%{armed_for}, armada %{ago}"
|
|
286
|
+
back: Voltar
|
|
287
|
+
configuration: Configuração
|
|
288
|
+
labels:
|
|
289
|
+
arguments: Argumentos
|
|
290
|
+
armed: Execução pendente
|
|
291
|
+
command: Comando
|
|
292
|
+
description: Descrição
|
|
293
|
+
job_class: Classe do job
|
|
294
|
+
priority: Prioridade
|
|
295
|
+
queue: Fila
|
|
296
|
+
source: Origem
|
|
297
|
+
last_run: Última execução
|
|
298
|
+
no_runs: Nenhuma execução registrada ainda
|
|
299
|
+
recent_runs: Execuções recentes
|
|
300
|
+
run_headers:
|
|
301
|
+
enqueued_at: Enfileirada em
|
|
302
|
+
queue: Fila
|
|
303
|
+
revision: Revisão
|
|
304
|
+
triggered_by: Disparada por
|
|
305
|
+
version: Versão
|
|
306
|
+
source_config: Arquivo de configuração
|
|
307
|
+
source_dynamic: Dinâmica
|
|
308
|
+
state: Estado
|
|
309
|
+
version: Versão
|
|
310
|
+
states:
|
|
311
|
+
disabled: Desativada
|
|
312
|
+
due: Pronta
|
|
313
|
+
never: Nunca executada
|
|
314
|
+
ran: Executada
|
|
315
|
+
waiting: Aguardando
|
|
316
|
+
tasks_table:
|
|
317
|
+
empty_hint_html: Declare tarefas pontuais em <code>config/deploy_tasks.yml</code>; <code>rails generate pgbus:add_deploy_tasks</code> cria um arquivo inicial.
|
|
318
|
+
empty_title: Nenhuma tarefa de deploy declarada
|
|
319
|
+
headers:
|
|
320
|
+
actions: Ações
|
|
321
|
+
last_run: Última execução
|
|
322
|
+
queue: Fila
|
|
323
|
+
state: Estado
|
|
324
|
+
task: Tarefa
|
|
325
|
+
version: Versão
|
|
326
|
+
never: Nunca
|
|
327
|
+
toggle:
|
|
328
|
+
disabled: Tarefa de deploy desativada
|
|
329
|
+
enabled: Tarefa de deploy ativada
|
|
330
|
+
failed: Falha ao alternar a tarefa de deploy
|
|
331
|
+
triggered_by:
|
|
332
|
+
manual: Manual
|
|
333
|
+
rollover: Transição
|
|
334
|
+
version: v%{version}
|
|
239
335
|
dialogs:
|
|
240
336
|
cancel: Cancelar
|
|
241
337
|
confirm: Confirmar
|
|
@@ -617,6 +713,7 @@ pt:
|
|
|
617
713
|
nav:
|
|
618
714
|
batches: Lotes
|
|
619
715
|
dashboard: Painel
|
|
716
|
+
deploy_tasks: Deploy
|
|
620
717
|
dlq: DLQ
|
|
621
718
|
events: Eventos
|
|
622
719
|
insights: Insights
|
data/config/locales/sv.yml
CHANGED
|
@@ -236,6 +236,102 @@ sv:
|
|
|
236
236
|
payload: Innehåll
|
|
237
237
|
retry: Försök igen
|
|
238
238
|
title: 'DLQ-meddelande #%{id}'
|
|
239
|
+
deploy_tasks:
|
|
240
|
+
banner:
|
|
241
|
+
heading: Utrullning
|
|
242
|
+
lagging_html:
|
|
243
|
+
one: 'Väntar på utrullning: 1 process kör ännu inte <code>%{revision}</code>.'
|
|
244
|
+
other: 'Väntar på utrullning: %{count} processer kör ännu inte <code>%{revision}</code>.'
|
|
245
|
+
lagging_more: "…och %{count} till"
|
|
246
|
+
lagging_process: "%{kind}@%{hostname} pid %{pid} på %{revision}"
|
|
247
|
+
live_count: "%{count} på %{revision}"
|
|
248
|
+
live_heading: 'Aktiva processer:'
|
|
249
|
+
no_revision_hint_html: Sätt <code>config.app_revision</code>, eller någon av <code>PGBUS_REVISION</code>, <code>DASH_VERSION</code> eller <code>KAMAL_VERSION</code>, eller leverera en <code>REVISION</code>-fil. Kör nu fungerar fortfarande.
|
|
250
|
+
no_revision_html: 'Appens revision är inte konfigurerad: deploy-uppgifter väntar tills den är det.'
|
|
251
|
+
no_revision_label: ingen revision
|
|
252
|
+
no_scheduler_html: 'Ingen schemaläggare körs: deploy-uppgifter startas bara av schemaläggaren. Bedömningen använder instrumentpanelens egen revision.'
|
|
253
|
+
no_workers_html: 'Väntar på utrullning: ingen worker på <code>%{revision}</code> ännu.'
|
|
254
|
+
ready_html: 'Utrullad: varje aktiv process kör <code>%{revision}</code>. Väntande uppgifter körs vid schemaläggarens nästa tick.'
|
|
255
|
+
unavailable_html: Utrullningsstatusen kunde inte läsas.
|
|
256
|
+
default_queue: default
|
|
257
|
+
disable: Inaktivera
|
|
258
|
+
enable: Aktivera
|
|
259
|
+
enqueue:
|
|
260
|
+
enqueued: Deploy-uppgiften köad
|
|
261
|
+
failed: 'Deploy-uppgiften köades inte: se loggen för orsaken'
|
|
262
|
+
every_deploy: Vid varje deploy
|
|
263
|
+
index:
|
|
264
|
+
task_count:
|
|
265
|
+
one: "%{count} uppgift deklarerad"
|
|
266
|
+
other: "%{count} uppgifter deklarerade"
|
|
267
|
+
title: Deploy-uppgifter
|
|
268
|
+
not_found: Deploy-uppgiften hittades inte
|
|
269
|
+
reasons:
|
|
270
|
+
disabled_armed_html: 'Inaktiverad med en väntande utrullningskörning: aktivera den för att låta den köras.'
|
|
271
|
+
disabled_html: 'Inaktiverad: schemaläggaren hoppar över den.'
|
|
272
|
+
due_html: 'Redo: körs vid schemaläggarens nästa tick.'
|
|
273
|
+
manual_html: "· kördes %{ago} (manuellt)"
|
|
274
|
+
never_html: Har aldrig körts efter en utrullning.
|
|
275
|
+
ran_html: Kördes %{ago} på <code>%{revision}</code> (utrullning).
|
|
276
|
+
waiting_lagging_html:
|
|
277
|
+
one: 'Väntar på utrullning: 1 process kör ännu inte <code>%{revision}</code>.'
|
|
278
|
+
other: 'Väntar på utrullning: %{count} processer kör ännu inte <code>%{revision}</code>.'
|
|
279
|
+
waiting_no_revision_html: 'Väntar: appens revision är inte konfigurerad.'
|
|
280
|
+
waiting_no_scheduler_html: 'Väntar: ingen schemaläggare körs.'
|
|
281
|
+
waiting_no_workers_html: Väntar på en worker på <code>%{revision}</code>.
|
|
282
|
+
waiting_unknown_html: 'Väntar: utrullningsstatusen kunde inte läsas.'
|
|
283
|
+
run_now: Kör nu
|
|
284
|
+
show:
|
|
285
|
+
armed_html: "%{armed_for}, förberedd %{ago}"
|
|
286
|
+
back: Tillbaka
|
|
287
|
+
configuration: Konfiguration
|
|
288
|
+
labels:
|
|
289
|
+
arguments: Argument
|
|
290
|
+
armed: Väntande körning
|
|
291
|
+
command: Kommando
|
|
292
|
+
description: Beskrivning
|
|
293
|
+
job_class: Jobbklass
|
|
294
|
+
priority: Prioritet
|
|
295
|
+
queue: Kö
|
|
296
|
+
source: Källa
|
|
297
|
+
last_run: Senaste körning
|
|
298
|
+
no_runs: Inga körningar registrerade ännu
|
|
299
|
+
recent_runs: Senaste körningar
|
|
300
|
+
run_headers:
|
|
301
|
+
enqueued_at: Köad
|
|
302
|
+
queue: Kö
|
|
303
|
+
revision: Revision
|
|
304
|
+
triggered_by: Startad av
|
|
305
|
+
version: Version
|
|
306
|
+
source_config: Konfigurationsfil
|
|
307
|
+
source_dynamic: Dynamisk
|
|
308
|
+
state: Tillstånd
|
|
309
|
+
version: Version
|
|
310
|
+
states:
|
|
311
|
+
disabled: Inaktiverad
|
|
312
|
+
due: Redo
|
|
313
|
+
never: Aldrig körd
|
|
314
|
+
ran: Körd
|
|
315
|
+
waiting: Väntar
|
|
316
|
+
tasks_table:
|
|
317
|
+
empty_hint_html: Deklarera engångsuppgifter i <code>config/deploy_tasks.yml</code>; <code>rails generate pgbus:add_deploy_tasks</code> skapar en startfil.
|
|
318
|
+
empty_title: Inga deploy-uppgifter deklarerade
|
|
319
|
+
headers:
|
|
320
|
+
actions: Åtgärder
|
|
321
|
+
last_run: Senaste körning
|
|
322
|
+
queue: Kö
|
|
323
|
+
state: Tillstånd
|
|
324
|
+
task: Uppgift
|
|
325
|
+
version: Version
|
|
326
|
+
never: Aldrig
|
|
327
|
+
toggle:
|
|
328
|
+
disabled: Deploy-uppgiften inaktiverad
|
|
329
|
+
enabled: Deploy-uppgiften aktiverad
|
|
330
|
+
failed: Det gick inte att växla deploy-uppgiften
|
|
331
|
+
triggered_by:
|
|
332
|
+
manual: Manuellt
|
|
333
|
+
rollover: Utrullning
|
|
334
|
+
version: v%{version}
|
|
239
335
|
dialogs:
|
|
240
336
|
cancel: Avbryt
|
|
241
337
|
confirm: Bekräfta
|
|
@@ -617,6 +713,7 @@ sv:
|
|
|
617
713
|
nav:
|
|
618
714
|
batches: Grupper
|
|
619
715
|
dashboard: Instrumentpanel
|
|
716
|
+
deploy_tasks: Deploy
|
|
620
717
|
dlq: DLQ
|
|
621
718
|
events: Händelser
|
|
622
719
|
insights: Insikter
|
data/config/routes.rb
CHANGED
|
@@ -33,7 +33,10 @@ module Pgbus
|
|
|
33
33
|
say " 2. Restart pgbus: bin/pgbus start"
|
|
34
34
|
say ""
|
|
35
35
|
say "You can now pass a configured ActiveJob instance to a batch:"
|
|
36
|
-
say " Pgbus
|
|
36
|
+
say " Pgbus.batch do |b|"
|
|
37
|
+
say " b.on_complete ReportJob.new.set(queue: :critical, wait: 5.minutes)"
|
|
38
|
+
say " b.add(*rows.map { |row| ImportRowJob.new(row.id) })"
|
|
39
|
+
say " end"
|
|
37
40
|
say ""
|
|
38
41
|
end
|
|
39
42
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
require_relative "migration_path"
|
|
6
|
+
|
|
7
|
+
module Pgbus
|
|
8
|
+
module Generators
|
|
9
|
+
class AddDeployTasksGenerator < Rails::Generators::Base
|
|
10
|
+
include ActiveRecord::Generators::Migration
|
|
11
|
+
include MigrationPath
|
|
12
|
+
|
|
13
|
+
source_root File.expand_path("templates", __dir__)
|
|
14
|
+
|
|
15
|
+
desc "Add deploy tasks tables (one-time jobs run after a rollover) to an existing Pgbus installation"
|
|
16
|
+
|
|
17
|
+
class_option :database,
|
|
18
|
+
type: :string,
|
|
19
|
+
default: nil,
|
|
20
|
+
desc: "Use a separate database for pgbus tables (e.g. --database=pgbus)"
|
|
21
|
+
|
|
22
|
+
def create_migration_file
|
|
23
|
+
migration_template "add_deploy_tasks_tables.rb.erb",
|
|
24
|
+
File.join(pgbus_migrate_path, "add_pgbus_deploy_tasks_tables.rb")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create_deploy_tasks_config
|
|
28
|
+
template "deploy_tasks.yml.erb", "config/deploy_tasks.yml"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def display_post_install
|
|
32
|
+
say ""
|
|
33
|
+
say "Pgbus deploy tasks installed!", :green
|
|
34
|
+
say ""
|
|
35
|
+
say "Next steps:"
|
|
36
|
+
say " 1. Run: rails db:migrate#{migrate_command_suffix}"
|
|
37
|
+
say " 2. Edit config/deploy_tasks.yml to declare your one-time tasks"
|
|
38
|
+
say " 3. Make sure every pgbus process knows the app revision (config.app_revision,"
|
|
39
|
+
say " PGBUS_REVISION, DASH_VERSION, KAMAL_VERSION or a REVISION file)"
|
|
40
|
+
say " 4. Restart pgbus: bin/pgbus start"
|
|
41
|
+
say ""
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def migration_version
|
|
47
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
class AddPgbusDeployTasksTables < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
+
# if_not_exists throughout: pgbus:update queues this migration when either
|
|
3
|
+
# table is missing, so a half-present schema must not fail it.
|
|
4
|
+
def change
|
|
5
|
+
create_table :pgbus_deploy_tasks, if_not_exists: true do |t|
|
|
6
|
+
t.string :key, null: false
|
|
7
|
+
t.string :class_name
|
|
8
|
+
t.string :command, limit: 2048
|
|
9
|
+
t.text :arguments
|
|
10
|
+
t.string :queue_name
|
|
11
|
+
t.integer :priority, default: 0
|
|
12
|
+
t.string :version, null: false, default: "1"
|
|
13
|
+
t.boolean :every_deploy, null: false, default: false
|
|
14
|
+
t.boolean :static, null: false, default: true
|
|
15
|
+
t.boolean :enabled, null: false, default: true
|
|
16
|
+
t.text :description
|
|
17
|
+
t.datetime :armed_at
|
|
18
|
+
t.string :armed_for
|
|
19
|
+
t.timestamps
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
add_index :pgbus_deploy_tasks, :key,
|
|
23
|
+
unique: true, name: "idx_pgbus_deploy_tasks_key", if_not_exists: true
|
|
24
|
+
add_index :pgbus_deploy_tasks, :armed_at,
|
|
25
|
+
where: "armed_at IS NOT NULL", name: "idx_pgbus_deploy_tasks_armed", if_not_exists: true
|
|
26
|
+
|
|
27
|
+
create_table :pgbus_deploy_task_runs, if_not_exists: true do |t|
|
|
28
|
+
t.string :task_key, null: false
|
|
29
|
+
t.string :version, null: false
|
|
30
|
+
t.string :revision
|
|
31
|
+
t.string :triggered_by, null: false
|
|
32
|
+
t.string :queue_name
|
|
33
|
+
t.bigint :msg_id
|
|
34
|
+
t.datetime :run_at, null: false
|
|
35
|
+
t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
add_index :pgbus_deploy_task_runs, [:task_key, :run_at],
|
|
39
|
+
name: "idx_pgbus_deploy_task_runs_task", if_not_exists: true
|
|
40
|
+
add_index :pgbus_deploy_task_runs, :run_at,
|
|
41
|
+
name: "idx_pgbus_deploy_task_runs_cleanup", if_not_exists: true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Pgbus Deploy Tasks Configuration
|
|
2
|
+
#
|
|
3
|
+
# One-time jobs that run once the whole fleet has rolled over to a new app
|
|
4
|
+
# revision: a backfill, a data fix, a cache warm. Same shape as
|
|
5
|
+
# config/recurring.yml, without schedule:. Supports environment-scoped or
|
|
6
|
+
# flat configuration.
|
|
7
|
+
#
|
|
8
|
+
# The scheduler enqueues a task only when every live pgbus process reports
|
|
9
|
+
# the scheduler's own app revision and at least one worker on it is alive,
|
|
10
|
+
# so a task never runs on old code during the deploy overlap window. The
|
|
11
|
+
# revision comes from config.app_revision, then PGBUS_REVISION, DASH_VERSION,
|
|
12
|
+
# KAMAL_VERSION, then the REVISION file at Rails.root. Without one, deploy
|
|
13
|
+
# tasks wait (the dashboard says so) and only "Run now" enqueues them.
|
|
14
|
+
#
|
|
15
|
+
# Format:
|
|
16
|
+
# task_key:
|
|
17
|
+
# class: JobClassName # ActiveJob class to run (or use command:)
|
|
18
|
+
# version: 1 # Runs once per version; bump it to run again after the next deploy
|
|
19
|
+
# every_deploy: false # true: run after every rollover instead (version is ignored)
|
|
20
|
+
# queue: default # Optional: queue name (default: pgbus default queue)
|
|
21
|
+
# args: [arg1, arg2] # Optional: job arguments
|
|
22
|
+
# priority: 0 # Optional: numeric priority
|
|
23
|
+
# description: "text" # Optional: human-readable description
|
|
24
|
+
#
|
|
25
|
+
# Command-based tasks (no job class needed):
|
|
26
|
+
# warm_fragment_caches:
|
|
27
|
+
# command: "FragmentCacheWarmer.run"
|
|
28
|
+
# every_deploy: true
|
|
29
|
+
#
|
|
30
|
+
# Examples:
|
|
31
|
+
#
|
|
32
|
+
# production:
|
|
33
|
+
# backfill_order_totals:
|
|
34
|
+
# class: BackfillOrderTotalsJob
|
|
35
|
+
# version: 2
|
|
36
|
+
# queue: maintenance
|
|
37
|
+
# args: [{ batch_size: 500 }]
|
|
38
|
+
# description: Recompute totals after the currency migration
|
|
39
|
+
#
|
|
40
|
+
# warm_fragment_caches:
|
|
41
|
+
# command: "FragmentCacheWarmer.run"
|
|
42
|
+
# every_deploy: true
|
|
@@ -170,6 +170,47 @@ class CreatePgbusTables < ActiveRecord::Migration<%= migration_version %>
|
|
|
170
170
|
add_index :pgbus_recurring_executions, :run_at,
|
|
171
171
|
name: "idx_pgbus_recurring_executions_cleanup"
|
|
172
172
|
|
|
173
|
+
# Deploy tasks (synced from config/deploy_tasks.yml): one-time jobs the
|
|
174
|
+
# scheduler enqueues once the fleet has rolled over to a new revision.
|
|
175
|
+
# armed_at/armed_for is the compare-and-set claim; runs are the history.
|
|
176
|
+
create_table :pgbus_deploy_tasks do |t|
|
|
177
|
+
t.string :key, null: false
|
|
178
|
+
t.string :class_name
|
|
179
|
+
t.string :command, limit: 2048
|
|
180
|
+
t.text :arguments
|
|
181
|
+
t.string :queue_name
|
|
182
|
+
t.integer :priority, default: 0
|
|
183
|
+
t.string :version, null: false, default: "1"
|
|
184
|
+
t.boolean :every_deploy, null: false, default: false
|
|
185
|
+
t.boolean :static, null: false, default: true
|
|
186
|
+
t.boolean :enabled, null: false, default: true
|
|
187
|
+
t.text :description
|
|
188
|
+
t.datetime :armed_at
|
|
189
|
+
t.string :armed_for
|
|
190
|
+
t.timestamps
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
add_index :pgbus_deploy_tasks, :key,
|
|
194
|
+
unique: true, name: "idx_pgbus_deploy_tasks_key"
|
|
195
|
+
add_index :pgbus_deploy_tasks, :armed_at,
|
|
196
|
+
where: "armed_at IS NOT NULL", name: "idx_pgbus_deploy_tasks_armed"
|
|
197
|
+
|
|
198
|
+
create_table :pgbus_deploy_task_runs do |t|
|
|
199
|
+
t.string :task_key, null: false
|
|
200
|
+
t.string :version, null: false
|
|
201
|
+
t.string :revision
|
|
202
|
+
t.string :triggered_by, null: false
|
|
203
|
+
t.string :queue_name
|
|
204
|
+
t.bigint :msg_id
|
|
205
|
+
t.datetime :run_at, null: false
|
|
206
|
+
t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
add_index :pgbus_deploy_task_runs, [:task_key, :run_at],
|
|
210
|
+
name: "idx_pgbus_deploy_task_runs_task"
|
|
211
|
+
add_index :pgbus_deploy_task_runs, :run_at,
|
|
212
|
+
name: "idx_pgbus_deploy_task_runs_cleanup"
|
|
213
|
+
|
|
173
214
|
# Stream queue registry — records which PGMQ queues back streams (vs jobs).
|
|
174
215
|
# Stream and job queues share the pgbus_ namespace, so maintenance
|
|
175
216
|
# (per-stream retention, orphan sweep) and wildcard workers rely on this
|
|
@@ -204,6 +245,8 @@ class CreatePgbusTables < ActiveRecord::Migration<%= migration_version %>
|
|
|
204
245
|
execute "SELECT pgmq.drop_queue('pgbus_default')"
|
|
205
246
|
|
|
206
247
|
drop_table :pgbus_stream_queues
|
|
248
|
+
drop_table :pgbus_deploy_task_runs
|
|
249
|
+
drop_table :pgbus_deploy_tasks
|
|
207
250
|
drop_table :pgbus_recurring_executions
|
|
208
251
|
drop_table :pgbus_recurring_tasks
|
|
209
252
|
drop_table :pgbus_batch_executions
|
|
@@ -8,9 +8,9 @@ module Pgbus
|
|
|
8
8
|
#
|
|
9
9
|
# * +batch_id+ — the batch this job is a *member* of. Assigned by the
|
|
10
10
|
# executor from the payload's +pgbus_batch_id+ before +perform+, so a
|
|
11
|
-
# running job can call +batch.
|
|
11
|
+
# running job can call +batch.add+ to add siblings.
|
|
12
12
|
# * +callback_batch_id+ — the batch this job *reports on*. Set on
|
|
13
|
-
# +
|
|
13
|
+
# +on_complete+/+on_success+/+on_failure+ jobs at fire time. A callback is
|
|
14
14
|
# never a member of the batch it reports on, so its +batch_id+ is nil.
|
|
15
15
|
#
|
|
16
16
|
# Both round-trip through +serialize+/+deserialize+, and both are omitted
|
|
@@ -36,8 +36,14 @@ module Pgbus
|
|
|
36
36
|
self.callback_batch_id = job_data["callback_batch_id"]
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
#
|
|
40
|
-
|
|
39
|
+
# Without a block: the batch this job reports on, or is a member of; nil
|
|
40
|
+
# when neither. With a block: open a new, independent batch through
|
|
41
|
+
# Pgbus.batch (this job's own membership is untouched).
|
|
42
|
+
#
|
|
43
|
+
# batch(description: "Import") { |b| b.add(*rows.map { |r| RowJob.new(r.id) }) }
|
|
44
|
+
def batch(**options, &block)
|
|
45
|
+
return Pgbus.batch(**options, &block) if block
|
|
46
|
+
raise ArgumentError, "batch(...) with options needs a block: batch(description: ...) { |b| ... }" if options.any?
|
|
41
47
|
return @batch if defined?(@batch)
|
|
42
48
|
|
|
43
49
|
id = callback_batch_id || batch_id
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pgbus
|
|
4
|
+
class Batch
|
|
5
|
+
# What Pgbus.batch (and +batch { |b| ... }+ inside a job) yields: callback
|
|
6
|
+
# setters and +add+ on a batch that is open for the length of the block.
|
|
7
|
+
#
|
|
8
|
+
# Pgbus.batch(description: "Import #{source.id}") do |b|
|
|
9
|
+
# b.on_complete ImportDoneJob.new.set(queue: :critical)
|
|
10
|
+
# b.on_failure ImportFailedJob
|
|
11
|
+
# b.add(*source.rows.map { |row| ImportRowJob.new(row.id) })
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# The batch row exists (and a uniqueness key is held) before the block
|
|
15
|
+
# runs, and it stays pending until the block returns, so the order of the
|
|
16
|
+
# calls does not matter and a job that completes during the block cannot
|
|
17
|
+
# finish the batch early. Once the block has returned the builder is
|
|
18
|
+
# closed: add more jobs through +batch.add+ on the returned batch.
|
|
19
|
+
class Builder
|
|
20
|
+
CLOSED_MESSAGE = "the batch builder only works inside its Pgbus.batch block; " \
|
|
21
|
+
"add more jobs later with batch.add on the batch Pgbus.batch returned"
|
|
22
|
+
UNTAGGED_MESSAGE = "b.add ran on a thread or fiber outside its Pgbus.batch block (or inside another " \
|
|
23
|
+
"batch's block), where its jobs would not join the batch; call b.add from the block itself"
|
|
24
|
+
|
|
25
|
+
# Flatten +jobs+ and check that each one is an ActiveJob instance.
|
|
26
|
+
def self.job_instances(jobs)
|
|
27
|
+
jobs = jobs.flatten
|
|
28
|
+
invalid = jobs.grep_v(::ActiveJob::Base)
|
|
29
|
+
return jobs if invalid.empty?
|
|
30
|
+
|
|
31
|
+
raise ArgumentError,
|
|
32
|
+
"a batch takes job instances (MyJob.new(args), optionally .set(wait:, queue:)), got #{invalid.first.inspect}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(batch)
|
|
36
|
+
@batch = batch
|
|
37
|
+
@closed = false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def on_complete(job = nil, &) = set_callback(:on_complete, job, &)
|
|
41
|
+
|
|
42
|
+
def on_success(job = nil, &) = set_callback(:on_success, job, &)
|
|
43
|
+
|
|
44
|
+
def on_failure(job = nil, &) = set_callback(:on_failure, job, &)
|
|
45
|
+
|
|
46
|
+
# Deprecated alias of #on_complete until 1.0.
|
|
47
|
+
def on_finish(job = nil, &)
|
|
48
|
+
Batch.warn_on_finish_deprecated
|
|
49
|
+
set_callback(:on_complete, job, &)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Enqueue job instances into the batch with one bulk send (and anything
|
|
53
|
+
# the block enqueues). Returns the jobs. Membership is the batch tag the
|
|
54
|
+
# block holds (Thread.current, so fiber-local), so an add from another
|
|
55
|
+
# thread or fiber, or from inside a nested batch's block, raises rather
|
|
56
|
+
# than send jobs that would not be counted.
|
|
57
|
+
def add(*jobs)
|
|
58
|
+
raise ArgumentError, CLOSED_MESSAGE if @closed
|
|
59
|
+
raise ArgumentError, UNTAGGED_MESSAGE unless Thread.current[:pgbus_batch_id] == @batch.batch_id
|
|
60
|
+
|
|
61
|
+
jobs = self.class.job_instances(jobs)
|
|
62
|
+
::ActiveJob.perform_all_later(jobs) if jobs.any?
|
|
63
|
+
yield if block_given?
|
|
64
|
+
jobs
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Called by Batch.start when the block returns or raises.
|
|
68
|
+
def close
|
|
69
|
+
@closed = true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def set_callback(slot, job, &block)
|
|
75
|
+
raise ArgumentError, CLOSED_MESSAGE if @closed
|
|
76
|
+
|
|
77
|
+
unless block.nil? && callback_job?(job)
|
|
78
|
+
raise ArgumentError,
|
|
79
|
+
"#{slot} takes an ActiveJob class or a configured instance (MyJob.new.set(queue: :critical)), " \
|
|
80
|
+
"not #{block ? "a block" : job.inspect}: callbacks are stored on the batch row"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
@batch.send(:write_callback, slot, job)
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def callback_job?(job)
|
|
88
|
+
(job.is_a?(Class) && job < ::ActiveJob::Base) || job.is_a?(::ActiveJob::Base)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|