6 pontos por GN⁺ 2023-09-25 | 1 comentários | Compartilhar no WhatsApp
  • Antes de adotar um sistema de filas separado, se for possível montar uma fila de tarefas em segundo plano apenas com o Postgres que já está em uso, dá para aproveitar bastante a simplicidade operacional e a familiaridade da equipe
  • Desde o Postgres 9.5, NOTIFY/LISTEN e FOR UPDATE SKIP LOCKED resolvem juntos tanto a notificação de novas tarefas quanto a prevenção de processamento duplicado entre workers
  • Redis, Kafka, RabbitMQ e Amazon SQS são opções poderosas, mas uma nova dependência adiciona modos de falha e custo de aprendizado aos ambientes de desenvolvimento, testes e operação
  • Filas com Postgres também não são solução para tudo: push/pop pode ser mais lento do que no Redis, e pode haver custos operacionais como um banco de dados ou servidor de filas separado e VACUUM mais frequente
  • O código de tarefas em segundo plano deve ser abstraído para não ficar preso ao backend de fila, e é mais seguro considerar outra tecnologia de fila apenas quando a tecnologia familiar não conseguir atender aos requisitos

Como uma fila com Postgres funciona

  • A tecnologia de fila com Postgres é composta por dois elementos
    • pub/sub para anunciar e receber novas tarefas
    • bloqueio de linhas para impedir que vários workers processem a mesma tarefa
  • As duas funcionalidades são fornecidas por padrão desde o Postgres 9.5, lançado em 2016
  • Usando NOTIFY e LISTEN juntos, é possível adicionar pub/sub à aplicação
  • FOR UPDATE SKIP LOCKED aplica bloqueio de linha nos registros que correspondem à condição e ignora os registros que já estão bloqueados
    • Aplicado a registros de tarefas, isso permite criar uma consulta de processamento de fila como SELECT * FROM jobs ORDER BY created_at FOR UPDATE SKIP LOCKED LIMIT 1
  • SKIP LOCKED fornece uma visão “inconsistente” dos dados, mas essa característica é útil em filas
    • Tarefas que já estão sendo processadas não ficam visíveis para outros workers por causa do bloqueio de linha
    • Esse comportamento permite exclusão mútua distribuída
  • Ao usar NOTIFY para avisar os workers que estão em LISTEN sobre novas tarefas, é possível montar tanto processamento periódico em lote quanto processamento em tempo real

Como o Redis virou o padrão para tarefas em segundo plano

  • Decidir como processar tarefas de longa duração é uma escolha técnica cheia de trade-offs
  • As opções de fila e message broker mais usadas incluem
    • Redis: armazenamento de dados em memória e backend de várias bibliotecas de tarefas em segundo plano
    • Apache Kafka: plataforma distribuída de streaming de eventos mantida pela Apache Foundation
    • RabbitMQ: apresentado como um message broker amplamente distribuído
    • Amazon SQS: SaaS da Amazon que oferece filas altamente escaláveis
  • No tópico background jobs do GitHub, as 5 bibliotecas mais populares são todas baseadas em Redis
    1. Sidekiq — Ruby
    2. resque — Ruby
    3. rq — Python
    4. Hangfire — C#
    5. asynq — Go
  • O Redis armazena dados em memória, então inserção e leitura são rápidas, e oferece API de pub/sub e estruturas de dados como list e set, o que combina bem com a implementação de filas
  • A escalabilidade do Redis faz dele a escolha padrão para muitos desenvolvedores, e padrões têm muita força em decisões tecnológicas

Critérios a avaliar antes da escalabilidade

  • A obsessão do setor de tecnologia com “scale” pode empurrar para segundo plano simplicidade, facilidade de manutenção e redução da carga cognitiva dos desenvolvedores
  • Muitas aplicações não exigem escala no nível de Google, Facebook ou Uber
  • Ao iniciar um projeto ou negócio, em vez de otimizar apenas para escalabilidade desde o começo, vale priorizar primeiro estes critérios
    • tecnologias que a equipe conhece bem
    • tecnologia boa o suficiente para atender às necessidades dos usuários
    • a solução que melhor combina com a capacidade da equipe e exige menos esforço
  • O Postgres também escala bem na prática, mas não é o sistema especializado em filas que mais escala para esse caso de uso
  • Como software de uso geral, o Postgres faz várias coisas razoavelmente bem, e fila é uma delas

