4 pontos por GN⁺ 2024-04-08 | 1 comentários | Compartilhar no WhatsApp
  • pgmock é um servidor PostgreSQL simulado em memória para testes unitários e E2E, que roda em WebAssembly no Node.js e no navegador sem dependências externas
  • Usuários de node-postgres podem se conectar usando um objeto de configuração que não abre uma porta, e esse método também funciona no navegador
  • No navegador, uma aplicação web não pode abrir portas TCP, mas é possível usar PostgresMock.createSocket e a configuração do node-postgres; se o bundler analisar imports estáticos, podem aparecer avisos sobre módulos opcionais do Node.js
  • A implementação atualmente executa um servidor PostgreSQL dentro de um emulador x86, priorizando evitar diferenças de comportamento entre testes e produção em vez de desempenho
  • No longo prazo, quando o fork nativo de PostgreSQL em WASM amadurecer, o plano é oferecer as duas abordagens e depois mudar o padrão para WASM nativo

O que o pgmock oferece

  • pgmock é um servidor PostgreSQL simulado em memória para testes unitários e E2E
  • Não requer dependências externas e roda dentro de WebAssembly tanto no Node.js quanto no navegador
  • A instalação pode ser feita via npm
npm install pgmock

Fluxo básico de uso

  • O servidor em memória é criado com PostgresMock.create(), e é possível receber uma string de conexão com listen(5432)
import { PostgresMock } from "pgmock";

const mock = await PostgresMock.create();
const connectionString = await mock.listen(5432);
  • Ao usar node-postgres, mock.getNodePostgresConfig() fornece um objeto de configuração que permite conectar sem escutar em uma porta
  • Ao terminar, recomenda-se chamar mock.destroy() para liberar os recursos
mock.destroy();

Suporte a navegadores e diferença em relação ao pglite

  • pgmock dá suporte completo ao ambiente de navegador
  • Aplicações web não podem abrir portas TCP, mas podem usar PostgresMock.createSocket e a configuração do node-postgres
  • Se o bundler analisar imports estaticamente, podem aparecer avisos de que módulos opcionais do Node.js não existem; um exemplo de configuração do Webpack está em examples/web-demo/next.config.mjs
  • Se você quer apenas executar um banco de dados no navegador, pode considerar o pglite
    • O pglite é mais rápido e mais leve, mas tem um conjunto de recursos limitado
    • pgmock foi projetado com o objetivo de oferecer a equivalência de recursos com o PostgreSQL de produção desejada em ambientes de teste

Como o PostgreSQL é executado em WebAssembly

  • Há duas formas de executar PostgreSQL em WebAssembly
  • A abordagem de fork nativo em WASM é mais rápida e usa muito menos memória, mas só oferece suporte ao modo de usuário único e não dá suporte a conexões nem extensões
  • pgmock atualmente usa a abordagem com emulador x86
    • O objetivo é evitar inconsistências entre testes e produção
    • Isso porque, em testes, desempenho geralmente não é um grande problema
  • No médio prazo, quando o fork nativo de PostgreSQL em WASM amadurecer, o plano é oferecer ambas as opções
  • Depois disso, o plano é mudar o padrão para WASM nativo, e a expectativa é que não haja muitos breaking changes grandes além da API interna PostgresMock.subtle

Diferença em relação a projetos PostgreSQL existentes para navegador

  • pgmock oferece compatibilidade completa de recursos dentro do runtime JavaScript e não depende de um proxy de rede para comunicação
  • Ele simula a stack de rede em JavaScript para se comportar como uma rede real, permitindo simular conexões TCP mesmo em plataformas que não permitem acesso a raw sockets

Extensibilidade e projetos relacionados

  • Em teoria, outras imagens Docker ou bancos de dados também poderiam ser executados, mas isso não foi testado
  • As seguintes implementações e projetos de base são mencionados
    • v86: emulador x86
    • Supabase & Snaplet: base da abordagem de executar PostgreSQL dentro de WebAssembly
    • Stackframe: mencionada como a empresa que pagou salários durante o desenvolvimento do pgmock

