Introdução
Em muitos ambientes Ruby on Rails que estão no ar em constante crescimento ocorre o momento em que a equipe desenvolvedora da aplicação, por diversos motivos, visualiza a necessidade de alguns processos internos do sistema serem executados de maneira assíncrona, liberando a thread principal para lidar com os processos mais imediatos e prioritários. Por exemplo, numa loja on-line que está recebendo cada vez mais pedidos é necessário enviar e-mails de confirmação aos clientes, atualizar estoques dos produtos, enviar dados para transportadores, contactar APIs de cartões de crédito, etc. Tratam-se de etapas que não necessariamente precisam ser executadas no momento exato em que o cliente concluir a compra.
Para cumprir ações não imediatas como estas é comumente utilizada a gem Sidekiq, que atua criando filas de tarefas a serem executadas (uma fila de envio de e-mails ou uma fila de checagem de boletos, por exemplo) em threads separadas da principal, evitando assim possíveis interrupções nos processos que precisam atender a interface com o usuário. Uma pesquisa rápida no rubygems.org no momento em que este artigo é escrito - no início de 2020 - mostra que esta gem possui por volta de 58 milhões de downloads!
O Sidekiq funciona em conjunto com o Redis, um software que, neste contexto, cuida do armazenamento das informações sobre os processos a serem executados, por meio do qual o Sidekiq enfileira e retira os processos de acordo com as necessidades e preferências definidas para a aplicação. O Redis é bastante utilizado em diversas outras aplicações devido à sua rápida performance.
Em sistemas de arquitetura mais básica, a simples configuração de um único processo do Redis pode ser mais que o suficiente para prover ao Sidekiq os dados essenciais para manter todas as rotinas da aplicação em dia. No entanto, se o sistema continuar crescendo, o Sidekiq deve acompanhar esse aumento e ativamente processar uma quantidade maior de tarefas. Consequentemente o Redis precisa estar pronto e desimpedido para receber e fornecer os dados sobre esses processos. Um pequeno mau funcionamento pode acabar se tornando perigoso e custoso para o sistema como um todo, acarretando em tarefas não concluídas se acumulando e não sendo processadas em tempo adequado.
Uma das ações que podem ser tomadas para adequar o Sidekiq a esse tipo de circunstância é conectá-lo a um cluster de instâncias do Redis de modo que, caso a principal instância (denominada master) pare de funcionar, haja um outro processo do Redis secundário (denominado slave) pronto para assumir seu lugar como nova instância principal, garantindo ao máximo a disponibilidade do Redis para auxiliar o Sidekiq.
Para que essa troca de instâncias Redis seja realizada rápida e automaticamente, utiliza-se o Sentinels, um sistema de monitoramento que pertence ao próprio Redis. Os Sentinels são processos que checam a saúde do Redis principal e, caso identifiquem que esse serviço não está operante da maneira como deveria, realizam uma eleição onde o Redis secundário mais votado é reconfigurado para ser o novo principal. Os Redis secundários restantes por sua vez também são reconfigurados, apontando para a nova instância principal escolhida, de onde replicam as informações armazenadas para estarem também preparados para assumir como principal caso uma nova falha ocorra. Os sentinels então seguem monitorando os processos do Redis e reconfigurando o cluster conforme surge a necessidade de intervir nos processos do Redis.
Para o Sidekiq ter proveito destes recursos é necessário ajustar suas configurações para que ele consiga enxergar todos os sentinels e estes lhe indiquem qual o endereço do Redis principal a ser consultado para carregar e descarregar as tarefas do sistema.
Definindo as configurações
Diversos artigos pela internet recomendam diferentes maneiras de se configurar e organizar o Redis e os Sentinels - é uma questão bem particular que vai depender bastante da necessidade e da arquitetura de cada aplicação. Este artigo tem enfoque em explicar como montar o código em Ruby, portanto não entrará em detalhes sobre os arquivos de configurações do Redis, dos Sentinels ou sobre a arquitetura do cluster e das máquinas que eventualmente hospedarão os serviços. Sigamos com um exemplo de código a ser colocado no arquivo inicializador do Sidekiq (geralmente definido em config/initializers/sidekiq.rb
) :
config_redis = {
url: 'redis://mymaster:6379',
role: :master,
sentinels: [
{ host: 'sentinel://sentinel1.com', port: '26379' },
{ host: 'sentinel://sentinel2.com', port: '26379' },
{ host: 'sentinel://sentinel3.com', port: '26379' }
]
}
Sidekiq.configure_server do |config|
config.redis = config_redis
end
Sidekiq.configure_client do |config|
config.redis = config_redis
end
Neste trecho de código temos um hash de configurações sobre o Redis e os Sentinels - config_redis
- sendo carregado tanto na definição das configurações do servidor quanto do cliente do Sidekiq. Ambos os blocos precisam receber estes mesmos dados para tudo funcionar corretamente. Através da chave url
deve ser informado o nome definido para a URL do cluster do Redis, que por padrão é redis://mymaster
, e responde pela porta padrão do Redis 6379
. É necessário verificar se o cluster está com um nome diferente, pois caso um valor errado seja informado a página do Sidekiq lançará uma exceção sobre não conseguir conectar ao Redis.
A configuração também exige que seja informada a chave role
para indicar explicitamente que esta é a URL utilizada para referenciar a instância master
do Redis. Não informá-la resultará no mesmo problema relatado no parágrafo anterior.
Por fim a chave sentinels
recebe um array
listando todos os Sentinels disponíveis no ecossistema por meio de hashes para indexar a URL de cada Sentinel na chave host
, e a porta utilizada por cada um (que por padrão é a porta 26379
). Estas configurações também podem variar muito de acordo com a disposição das máquinas utilizadas.
Na interface do Sidekiq é possível ver no rodapé da página a URL do serviço principal do Redis sendo consumido, indicando que tudo está configurado corretamente
Testando localmente
Pelo fato deste tipo de configuração exigir que várias máquinas com processos do Redis e Sentinels estejam operando, nem sempre possuímos uma infraestrutura adequada disponível para realizar testes a fim de reproduzir com mais fidelidade o ambiente de produção. Através do Docker é possível fazer diversos testes, incluindo alguns mais aprofundados - contudo requerendo que as configurações do Redis e dos Sentinels sejam ajustadas individualmente para cada container.
Existem algumas imagens já preparadas para rodar os Sentinels, como a do Bitnami, que tornam bem fácil testar o código citado acima sem depender de configurações adicionais e garantindo que na parte do Sidekiq tudo estará ajustado sem erros.