Julgando pela “tecnologia entediante”

  • Ao escolher uma tecnologia de fila, a pergunta mais importante é qual tecnologia já está em uso e é bem compreendida hoje
  • Uma tecnologia que já é usada e bem conhecida adiciona menos peso à stack de software
  • Se você já usa um banco de dados relacional e esse banco é o Postgres, faz sentido considerar uma fila com Postgres antes de outro software
  • “Tecnologia entediante” não é uma lista fixa; ela muda de acordo com as tecnologias já usadas no momento
    • Em aplicações centradas em troca de mensagens, RabbitMQ pode ser a tecnologia entediante
    • Em aplicações centradas em cache, Redis pode ser a tecnologia entediante
    • Em aplicações com muitos dados relacionais, Postgres pode ser a tecnologia entediante
  • Introduzir Redis, Kafka, RabbitMQ ou SQS do zero apenas para tarefas em segundo plano aumenta bastante o custo
    • uma nova dependência de sistema é adicionada aos ambientes de desenvolvimento, testes e produção
    • no futuro, Developer, DBA e SRE precisarão conhecer os modos de falha e as configurações do novo sistema
    • será necessário conhecimento de recuperação de falhas, diagnóstico de problemas e monitoramento de desempenho
    • unknown unknowns que a equipe ainda não conhece continuam sendo um risco

Os custos e os critérios de escolha de uma fila com Postgres

  • Tecnologia entediante não é solução mágica, e o Postgres não é exceção
  • Ao escolher uma fila com Postgres, você ganha familiaridade, modos de falha conhecidos e diluição de custos, mas pode pagar os seguintes preços
    • push/pop em fila com Postgres é consideravelmente mais lento do que no Redis
    • em vez de um único banco de dados relacional, pode ser necessário um banco da aplicação e outro banco para a fila
    • pode ser necessário um servidor de banco de dados separado para escalar tarefas em segundo plano de forma independente
    • VACUUM mais frequente pode ser necessário, gerando custo de desempenho
  • Nenhum entre Postgres, Redis, Kafka, RabbitMQ e SQS deveria virar um padrão automático
  • Escolha de tecnologia é um conjunto contínuo de trade-offs, e deve ser julgada conforme a equipe e as exigências da aplicação
  • Em caso de dúvida, pode-se aplicar o critério: “considere alternativas apenas quando ficar demonstrado que a tecnologia entediante não consegue atender aos requisitos”

Uma estrutura de tarefas em segundo plano que permite trocar a fila

  • O código de processamento de tarefas em segundo plano não deve depender de uma tecnologia de fila específica
  • À medida que a aplicação cresce, tecnologias como memcached ou Redis podem ser adicionadas conforme necessário, e com o tempo também podem se tornar “tecnologias entediantes” familiares para a equipe
  • Ao abstrair a tecnologia de fila, o usuário pode escolher a fila adequada para o trabalho
  • Entre as bibliotecas de tarefas em segundo plano mais populares no GitHub, com exceção de Hangfire, nenhuma oferece uma rota de saída para tecnologias de fila além do Redis
    • Em uma estrutura assim, trocar a fila exige reescrever o código da aplicação
  • Neoq é uma biblioteca para Go criada com essa filosofia
    • o backend de fila pode usar memória, Postgres ou Redis
    • o usuário pode inicializar com outro backend de fila sem mudar o código da aplicação
    • as implementações em memória e Postgres são nativas, e a implementação Redis usa asynq
  • Redis, Kafka, RabbitMQ e SQS podem ser excelentes tecnologias, mas nem sempre são a fila certa para aquele trabalho ou o nível adequado de complexidade

