Solid Queue é um back-end de enfileiramento baseado em banco de dados para Active Job, projetado com simplicidade e desempenho em mente.
Além do enfileiramento e processamento regular de trabalhos, o Solid Queue oferece suporte a trabalhos atrasados, controles de simultaneidade, trabalhos recorrentes, filas de pausa, prioridades numéricas por trabalho, prioridades por ordem de fila e enfileiramento em massa ( enqueue_all
para perform_all_later
do trabalho ativo).
Solid Queue pode ser usado com bancos de dados SQL, como MySQL, PostgreSQL ou SQLite, e aproveita a cláusula FOR UPDATE SKIP LOCKED
, se disponível, para evitar bloqueio e espera de bloqueios ao pesquisar trabalhos. Ele depende do Active Job para novas tentativas, descarte, tratamento de erros, serialização ou atrasos e é compatível com o multithreading do Ruby on Rails.
Solid Queue é configurado por padrão nas novas aplicações Rails 8. Mas se estiver executando uma versão anterior, você pode adicioná-la manualmente seguindo estas etapas:
bundle add solid_queue
bin/rails solid_queue:install
Isso configurará o Solid Queue como o back-end do Active Job de produção, criará os arquivos de configuração config/queue.yml
e config/recurring.yml
e criará o db/queue_schema.rb
. Ele também criará um wrapper executável bin/jobs
que você pode usar para iniciar o Solid Queue.
Depois de fazer isso, você terá que adicionar a configuração do banco de dados de filas em config/database.yml
. Se você estiver usando SQLite, ficará assim:
production :
primary :
<< : *default
database : storage/production.sqlite3
queue :
<< : *default
database : storage/production_queue.sqlite3
migrations_paths : db/queue_migrate
...ou se você estiver usando MySQL/PostgreSQL/Trilogy:
production :
primary : &primary_production
<< : *default
database : app_production
username : app
password : <%= ENV["APP_DATABASE_PASSWORD"] %>
queue :
<< : *primary_production
database : app_production_queue
migrations_paths : db/queue_migrate
Nota: Chamar bin/rails solid_queue:install
adicionará automaticamente config.solid_queue.connects_to = { database: { writing: :queue } }
a config/environments/production.rb
, portanto, nenhuma configuração adicional é necessária lá (embora você deva ter certeza que você use o nome queue
em database.yml
para que isso corresponda!). Mas se você quiser usar o Solid Queue em um ambiente diferente (como teste ou mesmo desenvolvimento), você terá que adicionar manualmente essa linha config.solid_queue.connects_to
ao respectivo arquivo de ambiente. E, como sempre, certifique-se de que o nome usado para o banco de dados em config/database.yml
corresponda ao nome usado em config.solid_queue.connects_to
.
Em seguida, execute db:prepare
em produção para garantir que o banco de dados seja criado e o esquema carregado.
Agora você está pronto para começar a processar trabalhos executando bin/jobs
no servidor que está fazendo o trabalho. Isso iniciará o processamento de trabalhos em todas as filas usando a configuração padrão. Veja abaixo para saber mais sobre como configurar o Solid Queue.
Para projetos pequenos, você pode executar o Solid Queue na mesma máquina do seu servidor web. Quando você estiver pronto para dimensionar, o Solid Queue oferece suporte ao dimensionamento horizontal pronto para uso. Você pode executar o Solid Queue em um servidor separado do seu servidor web ou até mesmo executar bin/jobs
em várias máquinas ao mesmo tempo. Dependendo da configuração, você pode designar algumas máquinas para executar apenas despachantes ou apenas trabalhadores. Consulte a seção de configuração para obter mais detalhes sobre isso.
Observação : alterações futuras no esquema virão na forma de migrações regulares.
É recomendado executar o Solid Queue em um banco de dados separado, mas também é possível usar um único banco de dados para o aplicativo e a fila. Basta seguir estas etapas:
db/queue_schema.rb
em uma migração normal e exclua db/queue_schema.rb
config.solid_queue.connects_to
de production.rb
bin/jobs
Você não terá vários bancos de dados, portanto, database.yml
não precisa ter banco de dados primário e de fila.
Se você está planejando adotar o Solid Queue de forma incremental alternando um trabalho por vez, você pode fazer isso deixando o config.active_job.queue_adapter
definido para seu back-end antigo e, em seguida, definir o queue_adapter
diretamente nos trabalhos que você está movendo:
# app/jobs/my_job.rb
class MyJob < ApplicationJob
self . queue_adapter = :solid_queue
# ...
end
Solid Queue foi projetado para o maior rendimento quando usado com MySQL 8+ ou PostgreSQL 9.5+, pois eles suportam FOR UPDATE SKIP LOCKED
. Você pode usá-lo com versões mais antigas, mas, nesse caso, poderá enfrentar esperas de bloqueio se executar vários trabalhos para a mesma fila. Você também pode usá-lo com SQLite em aplicativos menores.
Temos vários tipos de atores no Solid Queue:
solid_queue_ready_executions
.solid_queue_scheduled_executions
para a tabela solid_queue_ready_executions
para que os trabalhadores possam retirá-los. Além disso, eles fazem alguns trabalhos de manutenção relacionados aos controles de simultaneidade.O supervisor do Solid Queue criará um processo separado para cada trabalhador/despachante/agendador supervisionado.
Por padrão, Solid Queue tentará encontrar sua configuração em config/queue.yml
, mas você pode definir um caminho diferente usando a variável de ambiente SOLID_QUEUE_CONFIG
ou usando a opção -c/--config_file
com bin/jobs
, assim:
bin/jobs -c config/calendar.yml
Esta é a aparência desta configuração:
production :
dispatchers :
- polling_interval : 1
batch_size : 500
concurrency_maintenance_interval : 300
workers :
- queues : " * "
threads : 3
polling_interval : 2
- queues : [ real_time, background ]
threads : 5
polling_interval : 0.1
processes : 3
Tudo é opcional. Se nenhuma configuração for fornecida, o Solid Queue será executado com um despachante e um trabalhador com configurações padrão. Se você deseja executar apenas despachantes ou trabalhadores, basta incluir apenas essa seção na configuração. Por exemplo, com a seguinte configuração:
production :
dispatchers :
- polling_interval : 1
batch_size : 500
concurrency_maintenance_interval : 300
o supervisor executará 1 despachante e nenhum trabalhador.
Aqui está uma visão geral das diferentes opções:
polling_interval
: o intervalo de tempo em segundos que os trabalhadores e despachantes aguardarão antes de verificar mais trabalhos. Esse tempo é padronizado como 1
segundo para despachantes e 0.1
segundos para trabalhadores.
batch_size
: o despachante despachará os trabalhos em lotes deste tamanho. O padrão é 500.
concurrency_maintenance_interval
: o intervalo de tempo em segundos que o despachante aguardará antes de verificar se há jobs bloqueados que podem ser desbloqueados. Leia mais sobre controles de simultaneidade para saber mais sobre essa configuração. O padrão é 600
segundos.
queues
: a lista de filas das quais os trabalhadores escolherão os trabalhos. Você pode usar *
para indicar todas as filas (que também é o padrão e o comportamento que você obterá se omitir isso). Você pode fornecer uma única fila ou uma lista de filas como uma matriz. Os trabalhos serão pesquisados nessas filas em ordem, então, por exemplo, com [ real_time, background ]
, nenhum trabalho será retirado do background
a menos que não haja mais trabalhos aguardando em real_time
. Você também pode fornecer um prefixo com um curinga para corresponder às filas que começam com um prefixo. Por exemplo:
staging :
workers :
- queues : staging*
threads : 3
polling_interval : 5
Isso criará um trabalhador que buscará empregos em todas as filas, começando com staging
. O curinga *
só é permitido sozinho ou no final do nome de uma fila; você não pode especificar nomes de filas como *_some_queue
. Estes serão ignorados.
Finalmente, você pode combinar prefixos com nomes exatos, como [ staging*, background ]
, e o comportamento em relação à ordem será o mesmo que apenas com nomes exatos.
Verifique as seções abaixo sobre como a ordem das filas se comporta combinada com as prioridades e como a maneira como você especifica as filas por trabalhador pode afetar o desempenho.
threads
: este é o tamanho máximo do pool de threads que cada trabalhador terá para executar trabalhos. Cada trabalhador irá buscar esse número de trabalhos de sua(s) fila(s), no máximo, e irá publicá-los no pool de threads para serem executados. Por padrão, é 3
. Somente os trabalhadores têm essa configuração.
processes
: este é o número de processos de trabalho que serão bifurcados pelo supervisor com as configurações fornecidas. Por padrão, é 1
, apenas um único processo. Esta configuração é útil se você deseja dedicar mais de um núcleo de CPU a uma fila ou filas com a mesma configuração. Somente os trabalhadores têm essa configuração.
concurrency_maintenance
: se o despachante realizará o trabalho de manutenção de simultaneidade. Isso é true
por padrão e é útil se você não usa nenhum controle de simultaneidade e deseja desativá-lo ou se você executa vários despachantes e deseja que alguns deles apenas despachem trabalhos sem fazer mais nada.
Como mencionado acima, se você especificar uma lista de filas para um trabalhador, elas serão pesquisadas na ordem indicada, como para a lista real_time,background
, nenhum trabalho será retirado do background
a menos que não haja mais trabalhos aguardando em real_time
.
O Active Job também oferece suporte a prioridades de números inteiros positivos ao enfileirar trabalhos. No Solid Queue, quanto menor o valor, maior a prioridade. O padrão é 0
.
Isso é útil quando você executa trabalhos com importância ou urgência diferente na mesma fila. Dentro da mesma fila, os trabalhos serão selecionados em ordem de prioridade, mas em uma lista de filas, a ordem da fila tem precedência, portanto, no exemplo anterior com real_time,background
, os trabalhos na fila real_time
serão selecionados antes dos trabalhos em background
fila, mesmo que aqueles na fila background
tenham uma prioridade mais alta (valor menor) definida.
Recomendamos não misturar a ordem da fila com as prioridades, mas escolher uma ou outra, pois isso tornará a ordem de execução do trabalho mais simples para você.
Para manter o desempenho da pesquisa e garantir que um índice de cobertura seja sempre usado, o Solid Queue faz apenas dois tipos de consultas de pesquisa:
-- No filtering by queue
SELECT job_id
FROM solid_queue_ready_executions
ORDER BY priority ASC , job_id ASC
LIMIT ?
FOR UPDATE SKIP LOCKED;
-- Filtering by a single queue
SELECT job_id
FROM solid_queue_ready_executions
WHERE queue_name = ?
ORDER BY priority ASC , job_id ASC
LIMIT ?
FOR UPDATE SKIP LOCKED;
O primeiro (sem filtragem por fila) é usado quando você especifica
queues : *
e não há filas pausadas, pois queremos atingir todas as filas.
Em outros casos, precisamos ter uma lista de filas para filtrar, em ordem, porque só podemos filtrar por uma única fila por vez para garantir que usaremos um índice para classificar. Isso significa que se você especificar suas filas como:
queues : beta*
precisaremos primeiro obter uma lista de todas as filas existentes que correspondem a esse prefixo, com uma consulta semelhante a esta:
SELECT DISTINCT (queue_name)
FROM solid_queue_ready_executions
WHERE queue_name LIKE ' beta% ' ;
Este tipo de consulta DISTINCT
em uma coluna que é a coluna mais à esquerda em um índice pode ser executada muito rapidamente no MySQL graças a uma técnica chamada Loose Index Scan. PostgreSQL e SQLite, entretanto, não implementam esta técnica, o que significa que se sua tabela solid_queue_ready_executions
for muito grande porque suas filas são muito profundas, esta consulta ficará lenta. Normalmente sua tabela solid_queue_ready_executions
será pequena, mas pode acontecer.
Da mesma forma que o uso de prefixos, o mesmo acontecerá se você tiver filas pausadas, pois precisamos obter uma lista de todas as filas com uma consulta como
SELECT DISTINCT (queue_name)
FROM solid_queue_ready_executions
e, em seguida, remova os pausados. A pausa em geral deve ser algo raro, usado em circunstâncias especiais e por um curto período de tempo. Se você não quiser mais processar trabalhos de uma fila, a melhor maneira de fazer isso é removê-lo da sua lista de filas.
Resumindo, se você deseja garantir o desempenho ideal nas pesquisas , a melhor maneira de fazer isso é sempre especificar nomes exatos para eles e não ter nenhuma fila pausada.
Faça isso:
queues : background, backend
em vez disso:
queues : back*
Os trabalhadores no Solid Queue usam um pool de threads para executar trabalho em vários threads, configurável por meio do parâmetro threads
acima. Além disso, o paralelismo pode ser alcançado através de múltiplos processos em uma máquina (configurável através de diferentes trabalhadores ou do parâmetro processes
acima) ou por escala horizontal.
O supervisor é responsável pela gestão destes processos e responde aos seguintes sinais:
TERM
, INT
: inicia o encerramento normal. O supervisor enviará um sinal TERM
para seus processos supervisionados e aguardará o tempo SolidQueue.shutdown_timeout
até que eles sejam concluídos. Se algum processo supervisionado ainda estiver disponível, ele enviará um sinal QUIT
para indicar que deve sair.QUIT
: inicia o encerramento imediato. O supervisor enviará um sinal QUIT
aos seus processos supervisionados, fazendo com que eles saiam imediatamente. Ao receber um sinal QUIT
, se os trabalhadores ainda tiverem trabalhos em andamento, estes serão devolvidos à fila quando os processos forem cancelados.
Se os processos não tiverem chance de serem limpos antes de saírem (por exemplo, se alguém puxar um cabo em algum lugar), os trabalhos em andamento poderão permanecer reivindicados pelos processos que os executam. Os processos enviam pulsações e o supervisor verifica e remove processos com pulsações expiradas, o que liberará quaisquer trabalhos reivindicados de volta às suas filas. Você pode configurar a frequência das pulsações e o limite para considerar um processo inativo. Veja a seção abaixo para isso.
Você pode configurar o banco de dados usado pelo Solid Queue por meio da opção config.solid_queue.connects_to
nos arquivos de configuração config/application.rb
ou config/environments/production.rb
. Por padrão, um único banco de dados é usado para gravação e leitura queue
chamada para corresponder à configuração do banco de dados definida durante a instalação.
Todas as opções disponíveis do Active Record para múltiplos bancos de dados podem ser usadas aqui.
Na fila Solid, você pode se conectar a dois pontos diferentes da vida do supervisor:
start
: depois que o supervisor terminar a inicialização e logo antes de bifurcar os trabalhadores e despachantes.stop
: após receber um sinal ( TERM
, INT
ou QUIT
) e logo antes de iniciar o desligamento normal ou imediato.E em dois pontos diferentes da vida de um trabalhador:
worker_start
: depois que o trabalhador terminar a inicialização e logo antes de iniciar o loop de pesquisa.worker_stop
: após receber um sinal ( TERM
, INT
ou QUIT
) e logo antes de iniciar o desligamento normal ou imediato (que é apenas exit!
).Você pode usar os seguintes métodos com um bloco para fazer isso:
SolidQueue . on_start
SolidQueue . on_stop
SolidQueue . on_worker_start
SolidQueue . on_worker_stop
Por exemplo:
SolidQueue . on_start { start_metrics_server }
SolidQueue . on_stop { stop_metrics_server }
Eles podem ser chamados várias vezes para adicionar vários ganchos, mas isso precisa acontecer antes que o Solid Queue seja iniciado. Um inicializador seria um bom lugar para fazer isso.
Nota : As configurações nesta seção devem ser definidas em seu config/application.rb
ou na configuração do seu ambiente assim: config.solid_queue.silence_polling = true
Existem várias configurações que controlam o funcionamento do Solid Queue e que você também pode definir:
logger
: o logger que você deseja que o Solid Queue use. O padrão é o registrador de aplicativos.
app_executor
: o executor Rails usado para agrupar operações assíncronas, o padrão é o executor do aplicativo
on_thread_error
: lambda/Proc personalizado para chamar quando há um erro em um thread Solid Queue que recebe a exceção levantada como argumento. O padrão é
-> ( exception ) { Rails . error . report ( exception , handled : false ) }
Isso não é usado para erros gerados na execução de um trabalho . Erros que acontecem em trabalhos são tratados por retry_on
ou discard_on
do trabalho ativo e, em última análise, resultarão em trabalhos com falha. Isso se aplica a erros que ocorrem no próprio Solid Queue.
use_skip_locked
: se deve usar FOR UPDATE SKIP LOCKED
ao realizar leituras de bloqueio. Isso será detectado automaticamente no futuro e, por enquanto, você só precisará definir isso como false
se o seu banco de dados não oferecer suporte. Para MySQL, seriam versões <8, e para PostgreSQL, versões <9.5. Se você usar SQLite, isso não terá efeito, pois as gravações são sequenciais.
process_heartbeat_interval
: o intervalo de pulsação que todos os processos seguirão – o padrão é 60 segundos.
process_alive_threshold
: quanto tempo esperar até que um processo seja considerado morto após sua última pulsação – o padrão é 5 minutos.
shutdown_timeout
: tempo que o supervisor aguardará desde que enviou o sinal TERM
para seus processos supervisionados antes de enviar uma versão QUIT
para eles solicitando encerramento imediato – o padrão é 5 segundos.
silence_polling
: se deve silenciar os logs do Active Record emitidos durante a pesquisa de trabalhadores e despachantes — o padrão é true
.
supervisor_pidfile
: caminho para um pidfile que o supervisor criará ao inicializar para evitar a execução de mais de um supervisor no mesmo host, ou caso você queira usá-lo para uma verificação de integridade. É nil
por padrão.
preserve_finished_jobs
: se os trabalhos concluídos devem ser mantidos na tabela solid_queue_jobs
— o padrão é true
.
clear_finished_jobs_after
: período para manter os trabalhos concluídos, caso preserve_finished_jobs
seja verdadeiro - o padrão é 1 dia. Observação: no momento, não há limpeza automática de trabalhos concluídos. Você precisaria fazer isso invocando periodicamente SolidQueue::Job.clear_finished_in_batches
, mas isso acontecerá automaticamente em um futuro próximo.
default_concurrency_control_period
: o valor a ser usado como padrão para o parâmetro duration
nos controles de simultaneidade. O padrão é 3 minutos.
Solid Queue gerará um SolidQueue::Job::EnqueueError
para quaisquer erros de Active Record que ocorram ao enfileirar um trabalho. A razão para não gerar ActiveJob::EnqueueError
é que este é tratado pelo Active Job, fazendo com que perform_later
retorne false
e defina job.enqueue_error
, entregando o trabalho a um bloco que você precisa passar para perform_later
. Isso funciona muito bem para seus próprios trabalhos, mas torna muito difícil lidar com falhas em trabalhos enfileirados por Rails ou outras gems, como Turbo::Streams::BroadcastJob
ou ActiveStorage::AnalyzeJob
, porque você não controla a chamada para perform_later
nesses casos.
No caso de tarefas recorrentes, se tal erro for gerado ao enfileirar o trabalho correspondente à tarefa, ele será tratado e registrado, mas não aparecerá.
Solid Queue estende Active Job com controles de simultaneidade, que permitem limitar quantos jobs de um determinado tipo ou com determinados argumentos podem ser executados ao mesmo tempo. Quando limitados dessa forma, a execução dos trabalhos será bloqueada e permanecerão bloqueadas até que outro trabalho seja concluído e desbloqueado, ou após o tempo de expiração definido ( duração do limite de simultaneidade) ter decorrido. Os trabalhos nunca são descartados ou perdidos, apenas bloqueados.
class MyJob < ApplicationJob
limits_concurrency to : max_concurrent_executions , key : -> ( arg1 , arg2 , ** ) { ... } , duration : max_interval_to_guarantee_concurrency_limit , group : concurrency_group
# ...
key
é o único parâmetro obrigatório, podendo ser um símbolo, uma string ou um proc que recebe os argumentos do job como parâmetros e será utilizado para identificar os jobs que precisam ser limitados juntos. Se o proc retornar um registro Active Record, a chave será construída a partir de seu nome de classe e id
.to
é 1
por padrão.duration
é definida como SolidQueue.default_concurrency_control_period
por padrão, cujo padrão é 3 minutes
, mas que você também pode configurar.group
é usado para controlar a simultaneidade de diferentes classes de trabalho juntas. O padrão é o nome da classe de trabalho. Quando um trabalho inclui esses controles, garantiremos que, no máximo, o número de trabalhos (indicados to
) que geram a mesma key
será executado simultaneamente, e essa garantia durará a duration
de cada trabalho enfileirado. Observe que não há garantia sobre a ordem de execução , apenas sobre os trabalhos executados ao mesmo tempo (sobreposição).
Os limites de simultaneidade usam o conceito de semáforos ao enfileirar e funcionam da seguinte forma: quando um trabalho é enfileirado, verificamos se ele especifica controles de simultaneidade. Se isso acontecer, verificamos o semáforo em busca da chave de simultaneidade computada. Se o semáforo estiver aberto, nós o reivindicamos e configuramos o trabalho como ready . Pronto significa que pode ser recolhido pelos trabalhadores para execução. Quando o trabalho termina de ser executado (seja com ou sem sucesso, resultando em falha na execução), sinalizamos o semáforo e tentamos desbloquear o próximo trabalho com a mesma chave, se houver. Desbloquear o próximo trabalho não significa executá-lo imediatamente, mas sim movê-lo de bloqueado para pronto . Como pode acontecer algo que impeça o primeiro job de liberar o semáforo e desbloquear o próximo job (por exemplo, alguém desligando a máquina onde o trabalhador está rodando), temos a duration
como à prova de falhas. Os trabalhos bloqueados por mais tempo são candidatos a serem liberados, mas apenas o número permitido pelas regras de simultaneidade, pois cada um precisaria passar pela verificação da dança do semáforo. Isso significa que a duration
não se trata realmente do trabalho que está na fila ou em execução, mas sim dos trabalhos que estão bloqueados aguardando.
Por exemplo:
class DeliverAnnouncementToContactJob < ApplicationJob
limits_concurrency to : 2 , key : -> ( contact ) { contact . account } , duration : 5 . minutes
def perform ( contact )
# ...
Onde contact
e account
são registros ActiveRecord
. Nesse caso, garantiremos que no máximo dois trabalhos do tipo DeliverAnnouncementToContact
para a mesma conta serão executados simultaneamente. Se, por qualquer motivo, um desses trabalhos demorar mais de 5 minutos ou não liberar seu bloqueio de simultaneidade (sinalizar o semáforo) dentro de 5 minutos após adquiri-lo, um novo trabalho com a mesma chave poderá obter o bloqueio.
Vamos ver outro exemplo usando group
:
class Box :: MovePostingsByContactToDesignatedBoxJob < ApplicationJob
limits_concurrency key : -> ( contact ) { contact } , duration : 15 . minutes , group : "ContactActions"
def perform ( contact )
# ...
class Bundle :: RebundlePostingsJob < ApplicationJob
limits_concurrency key : -> ( bundle ) { bundle . contact } , duration : 15 . minutes , group : "ContactActions"
def perform ( bundle )
# ...
Nesse caso, se tivermos um trabalho Box::MovePostingsByContactToDesignatedBoxJob
enfileirado para um registro de contato com id 123
e outro Bundle::RebundlePostingsJob
enfileirado simultaneamente para um registro de pacote que faz referência ao contato 123
, apenas um deles poderá prosseguir. O outro ficará bloqueado até que o primeiro termine (ou passem 15 minutos, o que acontecer primeiro).
Observe que a configuração duration
depende indiretamente do valor de concurrency_maintenance_interval
que você definiu para seu(s) despachante(s), pois essa seria a frequência com que os trabalhos bloqueados são verificados e desbloqueados. Em geral, você deve definir duration
de forma que todos os seus trabalhos terminem bem abaixo dessa duração e pensar na tarefa de manutenção de simultaneidade como uma solução à prova de falhas caso algo dê errado.
Os trabalhos são desbloqueados por ordem de prioridade, mas a ordem da fila não é levada em consideração para o desbloqueio dos trabalhos. Isso significa que se você tiver um grupo de trabalhos que compartilham um grupo de simultaneidade, mas estão em filas diferentes, ou trabalhos da mesma classe que você enfileira em filas diferentes, a ordem da fila definida para um trabalhador não será levada em consideração ao desbloquear bloqueado uns. A razão é que um trabalho executado desbloqueia o próximo, e o trabalho em si não sabe sobre a ordem da fila de um trabalhador específico (você pode até ter trabalhadores diferentes com ordens de fila diferentes), ele só pode saber sobre a prioridade. Depois que os trabalhos bloqueados forem desbloqueados e estiverem disponíveis para pesquisa, eles serão coletados por um trabalhador seguindo a ordem da fila.
Por fim, os trabalhos com falha que são repetidos automática ou manualmente funcionam da mesma maneira que os novos trabalhos que são enfileirados: eles entram na fila para obter um semáforo aberto e, sempre que o conseguirem, serão executados. Não importa se eles já obtiveram um semáforo aberto no passado.
Solid Queue não inclui nenhum mecanismo de nova tentativa automática, ele depende do Active Job para isso. Os trabalhos que falharem serão mantidos no sistema e uma execução com falha (um registro na tabela solid_queue_failed_executions
) será criada para eles. O trabalho permanecerá lá até ser descartado manualmente ou recolocado na fila. Você pode fazer isso em um console como:
failed_execution = SolidQueue :: FailedExecution . find ( ... ) # Find the failed execution related to your job
failed_execution . error # inspect the error
failed_execution . retry # This will re-enqueue the job as if it was enqueued for the first time
failed_execution . discard # This will delete the job from the system
No entanto, recomendamos dar uma olhada em mission_control-jobs, um painel onde, entre outras coisas, você pode examinar e tentar novamente/descartar trabalhos com falha.
Alguns serviços de rastreamento de erros que se integram ao Rails, como Sentry ou Rollbar, conectam-se ao Active Job e relatam automaticamente erros não tratados que acontecem durante a execução do trabalho. No entanto, se o seu sistema de rastreamento de erros não funcionar, ou se você precisar de algum relatório personalizado, você mesmo poderá conectar-se ao Active Job. Uma possível maneira de fazer isso seria:
# application_job.rb
class ApplicationJob < ActiveJob :: Base
rescue_from ( Exception ) do | exception |
Rails . error . report ( exception )
raise exception
end
end
Observe que você também terá que duplicar a lógica acima em ActionMailer::MailDeliveryJob
. Isso ocorre porque ActionMailer
não herda de ApplicationJob
, mas usa ActionMailer::MailDeliveryJob
que herda de ActiveJob::Base
.
# application_mailer.rb
class ApplicationMailer < ActionMailer :: Base
ActionMailer :: MailDeliveryJob . rescue_from ( Exception ) do | exception |
Rails . error . report ( exception )
raise exception
end
end
Fornecemos um plugin Puma se você deseja executar o supervisor do Solid Queue junto com o Puma e fazer com que o Puma o monitore e gerencie. Você só precisa adicionar
plugin :solid_queue
à configuração do puma.rb
Como isso pode ser bastante complicado e muitas pessoas não precisam se preocupar com isso, por padrão, o Solid Queue é configurado em um banco de dados diferente do aplicativo principal, o enfileiramento de tarefas é adiado até que qualquer transação em andamento seja confirmada, graças ao recurso integrado do Active Job. capacidade para fazer isso. Isso significa que mesmo se você executar o Solid Queue no mesmo banco de dados do seu aplicativo, você não aproveitará essa integridade transacional.
Se preferir alterar isso, você pode definir config.active_job.enqueue_after_transaction_commit
como never
. Você também pode definir isso por trabalho.
Se você definir isso como never
, mas ainda quiser ter certeza de que não está inadvertidamente na integridade transacional, poderá ter certeza de que:
Seus trabalhos que dependem de dados específicos são sempre enfileirados em retornos de chamada after_commit
ou de outro modo, de um local onde você tem certeza de que quaisquer dados que o trabalho usará foram confirmados no banco de dados antes de o trabalho ser enfileirado.
Ou você configura um banco de dados diferente para Solid Queue, mesmo que seja o mesmo do seu aplicativo, garantindo que uma conexão diferente no thread que manipula solicitações ou executa trabalhos para seu aplicativo será usada para enfileirar trabalhos. Por exemplo:
class ApplicationRecord < ActiveRecord :: Base
self . abstract_class = true
connects_to database : { writing : :primary , reading : :replica }
config . solid_queue . connects_to = { database : { writing : :primary , reading : :replica } }
Solid Queue oferece suporte à definição de tarefas recorrentes que serão executadas em horários específicos no futuro, regularmente, como tarefas cron. Eles são gerenciados pelo processo do agendador e definidos em seu próprio arquivo de configuração. Por padrão, o arquivo está localizado em config/recurring.yml
, mas você pode definir um caminho diferente usando a variável de ambiente SOLID_QUEUE_RECURRING_SCHEDULE
ou usando a opção --recurring_schedule_file
com bin/jobs
, assim:
bin/jobs --recurring_schedule_file=config/schedule.yml
A configuração em si é assim:
production :
a_periodic_job :
class : MyJob
args : [ 42, { status: "custom_status" } ]
schedule : every second
a_cleanup_task :
command : " DeletedStuff.clear_all "
schedule : every day at 9am
As tarefas são especificadas como um hash/dicionário, onde a chave será a chave da tarefa internamente. Cada tarefa precisa ter uma class
, que será a classe de trabalho a ser enfileirada, ou um command
, que será avaliado no contexto de um trabalho ( SolidQueue::RecurringJob
) que será enfileirado de acordo com seu agendamento, em a fila solid_queue_recurring
.
Cada tarefa precisa ter também um cronograma, que é analisado usando o Fugit, para que aceite qualquer coisa que o Fugit aceite como cron. Opcionalmente, você pode fornecer o seguinte para cada tarefa:
args
: os argumentos a serem passados para o trabalho, como um único argumento, um hash ou uma matriz de argumentos que também pode incluir kwargs como o último elemento da matriz.O trabalho na configuração de exemplo acima será enfileirado a cada segundo como:
MyJob . perform_later ( 42 , status : "custom_status" )
queue
: uma fila diferente a ser usada ao enfileirar o trabalho. Se não houver, a fila configurada para a classe de trabalho.
priority
: um valor numérico de prioridade a ser usado ao enfileirar o trabalho.
As tarefas são enfileiradas nos horários correspondentes pelo agendador e cada tarefa agenda a próxima. Isso é bastante inspirado no que GoodJob faz.
É possível executar vários agendadores com a mesma configuração recurring_tasks
, por exemplo, se você tiver vários servidores para redundância e executar o scheduler
em mais de um deles. Para evitar enfileirar tarefas duplicadas ao mesmo tempo, uma entrada em uma nova tabela solid_queue_recurring_executions
é criada na mesma transação em que a tarefa é enfileirada. Esta tabela possui um índice exclusivo em task_key
e run_at
, garantindo que apenas uma entrada por tarefa por vez será criada. Isso só funciona se você tiver preserve_finished_jobs
definido como true
(o padrão), e a garantia se aplica desde que você mantenha os trabalhos por perto.
Observação : há suporte para um único agendamento recorrente, portanto você pode ter vários agendadores usando o mesmo agendamento, mas não vários agendadores usando configurações diferentes.
Finalmente, é possível configurar trabalhos que não são tratados pelo Solid Queue. Ou seja, você pode ter um trabalho como este no seu app:
class MyResqueJob < ApplicationJob
self . queue_adapter = :resque
def perform ( arg )
# ..
end
end
Você ainda pode configurar isso no Solid Queue:
my_periodic_resque_job :
class : MyResqueJob
args : 22
schedule : " */5 * * * * "
e o trabalho será enfileirado via perform_later
para ser executado no Resque. No entanto, neste caso, não rastrearemos nenhum registro solid_queue_recurring_execution
para ele e não haverá nenhuma garantia de que o trabalho seja enfileirado apenas uma vez por vez.
Solid Queue foi inspirado em resque e GoodJob. Recomendamos conferir esses projetos, pois são ótimos exemplos com os quais aprendemos muito.
A gema está disponível como código aberto sob os termos da licença MIT.