1 comentários

 
GN⁺ 2024-04-08
Comentários no Hacker News
  • Há alguns meses, venho criando no trabalho uma versão de Postgres em memória, com equivalência funcional ao banco de dados de produção
    A vantagem é que não precisa de processo externo nem de proxy. Se a plataforma consegue executar WASM, também dá para rodar o pgmock no Node.js ou no navegador, e até criar um banco novo com dados mockados é tão simples quanto criar um objeto JavaScript
    É um pouco diferente do pglite, que foi o que motivou a abertura do código do pgmock. O pgmock executa o Postgres original dentro de um emulador x86, enquanto o pglite compila diretamente um fork do Postgres para WASM nativo, ficando mais rápido e leve
    Mas o pglite só oferece suporte a modo de usuário único e a algumas extensões, então não dá para conectar com um cliente Postgres comum, e isso é bem importante em testes E2E
    Em teoria, talvez fosse possível adaptar isso para executar qualquer imagem Docker numa plataforma WebAssembly; fico curioso se há algum alvo específico que vocês gostariam de ver

    • Trabalho muito legal. É verdade que o PGlite hoje só suporta usuário único, e isso pode ser um problema para usá-lo em testes de integração em alguns ambientes
      Tenho algumas ideias de como adicionar um modo de múltiplas conexões, mas isso ainda deve levar um tempo. O PGlite também tem outras limitações relacionadas ao modo de usuário único; por exemplo, ainda não suporta pg_notify, embora isso também esteja nos planos para ser corrigido
      Por outro lado, este projeto é muito mais próximo do Postgres real, então há uma boa chance de simplesmente funcionar. Esses projetos de Postgres em memória parecem capazes de reduzir o tempo de execução dos testes para menos de um quarto, então vejo muito potencial na área de testes
      Opinião de alguém que trabalha no PGlite
    • A ideia de executar imagens Docker em WASM parece promissora para vários problemas
      Recentemente quis rodar um pipeline de FFMPEG/SoX no cliente, mas havia dependências demais para recompilar facilmente com Emscripten. Fico curioso se essa abordagem também poderia ajudar em casos assim
    • Se conseguisse oferecer suporte à extensão pgvector, isso poderia virar um banco vetorial extremamente rápido com todo o poder do Postgres
      Graças aos recursos relacionais, daria para adicionar e consultar junto toda a rica metadata específica de domínio que normalmente fica num banco relacional
    • Só como referência, a demo online parece quebrar com consultas que ela não gosta
      Ao executar select foo();, aparece Error.captureStackTrace is not a function, no Firefox 124.0.2 no Linux
    • Excelente, mas fico pensando se o próprio conceito de teste E2E não implica usar o ambiente real sem substituir componentes por mocks
  • Fico me perguntando por que não simplesmente executar os arquivos do Postgres em um ramdisk
    Atualização: como isso pode ser executado em navegador/Node, imagino que o teste consiga criar, atualizar e excluir. Sou backend demais para entender bem a vantagem em relação a um ambiente normal de desenvolvimento. Seria legal explicar onde, quando e como isso é melhor

    • Dentro do emulador, no fim das contas acontece algo bem parecido. O disco emulado é um sistema de arquivos 9P em memória
      O motivo de fazer isso em WebAssembly é tornar o comportamento mais portátil entre plataformas, arquiteturas, navegador e até ambientes de edge, além de eliminar dependências externas a ponto de nem precisar de Docker
      Como o emulador pode inicializar diretamente a partir de um estado já em execução, o boot do banco emulado fica mais rápido do que subir um banco real ou um contêiner Docker. Mas isso foi mais um efeito colateral feliz do que um objetivo de projeto
    • Também não tenho certeza. Parece haver código desnecessário demais, como emulador, stack de rede etc.
      Fico pensando se não bastaria usar algo como https://testcontainers.com/. Não sei se ter um engine de contêiner como dependência externa é algo tão ruim assim
    • O objetivo do teste E2E é testar o sistema em seu estado real. Como é uma emulação do ambiente de produção, dá até para verificar o que acontece quando falta energia ou o disco enche
      No momento em que você enfia mocks ali dentro, isso vira teste unitário. Continua útil, mas não é a mesma coisa. Um dos pontos centrais de E2E é saber que o teste está correto justamente porque não há mocks. Em vez de testar o Postgres, você está testando este sistema toda vez
      Se você estiver criando um PG embarcado, leve e de baixo desempenho, faz sentido como teste de validação antes dos testes E2E reais, que são lentos. Eu também tenho esse tipo de uso
      Fora isso, é um projeto legal e parece ser uma ferramenta útil quando você precisa de um shim de PG
    • Pode ser útil para isolamento de testes. Quando migrei um backend Redis para FakeRedis nos testes, o ruído da suíte caiu bastante
      No Postgres estamos usando savepoints, e mesmo em ramdisk isso não é tão rápido
  • Antigamente eu rodava todo tipo de servidor falso em memória nos testes. Hoje em dia, uso https://testcontainers.com para executar os alvos reais

  • Se um desenvolvedor de Prisma/Node.js só quiser um “Postgres enlatado” para desenvolvimento local, a versão recente em modo servidor do PGlite, pglite-server, pode ser uma opção melhor: https://github.com/kamilogorek/pglite-server
    É mais rápido e pode manter os dados no sistema de arquivos, mas sob carga maior é menos estável do que um servidor de teste E2E baseado em emulador x86 completo. O pglite-server usava apenas 150MB de memória, enquanto o pgmock-server usava 830MB
    Basta fazer checkout de um novo .env.local com dotenv e trocar o DATABASE_URL em todos os scripts de execução do package.json do nextjs/prisma
    DATABASE_URL="postgresql://postgres@localhost:5432/awesomeproject"
    "db:pushlocal": "dotenv -e .env.local -- pnpm prisma db push"
    É muito fácil plugar isso em qualquer projeto, e dá para entender por que a Neon está patrocinando esse espaço

  • Não quero jogar água fria, mas eu não pretendo usar isso
    Pode funcionar para aplicações simples, mas quando a complexidade aumenta — como risco de deadlock ou dependência do formato do banco de dados — até pequenas diferenças de comportamento podem virar problemas críticos, então o valor disso diminui
    Hoje em dia prefiro ambientes de E2E com restrição de recursos. Isso dá ao executor de testes local a chance de quebrar quando alguém escreve um código absurdamente ineficiente
    Além disso, a abordagem de criar um snapshot do banco de dados após alguns segundos e distribuir esse snapshot para as partições de teste é muito rápida, e muitas vezes já reduziu vários minutos da suíte de testes
    É uma ideia interessante e deve ser uma boa experiência de aprendizado, mas acho que o público-alvo é limitado

  • O título é um pouco confuso. Se “foi feito na empresa”, então assumindo que foram usados recursos da empresa, parece que a propriedade intelectual desse projeto não pertence ao empregador?
    Nesse caso, fico curioso se tecnicamente ele pode mesmo ser publicado como open source

    • Como é uma startup, publicar como open source foi tão simples quanto obter a aprovação do restante do time
    • O repositório é de propriedade da Stackframe e o arquivo LICENSE diz Copyright 2024 Stackframe.. Parece que o autor trabalha na Stackframe
  • Fico curioso sobre como isso se compara ao modo de compatibilidade com Postgres do H2

    • Posso estar errado, mas acho que no H2 não dá para usar stored procedures do PostgreSQL
  • Bem legal. Se puder responder, tenho algumas curiosidades
    O que levou a empresa a criar este projeto, se rodar o Postgres em um contêiner Docker era lento demais
    Também queria saber como a configuração de CI para testes E2E mudou antes e depois de integrar o pgmock ao fluxo
    Também tenho curiosidade se foi difícil migrar para essa solução

  • Se você fizer um dump dos dados de produção, remover todos os dados sensíveis e depois der truncate nas tabelas desnecessárias, como tabelas de log, terá uma boa cópia para desenvolvimento
    Depois é só replicar isso para desenvolvimento, QA, E2E etc. O que o E2E precisa é exatamente dessas extensões, triggers, funções, views, índices e dados

  • Fico pensando se não daria para simplesmente usar Docker e ter um banco de testes separado
    No Elixir, é assim que fazem, e o framework de testes envolve cada teste em uma transação e faz rollback para isolamento. Seria interessante entender qual é a vantagem dessa abordagem