1 comentários

 
GN⁺ 2023-09-25
Opiniões do Hacker News
  • Até agora, já construí sistemas distribuídos de jobs três vezes, e a regra prática que recomendo há anos é: “projete para 10 vezes a escala atual”
    Se são 70 requisições por segundo, pense em 700; se você precisa de 20 servidores de jobs em lote, dimensione para 200. Mesmo uma startup que cresce 100% ao ano estará 8 vezes maior em 3 anos, então há tempo para reescrever conforme cresce
    No primeiro sistema que criei, evitei SQL por causa de “escalabilidade”, mas sofri bastante ao encontrar muitos casos de exceção que exigiam integridade transacional
    Nos dois sistemas distribuídos de jobs que construí depois, usei PostgreSQL como coordenador e, literalmente, estruturei tudo em torno de SELECT FOR UPDATE SKIP LOCKED. Um deles controla normalmente 350 workers, e o outro aplica prioridades complexas a milhares de jobs
    Ambos vão aguentar sem problemas até chegar a uma receita anual de vários milhões de dólares; por exemplo, o sistema com 350 workers poderia escalar para cerca de 2.000 CPUs com pequenos ajustes
    Tecnologias de escala massiva parecem bem baratas até você perceber que precisa de transações. A partir desse momento, imitar semântica transacional em cima de um armazenamento com consistência eventual vira um pesadelo de engenharia
    Portanto, é preciso sentar e fazer as contas. Se a empresa faturar US$ 100 milhões por ano, quão grande o sistema distribuído precisará ser, se é fácil obter uma instância PostgreSQL que aguente essa carga, ou se sharding por cliente basta; se for possível, vale considerar seriamente PostgreSQL. Cem pequenas coisas ficam mais fáceis

    • Tive uma experiência parecida. Várias vezes levei uma fila baseada em SQL dezenas de vezes além da escala em que as pessoas dizem “SQL quebra aqui e uma solução distribuída é obrigatória”
      A solução em SQL costuma ser mais simples, consumir menos recursos computacionais e ser mais fácil de operar
      Mas, para fazê-la funcionar, é preciso conhecer bem o banco de dados a ponto de saber que recursos como SELECT FOR UPDATE SKIP LOCKED existem. Hoje, como muitos engenheiros crescem em ambientes onde ORMs pesados bloqueiam a capacidade real do DBMS, esse conhecimento está ficando bastante raro
    • Projete e teste para 10 vezes a escala atual, mas implemente só o que é necessário agora
      O sistema precisa suportar a carga de pico e, se você não sabe qual é o pico, deve colocar uma margem de segurança ou incluir uma forma de descartar ou adiar jobs quando necessário
      Tudo é uma troca; você deve otimizar apenas o que precisa ser otimizado, e a capacidade de julgar o que entra nesse grupo é uma característica de bons engenheiros
    • A frase “imitar semântica transacional em cima de um armazenamento com consistência eventual é um pesadelo de engenharia” é realmente importante
      Se isso é novidade para você, acabou de obter, lendo o HN durante o expediente, o valor de uma semana inteira; vale escrever em um papel e colar no seu cartão do banco
    • Também dá para criar no PostgreSQL uma fila baseada em advisory locks, que é bem mais lenta, mas também tem vantagens
  • Em vários projetos, optei por uma abordagem mais simples: usar uma tabela comum e SELECT FOR UPDATE SKIP LOCKED, algo que funciona diretamente em ORMs/frameworks de DSL de consulta de todas as linguagens
    https://www.pgcasts.com/episodes/the-skip-locked-feature-in-...
    Não é “escala web”, mas, na minha experiência, escala facilmente até milhares de jobs em background

    • Já fiz algo ainda mais simples, sem locks, porque não havia lógica transacional. Escolhia uma linha, tentava atualizar um campo indicando que ela foi “pegada”; se o número de linhas afetadas fosse 1, o job era meu, se fosse 0, alguém tinha pegado antes, então eu escolhia outra linha
      Usei essa abordagem sem problemas em trabalhos de uma organização grande. Não precisa de deploy especial nem de nova infraestrutura; basta subir algumas threads de worker dentro da aplicação. Também dá para ter uma thread que reinicialize jobs abandonados
      Porém, em 3 anos isso nunca aconteceu de fato; tudo ficava dentro de try/catch, e, se falhasse, era colocado de volta na fila, além de a aplicação Java ser muito estável
    • Recentemente publiquei um manifesto e trechos de código para exatamente essa abordagem no PostgreSQL
      delete from task
      where task_id in
      ( select task_id
      from task
      order by random() -- use tablesample for better performance
      for update
      skip locked
      limit 1
      )
      returning task_id, task_type, params::jsonb as params
      [1] https://taylor.town/pg-task
    • O fato de um sistema de filas não escalar direito é o tipo de problema que você não quer descobrir por último. Depois que você percebe que o sistema de filas é estruturalmente incapaz de escalar, é difícil consertá-lo sem perda de dados
      Você disse “milhares de jobs em background”, mas filas normalmente devem ser avaliadas pela Lei de Little, falando em taxas como a taxa média de entrada de jobs por segundo e a duração média dos jobs. O número bruto, sozinho, não significa muita coisa
      No começo, um UPDATE ... SET ingênuo até funciona, mas pega locks demais. Usar uma subconsulta SELECT no UPDATE ou tornar o lock de retirada mais eficiente com SELECT FOR UPDATE SKIP LOCKED pode ajudar, mas, no fim, as consultas de retirada podem bloquear os locks umas das outras e travar a fila
      Nesse momento, dá para desligar a entrada para deixar o DB respirar, mas a entrada perdida causa perda de dados, e geralmente o problema central é que as retiradas bloqueiam umas às outras
      Também dá para shardear às pressas a tabela de jobs para evitar locks, mas isso é frágil para distribuir entre vários workers e pode levar à perda de dados. Você poderia descartar uma parte aleatória dos jobs, mas, obviamente, isso é perda de dados
      Essas opções são extremamente estressantes em produção e muito difíceis de recuperar sem redesenhar do zero. A menos que você tenha apenas alguns clientes e tenha certeza de que ficará para sempre no nível de dezenas de jobs por segundo, depois de passar por isso pessoalmente, eu escolho uma tecnologia de filas de verdade em vez de banco de dados sempre que possível
    • O https://github.com/graphile/worker da Graphile também é implementado mais ou menos dessa forma
  • Vejo com frequência críticas do tipo “engenheiros copiam a infraestrutura da FAANG para parecerem descolados, mesmo que ela não tenha nada a ver com suas necessidades”, mas acho que, em grande parte, isso é um problema de conhecimento e documentação.
    Se você quiser seguir a FAANG ou outras startups e criar uma arquitetura baseada em filas infinitamente escalável, há dezenas de guias, tutoriais e white papers de alta qualidade que permitem configurar soluções escaláveis como Redis e SQS em poucas horas.
    O custo de manutenção é mais alto, mas dá para começar copiando e colando comandos, código e configurações de fontes respeitadas.
    Por outro lado, se você quiser usar NOTIFY do PostgreSQL e pesquisar “SQLALchemy notify listen postgres”, só aparecem algumas perguntas sem resposta no Stack Overflow e alguns GitHub Gists sem contexto.
    Eu realmente gostaria de experimentar essa abordagem em um projeto paralelo, mas não tenho folga para gastar 2 ou 3 dias descobrindo tudo sozinho.
    Por fora, parece uma escolha entre “simples, mas não escala, ou seja, usar só PostgreSQL” e “complexo, mas escala, ou seja, Redis/SQS etc.”; nesse caso, vira a narrativa de que eu escolhi uma escalabilidade desnecessária porque fiquei cego por tecnologias legais e pela FAANG.
    Mas, quando se consideram também os guias e materiais disponíveis, as opções reais ficam mais próximas de “complexo e com escalabilidade imprevisível, porque eu não conheço a implementação nem as armadilhas” versus “simples e escalável, o jeito que todo mundo realmente faz”. Nesse cenário, a escolha do engenheiro que segue a FAANG parece muito mais racional.

    • A vantagem de tecnologias “chatas” como PostgreSQL é que a documentação é excelente. Para usar NOTIFY, basta ler https://www.postgresql.org/docs/current/sql-notify.html, sem precisar de grandes habilidades de busca.
    • Escalabilidade tem um custo. Se você a coloca sem precisar, reduz a flexibilidade — e, para uma startup, esse é exatamente o estado indesejado.
      Por exemplo, com um PostgreSQL sob baixa carga, migrações de schema, adição de novas constraints, análises etc. se tornam quase triviais.
      Ao usar SQS, Cassandra etc., você ganha escalabilidade/disponibilidade, mas, quando descobre que o design original não se encaixa, mudar leva muito mais tempo. É o caso de quando o negócio pede para adicionar uma constraint como “usuários do tipo foo não podem combinar simultaneamente o valor bar”.
      Dá para implementar sem PostgreSQL, mas, especialmente quando mudanças são necessárias, não é fácil nem simples.
      Por isso, para manter flexibilidade, acho melhor usar PostgreSQL; quando você sabe que algo não vai mudar, aí sim usar outra coisa junto por cima do PostgreSQL. Claro que, nesse caso, há overhead adicional de infraestrutura e manutenção.
      No fim, é sempre um trade-off, e é preciso saber o que trocar, quando e pelo quê.
    • Este texto é mais voltado a desenvolvedores de bibliotecas do que a desenvolvedores de aplicações.
      Não acho ideal que todo mundo implemente dentro da própria aplicação um worker de tarefas em background sob medida, seja com backend PostgreSQL ou com outra fila.
      Tarefas em background têm muitas sutilezas e detalhes de implementação fáceis de errar, então normalmente é melhor que uma biblioteca ou framework dedicado e mais abrangente cuide disso.
      Se nem toda aplicação Rails tivesse Sidekiq/Active Jobs e cada uma implementasse seu próprio worker em background, é bem provável que apps Rails tivessem uma reputação muito pior em termos de confiabilidade.
    • O ponto de “a manutenção é mais alta” é o que mata startups pequenas. Claro que também há muito a ganhar, mas, se a empresa quebrar, nada disso importa.
      Basta escolher a solução que traz o maior benefício sem se afastar demais do objetivo principal.
      Já vi uma startup em que os desenvolvedores gastavam cerca de 80% do tempo brigando com ferramentas e infraestrutura. O runway era de 3 meses e, hoje, só sobrou um grande buraco no fim dele. Ainda me dá arrepios só de pensar.
    • Se o argumento é que um design no nível da FAANG economiza tempo, então a principal evidência é apenas que dá para encontrar guias online.
      Parece faltar respeito pela complexidade.
  • A maior desvantagem de usar PostgreSQL como barramento publish/subscribe com LISTEN/NOTIFY é que LISTEN é um recurso de sessão e, portanto, não combina com pooling de conexões por instrução.
    Se for seguir esse caminho, é melhor usar advisory locks. Outros locks explícitos impõem uma carga maior ao banco de dados, mas advisory locks foram projetados intencionalmente para serem muito leves.
    Meu exemplo favorito de implementação é o que, que foi portado para várias linguagens.

    • Um dos motivos pelos quais não gosto de NOTIFY/LISTEN é que, quando há problema, é difícil de diagnosticar.
      Recentemente também tivemos de parar de usar porque, depois de algum tempo, todos os NOTIFY/LISTEN travavam, e só reiniciar o banco de dados resolvia https://dba.stackexchange.com/questions/325104/error-could-n...
    • Fico curioso se existe uma versão do Que para Node.
  • A Skype usou o PostgreSQL como fila com um pequeno plugin, anos atrás, para processar todos os CDRs. Não sei se ainda usa, mas, pelos padrões de 10 anos atrás, era “escala web” e simplesmente funcionava bem enquanto, na internet, as pessoas discutiam que usar banco de dados como fila era um antipadrão
    Ter transações é bastante conveniente
    https://wiki.postgresql.org/wiki/SkyTools
    Como usei bastante no trabalho, também fiz algumas apresentações sobre esse tema no Sydpy. Se você já opera e dá suporte bem ao PostgreSQL, é útil
    Mas, hoje em dia, eu provavelmente usaria uma fila dedicada e evitaria o RabbitMQ a todo custo

    • Seria bom explicar melhor a parte de “evitaria o RabbitMQ a todo custo”. Também tenho curiosidade sobre as experiências e opiniões de outras pessoas
    • Concordo em usar uma fila dedicada hoje em dia. O principal motivo é que, em ambientes de nuvem, a maioria dos provedores oferece tecnologias de fila hospedadas muito fáceis de usar e baratas
      Mesmo que haja preocupação com aprisionamento ao fornecedor, as operações básicas de uma fila são, na prática, algo pequeno como push/pop, então também é relativamente fácil escrever de um jeito que permita migrar se necessário
    • Tenho curiosidade se você teve uma experiência ruim com RabbitMQ. Se teve, também gostaria de saber se foi por causa da tecnologia em si ou de fatores ao redor, como documentação ou comunidade
    • Usar um banco de dados como fila é ótimo quando funciona bem, mas os modos de falha levam a estados dos quais é muito difícil se recuperar
      Se você colocou a fila dentro do banco de dados só porque já operava um banco de dados, essa lógica é uma faca de dois gumes. Não é algo a comemorar, mas agora a bagunça da fila pode derrubar também o armazenamento principal de dados
  • Uma das maiores vantagens de usar PostgreSQL como fila da aplicação é que todos os trabalhos assíncronos agendados se beneficiam da transacionalidade
    Por exemplo, se houver uma alteração complexa no backend após o cadastro de um usuário, que envia um e-mail, e depois de inserir a tarefa de envio de e-mail uma parte posterior da mesma transação falhar e fizer rollback, essa tarefa de e-mail nem entra na fila em primeiro lugar

    • É preciso deixar claro que, ao se conectar a outro sistema não idempotente, inevitavelmente você precisa escolher entre semântica de pelo menos uma vez ou de no máximo uma vez
      No caso de e-mail, se você falhar esperando a confirmação de envio, precisa escolher entre fazer a transação falhar e aceitar a possibilidade de e-mail duplicado, ou seguir em frente e aceitar a possibilidade de perda do e-mail
      A grande vantagem aparece em caminhos de código que modificam o DB de forma assíncrona. Como o consumo do trabalho e a atualização do DB ficam dentro da mesma transação, a semântica de exatamente uma vez pode ser tratada de forma totalmente transacional
    • Esse é um ponto realmente importante. Uso com frequência uma combinação de PostgreSQL e SQS, porque o SQS facilita o autoescalonamento do cluster de processamento de trabalhos
      No PostgreSQL, mantenho uma tabela de log de transações que contém o evento disparado e o pg_current_xact_id() daquela transação. Também daria para usar o xmin embutido da linha, mas aí é preciso se preocupar com wraparound de transações. Inserir nessa linha dispara um NOTIFY
      Um processo em segundo plano roda repetidamente e seleciona, da tabela de transações, todas as linhas com IDs de transação entre o xmin da execução anterior e o pg_snapshot_xmin(pg_current_snapshot()) atual. Ele mapeia esses eventos para trabalhos e os envia ao SQS, registra o xmin e então espera o próximo NOTIFY com LISTEN
    • Nós também mitigamos um pouco esse problema agendando a adição à fila para ser executada depois do commit. Ainda assim, sobram partes inseguras, e se a conexão com o RabbitMQ cair ficamos em apuros
    • Avisar ao banco de dados que algo foi processado e enviar uma mensagem para algum lugar como o RabbitMQ não pode ser 100% transacional. Esse é justamente o principal motivo dessa abordagem
      Em uma fila no PostgreSQL, é verdade que, se você inserir uma tarefa de envio de e-mail e depois a transação falhar, o e-mail não entra na fila
      Código de banco de dados que precisa ser totalmente separado vale a pena ser separado em transações diferentes. Por outro lado, se o usuário não foi criado no DB, talvez você também não queira enviar o e-mail de cadastro, então depende da situação
    • Outra vantagem é que a conclusão da transação é garantida antes que o trabalho seja pego
      Em filas baseadas em Redis, ou praticamente qualquer outra fila, você rapidamente acaba encontrando situações em que um trabalho que assume que um registro existe no banco de dados é executado antes de a transação ser commitada. O código para corrigir isso costuma ficar estranho e complexo
  • O que gosto no Kafka é que ele é simplesmente um log somente de acréscimo, e os clientes essencialmente só mantêm offsets
    Conceitualmente, é muito simples de entender, é persistente e bastante resiliente a falhas, porque você pode voltar para qualquer offset e reler
    Infelizmente, por sua natureza distribuída, o Kafka traz complexidade suficiente para que, na maioria dos casos de uso, acabe não valendo a pena
    Pessoalmente, eu gostaria que existisse algo parecido, mas mais fácil de operar. Em um único nó, provavelmente conseguiria processar centenas, talvez milhares, de eventos por segundo, e seria bem interessante sem a complexidade distribuída
    Em tese, também dá para fazer isso com PostgreSQL: basta nunca apagar as linhas. Talvez essa seja a resposta

    • O PostgreSQL tem IDs autoincrementais, que equivalem a “offsets” nativos, e também permite particionamento por data, então acho que é um excelente candidato como substituto simples do Kafka
      Se você não precisa do conjunto completo de recursos, como consumer groups e partições, a configuração dos consumidores também fica muito mais simples
    • Eu costumava torcer para que o FasterLog um dia amadurecesse o suficiente
  • No Windmill usamos exatamente essa abordagem. O Windmill é uma alternativa open source ao Retool e uma ferramenta mais próxima de um Airflow moderno, e rodamos benchmarks todos os dias
    Em uma instância razoável do GitHub CI, executando um worker do Windmill e o PostgreSQL em contêineres, o benchmark processa 1.200 trabalhos por segundo. Ao adicionar workers, escala de forma estável até 5.000 trabalhos por segundo
    Estamos avaliando o uso do Citus para ultrapassar a barreira de 5.000 trabalhos por segundo em uma instância multi-tenant
    https://github.com/windmill-labs/windmill/tree/benchmarks

  • Na época em que o volume era de cerca de 10 mensagens por segundo, usamos PostgreSQL para algumas filas e ele até escalou bastante. Mas, sendo sincero, configurar SQS ou outras stacks de fila na AWS, GCP ou Azure é fácil demais; elas foram criadas para esse propósito e já trazem coisas como DLQ embutidas
    Não vejo motivo para escolher esse caminho tendo que se preocupar com a possibilidade de esse sistema desabar e afetar até o estado do restante do banco de dados
    Gosto do princípio de “use a ferramenta mais simples”, mas às vezes parece que os engenheiros vão longe demais e acabam ficando só com a ferramenta mais burra, cheia de ressalvas ambíguas, mesmo quando a alternativa mainstream é relativamente barata e simples

    • A abordagem em que acabei me fixando é armazenar a maior parte do estado dos jobs no banco de dados e usar a fila de jobs apenas para acordar os workers e fazê-los processar esses jobs
      Ao armazenar o estado dos jobs no banco de dados, dá para consultá-lo de uma forma conveniente. Mesmo que não mostre o estado real com precisão, isso ajuda a analisar falhas em operação. Especialmente porque a maioria das filas de jobs simplesmente apaga os registros processados
      E, se você tornar todos os jobs em background idempotentes, um modelo como “enviar uma mensagem para a fila de jobs mandando processar este job” quase sempre é seguro
      Quando se depende apenas da fila de mensagens, muitas vezes fica difícil entender o que está acontecendo quando surgem problemas de desempenho. Por exemplo, o RabbitMQ pode informar o tamanho da fila, mas tem pouca ou nenhuma capacidade de inspecionar os dados internos
    • É por causa de transações e consistência de dados. É uma resposta que você não consegue obter com SQS
    • Porque cada software tem requisitos diferentes. Em appliances on-premises/B2B, não exigir nenhum serviço externo além do PostgreSQL pode ser uma funcionalidade por si só
      Pode-se prever que alguns softwares nunca vão ultrapassar a capacidade do PostgreSQL e, mesmo que ultrapassem, é possível projetá-los para facilitar a migração para outro serviço
      E talvez você queira criar de forma simples, dentro do PostgreSQL, um sistema de jobs com transações
  • Usamos o Oban, do Elixir, para processar centenas de milhares a milhões de jobs por dia dentro do PostgreSQL
    Ter semântica transacional em torno de jobs em background, como agendar um e-mail apenas quando uma transação é bem-sucedida, é extremamente conveniente
    É preciso fazer um pouco de tuning no autovacuum, mas, depois de ajustado, funcionou muito bem para nós