CTO no Integrar Plus • Publicado em 14 de September de 2026
Durante mais de uma década, iniciar qualquer aplicação Ruby on Rails com processamento em segundo plano exigiu quase que automaticamente a introdução do Redis e do Sidekiq. O ecossistema se acostumou tanto com esse arranjo que poucos questionavam a necessidade real de manter um serviço em memória separado apenas para gerenciar filas de tarefas.
Quando operamos servidores próprios em VPS, cada processo adicional no sistema operacional impõe um custo contínuo de manutenção. O Redis é rápido e confiável, mas introduz um ponto de falha independente, exige monitoramento de consumo de memória RAM, configuração criteriosa de persistência em disco (RDB versus AOF) e rotinas próprias de backup.
Na maioria das aplicações web, o gargalo dos jobs assíncronos não é a taxa de transferência da fila, mas sim o que o job faz: enviar um e-mail, processar um webhook externo, gerar um relatório ou converter um arquivo. Manter um banco de dados em memória para enfileirar algumas centenas ou milhares de tarefas por minuto quase sempre representa complexidade desnecessária.
Com o Solid Queue integrado ao Rails, o PostgreSQL assume essa responsabilidade sem a necessidade de componentes intermediários.
O principal motivo histórico para evitar filas em bancos relacionais era a contenção de concorrência. Quando múltiplos workers tentavam buscar a próxima tarefa disponível em uma tabela convencional, ocorriam bloqueios de linha ou de tabela, gerando deadlocks e lentidão generalizada.
O PostgreSQL resolveu essa limitação com a cláusula FOR UPDATE SKIP LOCKED. Essa funcionalidade permite que uma transação bloqueie apenas as linhas selecionadas e ignore imediatamente qualquer linha que já esteja bloqueada por outra transação ativa.
Em termos práticos, a consulta executada pelo worker para obter trabalho opera com a seguinte lógica:
SELECT id
FROM solid_queue_ready_executions
ORDER BY priority ASC, job_id ASC
LIMIT 1
FOR UPDATE SKIP LOCKED;
Se dez workers executarem essa consulta simultaneamente, nenhum deles ficará esperando a liberação do lock de outro worker. Cada um captura a próxima tarefa livre e segue a execução de forma completamente isolada. A operação é atômica, segura e elimina qualquer disputa que degrade o throughput do banco.
A maior vantagem técnica de colocar a fila dentro do PostgreSQL não é a economia de RAM, mas a consistência transacional.
Quando o Redis armazena a fila, a gravação dos dados da aplicação e o enfileiramento do job ocorrem em sistemas distintos. Isso gera uma clássica condição de corrida:
## Cenário problemático com filas externas
ActiveRecord::Base.transaction do
fatura = Fatura.create!(dados)
ProcessarFaturaJob.perform_later(fatura.id)
end
Se o worker do Redis for mais rápido do que o encerramento da transação no PostgreSQL, o job inicia sua execução antes do COMMIT do banco relacional. O resultado é o erro ActiveRecord::RecordNotFound, exigindo soluções de contorno como retries imediatos ou o uso de callbacks como after_commit.
Com o Solid Queue gravando nas tabelas do próprio PostgreSQL, a gravação do registro e a criação do job podem residir exatamente na mesma transação ACID:
## Cenário atômico com Solid Queue
ActiveRecord::Base.transaction do
fatura = Fatura.create!(dados)
ProcessarFaturaJob.perform_later(fatura)
end
Se a transação falhar ou for revertida por rollback, o job simplesmente nunca existirá na fila. Se o commit tiver sucesso, o job se torna imediatamente visível para os workers, com a garantia física de que os dados persistidos já estão disponíveis para consulta.
O Solid Queue divide suas responsabilidades operacionais em papéis bem definidos dentro de um único supervisor de processos:
set(wait: 10.minutes)), verificar limites de concorrência e movê-los para a tabela de prontos para execução.A configuração no Rails é declarativa e fica no arquivo config/queue.yml:
production:
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: [ "urgent", "default" ]
threads: 5
polling_interval: 0.1
- queues: [ "mailers", "background" ]
threads: 3
polling_interval: 1
O supervisor pode rodar dentro do próprio processo do Puma em ambientes menores ou como um serviço de sistema independente gerenciado via systemd em servidores de produção.
A substituição de uma solução baseada em Redis pelo Solid Queue altera diretamente as responsabilidades da pilha de infraestrutura:
| Critério | Sidekiq com Redis | Solid Queue no PostgreSQL |
|---|---|---|
| Pilha de serviços | PostgreSQL + Redis | Apenas PostgreSQL |
| Garantia transacional | Nenhuma (sistemas separados) | Nativa via transações ACID |
| Backup de estado | RDB/AOF desacoplado do banco | Unificado no dump/WAL do banco |
| Agendamento recorrente | Exige gems de terceiros | Integrado nativamente |
| Gargalo operacional | Memória volátil e rede | IOPS e escrita na tabela |
| Complexidade em VPS | Dois daemons para monitorar | Apenas a aplicação e o banco |
Adotar o PostgreSQL como fila de mensagens exige atenção a detalhes específicos do motor de armazenamento relacional. Uma fila de jobs gera um volume elevado de inserções e deleções rápidas, o que pode causar inchaço (bloat) de tabelas e índices se a manutenção automática não estiver bem calibrada.
As tabelas de execução do Solid Queue precisam de uma rotina de autovacuum mais agressiva do que as tabelas de negócio comuns. Caso contrário, páginas mortas acumulam e tornam as consultas lentas.
É recomendável aplicar parâmetros específicos nas tabelas do Solid Queue:
ALTER TABLE solid_queue_ready_executions SET (
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_vacuum_cost_limit = 1000
);
Esse ajuste força o PostgreSQL a limpar linhas mortas com muito mais frequência nessa tabela específica, sem onerar o comportamento global do banco.
Em aplicações pequenas e médias, as tabelas do Solid Queue podem compartilhar o mesmo banco de dados da aplicação sem qualquer impacto perceptível.
Quando o volume de jobs aumenta, o Rails permite direcionar o Solid Queue para um banco de dados PostgreSQL secundário ou uma conexão isolada utilizando o suporte nativo a múltiplos bancos de dados do framework:
production:
primary:
adapter: postgresql
database: app_production
queue:
adapter: postgresql
database: queue_production
migrations_paths: db/queue_migrate
Essa separação mantém a simplicidade de operar apenas uma tecnologia de banco de dados no servidor, mas isola o pool de conexões e o tráfego de disco do processamento assíncrono.
O Solid Queue não aposenta o Redis para todos os cenários possíveis. Se a sua arquitetura processa dezenas de milhares de jobs curtos por segundo com requisitos de latência na casa dos microssegundos, o overhead de escrita do WAL (Write-Ahead Logging) do PostgreSQL se tornará um gargalo de disco antes de uma solução puramente em memória.
No entanto, para sistemas onde a confiabilidade, a integridade transacional e a facilidade de manutenção em servidores próprios superam a necessidade de throughput massivo, eliminar o Redis reduz custos operacionais imediatos. Menos ferramentas na esteira de deploy significam menos pontos de falha às três da manhã.
CTO e arquiteto full stack especializado em Ruby on Rails, Vue.js, sistemas multi-tenant e infraestrutura distribuída.