Redis é necessário? O PostgreSQL oferece filas, locks e Pub/Sub (2021)
(spin.atomicobject.com)- A combinação comum PostgreSQL + Redis em serviços web é conveniente, mas parte das necessidades de fila de jobs em background, lock distribuído e Pub/Sub pode ser atendida apenas com PostgreSQL
- O
FOR UPDATE SKIP LOCKEDdo PostgreSQL 9.5 ignora linhas bloqueadas em vez de esperar, sendo útil para implementar filas que evitam que vários workers peguem o mesmo job - Locks distribuídos no nível da aplicação podem ser criados com advisory locks do PostgreSQL, reutilizando o mecanismo interno de locks para finalidades definidas pela aplicação
- O
LISTEN/NOTIFYdo PostgreSQL 9 permite assinar canais de string arbitrários e enviar notificações, podendo servir como camada de Pub/Sub, e o Rails ActionCable também oferece suporte nativo ao uso de PostgreSQL - O Redis continua forte em cache com TTL e manipulação de dados temporários, mas alguns sistemas podem reduzir a dependência de Redis para baixar o custo operacional e a complexidade de desenvolvimento
Papéis do Redis que podem ser absorvidos pelo PostgreSQL
- Um serviço web típico usa PostgreSQL como armazenamento de dados e Redis para coordenar filas de jobs em background ou operações atômicas limitadas
- O Redis em si é útil, mas alguns dos papéis que ele assume nessa combinação podem ser substituídos apenas com recursos do PostgreSQL
Fila de jobs em background
- O Redis é frequentemente usado em serviços web para a coordenação de filas de jobs que entregam tarefas a um pool de workers em background
- Registrar os jobs em background que precisam ser executados e seus dados de entrada
- Garantir que apenas um entre vários workers pegue determinado job
- O Redis se encaixa bem nesse uso por ter operações atômicas ricas sobre estruturas de dados
- Desde o PostgreSQL 9.5, é possível usar a opção
SKIP LOCKEDem instruçõesSELECT ... FOR ...- Ao definir essa opção, o PostgreSQL ignora linhas que exigiriam esperar pela liberação de um lock
- Com
FOR UPDATE SKIP LOCKED, um lock em nível de linha é adquirido implicitamente sobre as linhas retornadas - Graças ao
SKIP LOCKED, não há possibilidade de bloqueio por locks de outras transações - Se houver outro job para processar, ele será retornado
- Mesmo que vários workers executem o mesmo comando, eles não receberão a mesma linha por causa do lock em nível de linha
- O fluxo básico é selecionar, dentro de uma transação, um job com status
pending, mudar esse job pararunninge então retorná-loBEGINSELECT ... FOR UPDATE SKIP LOCKEDUPDATE jobs SET status = 'running' ... RETURNING jobs.*COMMIT
- Um ponto de atenção é que, quando há muitos workers e muitos jobs, o custo de varrer a fila tentando adquirir locks pode crescer
- Na maioria dos apps tratados na prática, havia menos de 12 workers em background, então era provável que esse custo não fosse grande
Locks de aplicação
- Há casos, como rotinas de sincronização com serviços de terceiros, em que é necessário garantir que apenas uma instância rode para um usuário específico em todos os processos do servidor
- Esse tipo de lock distribuído é outro caso de uso comum do Redis
- O PostgreSQL pode alcançar o mesmo objetivo com
advisory locks- Advisory locks permitem usar o mecanismo de locks que o PostgreSQL já utiliza internamente para finalidades definidas pela aplicação
Pub/Sub
- O Redis também é muito usado para enviar eventos a clientes ativos
- Notificar que o usuário pode ler uma nova mensagem
- Fazer streaming de dados para o cliente assim que eles estiverem prontos
- Em geral, WebSocket é a camada de transporte de eventos, e o Redis atua como motor de Pub/Sub
- Desde o PostgreSQL 9, há suporte a Pub/Sub com as instruções
LISTENeNOTIFY- Clientes PostgreSQL podem assinar com
LISTENum canal de mensagens identificado por uma string arbitrária - Quando outro cliente envia um
NOTIFYpara esse canal, todos os clientes inscritos recebem a notificação - Opcionalmente, uma mensagem pequena pode ser anexada
- Clientes PostgreSQL podem assinar com
- Se você usa Rails com ActionCable, o uso de PostgreSQL já tem suporte nativo
Áreas em que vale manter o Redis
- O Redis atua em áreas diferentes do PostgreSQL e é forte em tarefas que não são o alvo do PostgreSQL
- Cache de dados com TTL
- Armazenamento e manipulação de dados temporários
- O PostgreSQL tem uma amplitude de recursos grande demais para ser visto apenas como um banco SQL simples ou um componente opaco por trás de um ORM
- Parte do trabalho delegado ao Redis também pode se encaixar bem no PostgreSQL
- Abrir mão do Redis pode ser uma opção para reduzir o custo operacional e a complexidade de desenvolvimento gerados pela dependência de vários serviços de dados
1 comentários
Opiniões do Hacker News
Como todo mundo insiste em arquiteturas excessivamente distribuídas, muitas vezes não enxerga as verdadeiras vantagens do Redis. Rodando na mesma máquina que a aplicação, ele pode responder em bem menos de 1 milissegundo, permitindo fazer na aplicação coisas que seriam difíceis com Postgres
É verdade que o Postgres é excelente, mas ele não roda na memória da mesma máquina que a aplicação. Se você só precisa de algo como filas, talvez não precise de um armazenamento chave-valor em memória. O ponto central de um armazenamento chave-valor em memória é fazer tarefas que precisam das características de desempenho da RAM, e não dá para obter essas características do outro lado de uma conexão de rede
Não há nenhuma mágica especial no Postgres; assim como o Redis, ele é apenas um programa rodando em outro processo. Em conexões locais, ele usa pipes rápidos para reduzir a latência, e também pode usar métodos mais rápidos de transferência de dados em massa. Já usei várias vezes dessa forma
Porém, em casos como ferramentas internas de trabalho, dá para manter uma única instância crescendo por muito tempo, e esse cache em memória a torna muito rápida. django-cachalot é uma biblioteca que lida automaticamente com a invalidação de cache sempre que há uma escrita em uma tabela. É uma abordagem meio bruta, mas oferece ganho de desempenho com quase nenhum esforço; apps internos com poucas atualizações passam, na prática, a rodar a partir da RAM e só voltam a fazer consultas normais ao banco de dados quando não há acerto no cache
https://github.com/noripyt/django-cachalot
Há muitas defesas aqui partindo da perspectiva do Redis, mas é claro que existem áreas específicas em que o Redis é melhor
Ainda assim, não acho que esse seja o ponto principal do texto. A frase central pode ser resumida como: “O PostgreSQL tem muito mais funcionalidades do que você esperaria ao tratá-lo como um simples banco de dados SQL ou como alguma entidade misteriosa escondida atrás de um ORM”. Se você usa qualquer banco de dados apenas por trás de um ORM, é bem provável que esteja deixando recursos de lado. Se for preciso adicionar outro serviço, como Redis, talvez seja melhor usar o banco de dados que você já configurou em vez de acrescentar uma nova dependência
É bom entender o que o Postgres consegue fazer. É um banco de dados poderoso
O contraponto é que a barreira para usar Redis é muito baixa e, em troca, você ganha alto desempenho, amplo suporte de bibliotecas e alivia a carga do banco de dados principal. Por exemplo, dá para criar um cache de respostas de API com Postgres. O TTL pode ser implementado com uma tarefa cron que varre valores antigos do cache. Ou você simplesmente usa Redis
Bloqueios consultivos são elegantes e úteis, mas, se você usar algo como PgBouncer, pode haver problemas entre bloqueios consultivos de sessão e interleaving de transações. Um sistema separado tem desvantagens como chamadas de rede, disponibilidade e conhecimento de domínio, mas o compromisso representado pelo Redis é relativamente baixo
É um texto bem antigo, mas hoje isso se tornou um padrão muito comum. Em 90% dos projetos que só precisam de uma fila de tarefas para envio de e-mails ou geração de relatórios, não se processam milhões de mensagens por segundo, então vale a pena considerar uma forma de simplificar a stack
Usei esse padrão com frequência para contornar problemas que tive com Celery e acabei separando isso em um framework próprio: https://github.com/TkTech/chancy feedback é bem-vindo
Existem muitas ferramentas desse tipo, e algumas delas são serviços comerciais, então claramente há demanda
https://worker.graphile.org/ (Node.js)
https://riverqueue.com/ (Go)
https://github.com/acaloiaro/neoq (Go)
https://github.com/contribsys/faktory (Go)
https://github.com/sorentwo/oban (Elixir)
https://github.com/procrastinate-org/procrastinate (Python)
O PGQueuer usa FOR UPDATE SKIP LOCKED e LISTEN/NOTIFY do PostgreSQL para oferecer fila de tarefas, bloqueios e notificações em tempo real
Para quem já usa PostgreSQL, é uma alternativa minimalista que dispensa Redis
https://github.com/janbjorge/PGQueuer
A propósito, fui eu que criei
Gosto do Postgres, mas ele tem algumas limitações
Se você precisa de um armazenamento chave-valor, é preciso avaliar se conhece o autovacuum, se entende os limites do pool de conexões e o que prefere entre throughput e segurança. Se precisa de uma fila, é preciso ver se o processamento é sequencial, se há limitação de taxa, se há fan-out ou se há separação por tópico. Se precisa de publicação/assinatura, é preciso avaliar se se importa com recebimento duplicado, perda de mensagens ou se precisa de replay. Se precisa de bloqueios, é preciso conhecer os limites do pool de conexões e o statement_timeout. A maioria dos problemas acima pode ser resolvida, mas não é tão simples assim
Um grande obstáculo no mecanismo de publicação/assinatura do Postgres é que o tamanho máximo da mensagem é de 8000 bytes
A solução alternativa recomendada é colocar os dados em uma tabela e enviar apenas o ID, mas, se você não quiser mantê-los para sempre, terá de fazer garbage collection desses dados, e isso adiciona trabalho a cada mensagem. Claro que há casos em que isso é aceitável, mas, em muitos usos típicos do Redis, essa limitação torna difícil considerá-los equivalentes
Vamos ver se o pgsql lida com 15000 conexões de clientes
Alguém da Planetscale disse em um podcast que as instâncias MySQL do GitHub lidam com mais de 50 mil conexões cada. Já no Postgres, se você precisa de mais de 100 conexões, já passa a precisar do PgBouncer
Filas, bloqueios e publicação/assinatura são possíveis. Mas o uso mais importante do Redis, cache, fica de fora
Atualizações no Postgres são notoriamente mais caras que inserções, geram lixo e exigem vacuum. Escritas também ficam consideravelmente mais lentas por causa de garantias de durabilidade que não importam para cache. A expiração automática é muito conveniente e reduz erros
Também é possível desativar commit síncrono, autovacuum etc. Claro que o Redis ainda será mais rápido, mas talvez a diferença não seja algo com que uma empresa comum precise se preocupar
Dito de forma mais clara, a ideia do texto é: comece com Postgres e migre para Redis quando surgir a necessidade
É melhor manter o número de partes móveis o menor possível