2 pontos por GN⁺ 2024-11-04 | 1 comentários | Compartilhar no WhatsApp
  • 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 LOCKED do 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/NOTIFY do 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 LOCKED em instruções SELECT ... 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 para running e então retorná-lo
    • BEGIN
    • SELECT ... FOR UPDATE SKIP LOCKED
    • UPDATE 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 LISTEN e NOTIFY
    • Clientes PostgreSQL podem assinar com LISTEN um canal de mensagens identificado por uma string arbitrária
    • Quando outro cliente envia um NOTIFY para esse canal, todos os clientes inscritos recebem a notificação
    • Opcionalmente, uma mensagem pequena pode ser anexada
  • 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

 
GN⁺ 2024-11-04
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

    • Se apenas um processo local vai usar o Redis dentro da máquina como cache em memória, é melhor simplesmente usar as estruturas de dados da linguagem de programação que você está usando
    • Fico curioso para saber quão diferente é o overhead do Postgres e do Redis quando executados localmente. Também não entendo por que se assume que o Postgres não roda localmente
      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
    • O Django tem cache integrado com suporte a Redis, e também uma opção de cache em memória, mas ela é marcada como “não destinada à produção”. Isso porque, se houver várias instâncias do Django, cada cache em memória será diferente
      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
    • Nessa configuração, o que é mais rápido que Redis é um hashmap. Também não há motivo para o Postgres não rodar no mesmo servidor que o app; na prática, é uma configuração bastante comum
    • Projeto excessivo e distribuição prematura são problemas reais, mas Redis é a sigla de “Remote Dictionary Server”. O objetivo original não é, de forma alguma, rodar localmente. Claro, a execução local também pode ser uma escolha de arquitetura justificável, como quando o dicionário padrão da linguagem não oferece suporte a consultas por intervalo
  • 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

    • Ter uma coisa a menos para gerenciar em produção é uma vantagem. Comece com PostgreSQL e, quando desempenho, escala ou custo tornarem necessário, adicione um sistema especializado
  • É 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)

    • Eu estava procurando uma fila de tarefas simples. Huey também é bom, mas já uso Postgres, e como as tarefas entram na fila mais ou menos uma vez por hora, algo baseado em Redis sempre pareceu exagerado
    • No lado do Node, também existe o PG-Boss: https://github.com/timgit/pg-boss
  • 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

    • Também é preciso ver se a implementação não quebra quando transações de longa duração impedem o vacuum de fazer a remoção de tuplas
  • 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

    • A diferença entre Postgres e MySQL aqui é realmente surpreendente
      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
    • Basta usar pooling de conexões. Dá para usar com poucos cliques, como no AWS RDS Proxy
  • 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

    • A maioria dos recursos desnecessários pode ser desativada. Por exemplo, é possível criar uma tabela como unlogged: https://www.postgresql.org/docs/current/sql-createtable.html...
      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

    • Redis não é particularmente difícil de implantar. Em vez de planejar uma migração que pode trazer efeitos colaterais inesperados no futuro, também é perfeitamente razoável usar Redis desde o início