4 points par GN⁺ 2024-04-08 | 1 commentaires | Partager sur WhatsApp
  • pgmock est un serveur PostgreSQL simulé en mémoire pour les tests unitaires et les tests E2E, qui s’exécute en WebAssembly dans Node.js et dans le navigateur sans dépendances externes
  • Les utilisateurs de node-postgres peuvent se connecter via un objet de configuration obtenu sans ouvrir de port, et cette méthode fonctionne aussi dans le navigateur
  • Dans le navigateur, une web app ne peut pas ouvrir de port TCP, mais peut utiliser PostgresMock.createSocket et la configuration node-postgres ; si le bundler analyse les imports statiquement, des avertissements peuvent apparaître au sujet de modules Node.js optionnels
  • L’implémentation exécute actuellement le serveur PostgreSQL dans un émulateur x86, en privilégiant l’absence de différences de comportement entre les tests et la production plutôt que les performances
  • À long terme, lorsqu’un fork WASM natif de PostgreSQL sera suffisamment mature, le projet prévoit de proposer les deux approches, puis de basculer le mode natif WASM comme option par défaut

Ce que propose pgmock

  • pgmock est un serveur PostgreSQL simulé en mémoire pour les tests unitaires et les tests E2E
  • Il ne nécessite aucune dépendance externe et s’exécute dans WebAssembly à la fois sur Node.js et dans le navigateur
  • L’installation se fait via npm
npm install pgmock

Flux d’utilisation de base

  • Le serveur en mémoire se crée avec PostgresMock.create(), et listen(5432) permet d’obtenir une chaîne de connexion
import { PostgresMock } from "pgmock";

const mock = await PostgresMock.create();
const connectionString = await mock.listen(5432);
  • Si vous utilisez node-postgres, mock.getNodePostgresConfig() fournit un objet de configuration permettant de se connecter sans écouter sur un port
  • Une fois le travail terminé, il est recommandé d’appeler mock.destroy() pour libérer les ressources
mock.destroy();

Prise en charge du navigateur et différence avec pglite

  • pgmock prend entièrement en charge les environnements navigateur
  • Une web app ne peut pas ouvrir de port TCP, mais elle peut utiliser PostgresMock.createSocket et la configuration node-postgres
  • Si le bundler analyse les imports de manière statique, il peut émettre des avertissements indiquant l’absence de modules Node.js optionnels ; un exemple de configuration Webpack se trouve dans examples/web-demo/next.config.mjs
  • Si vous souhaitez uniquement exécuter une base de données dans le navigateur, vous pouvez envisager pglite
    • pglite est plus rapide et plus léger, mais son ensemble de fonctionnalités est limité
    • pgmock a été conçu avec pour objectif une parité fonctionnelle avec PostgreSQL en production dans un environnement de test

Comment PostgreSQL est exécuté dans WebAssembly

  • Il existe deux approches pour exécuter PostgreSQL dans WebAssembly
  • L’approche par fork natif WASM est plus rapide et consomme beaucoup moins de mémoire, mais elle ne prend en charge que le mode mono-utilisateur, sans connexions ni extensions
  • pgmock utilise actuellement l’approche par émulateur x86
    • Le but est d’éviter les écarts entre les tests et la production
    • En test, les performances ne sont généralement pas le principal problème
  • À moyen terme, lorsque le fork WASM natif de PostgreSQL sera plus mature, le projet prévoit de proposer les deux options
  • Ensuite, il prévoit de faire du WASM natif l’option par défaut, et n’anticipe pas beaucoup de breaking changes en dehors de l’API interne PostgresMock.subtle

Différences avec les projets PostgreSQL existants dans le navigateur

  • pgmock offre une compatibilité fonctionnelle complète à l’intérieur du runtime JavaScript et ne dépend pas d’un proxy réseau pour la communication
  • Il simule la pile réseau en JavaScript pour reproduire le comportement d’un vrai réseau, et peut ainsi simuler des connexions TCP même sur les plateformes qui n’autorisent pas l’accès direct aux sockets bruts

Extensibilité et projets liés

  • D’autres images Docker ou bases de données pourraient théoriquement être exécutées, mais cela n’a pas été testé
  • Les implémentations et projets de base suivants sont mentionnés
    • v86 : émulateur x86
    • Supabase & Snaplet : base de l’approche consistant à exécuter PostgreSQL dans WebAssembly
    • Stackframe : société mentionnée comme ayant financé le développement de pgmock

1 commentaires

 
GN⁺ 2024-04-08
Avis sur Hacker News
  • Depuis quelques mois, nous développions en interne une version en mémoire de Postgres, avec une parité fonctionnelle avec la base de données de production.
    L’avantage est qu’elle ne nécessite ni processus externe ni proxy. Si la plateforme peut exécuter WASM, on peut faire tourner pgmock dans Node.js ou dans un navigateur, et créer une nouvelle base de données avec des données mockées est aussi simple que de créer un objet JavaScript.
    C’est un peu différent de pglite, qui nous a poussés à rendre pgmock open source. pgmock exécute le Postgres original dans un émulateur x86, tandis que pglite compile directement un fork de Postgres en WASM natif, ce qui le rend plus rapide et plus léger.
    En revanche, pglite ne prend en charge que le mode mono-utilisateur et certaines extensions, donc il n’est pas possible de s’y connecter avec un client Postgres standard, ce qui est assez important pour les tests E2E.
    En théorie, on pourrait l’adapter pour exécuter n’importe quelle image Docker sur une plateforme WebAssembly ; je serais curieux de savoir s’il y a des cibles précises que vous aimeriez voir.

    • Super boulot. Il est vrai que PGlite ne prend actuellement en charge que le mode mono-utilisateur, et cela peut poser problème pour l’utiliser dans des tests d’intégration dans certains environnements.
      Nous réfléchissons à plusieurs façons d’ajouter un mode multi-connexion, mais cela risque de prendre un peu de temps. PGlite a aussi d’autres limitations liées au mode mono-utilisateur : par exemple, pg_notify n’est pas encore pris en charge, et nous prévoyons également de corriger cela.
      En revanche, ce projet est beaucoup plus proche d’un vrai Postgres, donc il a de bonnes chances de fonctionner tel quel. Ces projets de Postgres en mémoire semblent capables de réduire les temps d’exécution des tests à moins d’un quart, et ils ont un gros potentiel dans le domaine du test.
      C’est mon point de vue en tant que personne travaillant sur PGlite.
    • L’idée de faire tourner des images Docker dans WASM semble prometteuse pour de nombreux problèmes.
      Récemment, je voulais exécuter un pipeline FFMPEG/SoX côté client, mais il y avait tellement de dépendances qu’il était difficile de tout recompiler facilement avec Emscripten. Je me demande si cette approche pourrait aussi aider dans ce genre de cas.
    • Si l’extension pgvector pouvait être prise en charge, cela pourrait devenir une base de données vectorielle très rapide, avec toute la puissance de Postgres.
      Grâce aux fonctionnalités relationnelles, on pourrait ajouter et interroger en même temps les riches métadonnées métier spécifiques que l’on trouve généralement dans les bases de données relationnelles.
    • Pour info, la démo en ligne semble casser sur les requêtes qu’elle n’aime pas.
      Quand on exécute select foo();, on obtient Error.captureStackTrace is not a function, sous Firefox 124.0.2 sur Linux.
    • C’est excellent, mais il me semble que le concept même de test E2E implique d’utiliser l’environnement réel plutôt que de remplacer des composants par des mocks.
  • Je me demande s’il ne suffirait pas de lancer Postgres en mettant ses fichiers sur un ramdisk.
    Mise à jour : comme cela peut tourner dans un navigateur/Node, les tests peuvent apparemment créer, modifier et supprimer des bases. Je suis trop développeur backend pour bien voir l’avantage par rapport à un environnement de développement classique. J’aimerais bien qu’on m’explique où, quand et en quoi c’est mieux.

    • À l’intérieur de l’émulateur, il se passe globalement quelque chose de similaire. Le disque émulé est un système de fichiers 9P en mémoire.
      La raison du choix de WebAssembly est que cela permet de rendre le comportement plus portable entre plateformes, architectures, navigateurs et environnements edge, tout en obtenant une configuration sans dépendance externe, qui ne nécessite même pas Docker.
      Comme l’émulateur permet de démarrer directement depuis un état déjà en cours d’exécution, le démarrage de la base émulée est plus rapide que le lancement d’une vraie base de données ou d’un conteneur Docker. Cela dit, c’est plutôt un effet obtenu par chance qu’un objectif de conception.
    • Je ne suis pas sûr non plus. Il semble y avoir énormément de code inutile : l’émulateur, la pile réseau, etc.
      Je me demande pourquoi ne pas utiliser quelque chose comme https://testcontainers.com/. Est-ce vraiment si grave qu’un moteur de conteneurs soit une dépendance externe ?
    • L’objectif des tests E2E est de tester le système dans son état réel. Comme il s’agit d’une émulation de l’environnement de production, on peut aussi vérifier ce qui se passe si on coupe l’alimentation ou si le disque est plein.
      Dès qu’on y introduit des mocks, cela devient un test unitaire. C’est utile, mais ce n’est pas la même chose. L’un des points clés de l’E2E est justement que l’absence de mocks permet de savoir que le test est fidèle. Ici, on ne teste pas Postgres : on teste ce système à chaque fois.
      Si l’on développe un PG embarqué, léger et peu performant, cela peut avoir du sens comme test de validation avant de lents vrais tests E2E. J’ai moi-même un cas d’usage de ce type.
      À part ça, c’est un beau projet, et cela ressemble à un outil utile quand on a besoin d’un shim PG.
    • Cela peut être utile pour l’isolation des tests. Quand nous avons remplacé le backend Redis par FakeRedis dans les tests, cela a pas mal réduit le bruit dans la suite de tests.
      Pour Postgres, nous utilisons des savepoints, mais même sur ramdisk ce n’est pas si rapide.
  • Avant, dans les tests, on faisait tourner toutes sortes de faux serveurs en mémoire personnalisés. Aujourd’hui, on exécute la vraie cible avec https://testcontainers.com.

  • Si un développeur Prisma/Node.js veut simplement un « Postgres en boîte » pour le développement local, la version récemment transformée en serveur de PGlite, pglite-server, peut être un meilleur choix : https://github.com/kamilogorek/pglite-server
    C’est plus rapide et cela peut conserver les données sur le système de fichiers, mais en cas d’utilisation intensive c’est moins stable qu’un serveur de tests E2E basé sur un émulateur x86 complet. pglite-server n’utilisait que 150 Mo de mémoire, contre 830 Mo pour pgmock-server.
    Il suffit de checkout un nouveau .env.local avec dotenv et de changer le DATABASE_URL de tous les scripts d’exécution package.json nextjs/prisma.
    DATABASE_URL="postgresql://postgres@localhost:5432/awesomeproject"
    "db:pushlocal": "dotenv -e .env.local -- pnpm prisma db push"
    C’est très facile à intégrer à n’importe quel projet, et je comprends que Neon sponsorise ce domaine.

  • Je ne veux pas jouer les rabat-joie, mais je ne pense pas utiliser ça.
    Pour une application simple, ça peut fonctionner, mais dès que la complexité augmente — risque de deadlock, dépendance à la forme de la base de données, etc. — de petites différences de comportement peuvent devenir des problèmes critiques, ce qui en réduit l’intérêt.
    Ces derniers temps, je préfère les environnements E2E avec contraintes de ressources. Cela donne au runner de tests local une chance de casser quand quelqu’un écrit du code vraiment inefficace.
    Par ailleurs, créer un snapshot de la base de données au bout de quelques secondes puis déployer ce snapshot sur les partitions de test est très rapide, et m’a souvent fait gagner plusieurs minutes sur des suites de tests.
    C’est une idée intéressante et une bonne expérience d’apprentissage, mais je pense que le public visé est limité.

  • Le titre est un peu ambigu. Si ça a été « construit au travail », j’aurais tendance à penser que, puisqu’il a utilisé les ressources de l’entreprise, la propriété intellectuelle de ce projet appartient à l’employeur.
    Je me demande donc si, techniquement, il a le droit de le publier en open source.

    • Comme c’est une startup, l’ouverture en open source a été aussi simple que d’obtenir l’accord du reste de l’équipe.
    • Le dépôt appartient à Stackframe, et le fichier LICENSE indique Copyright 2024 Stackframe.. L’auteur semble travailler chez Stackframe.
  • Je me demande comment cela se compare au mode de compatibilité Postgres de H2.

    • Je peux me tromper, mais je ne pense pas qu’on puisse utiliser des procédures stockées PostgreSQL dans H2.
  • Plutôt chouette. Si vous pouvez répondre, j’ai quelques questions.
    Je me demande ce qui a poussé l’entreprise à créer ce projet, et si lancer Postgres dans un conteneur Docker était trop lent.
    J’aimerais aussi savoir en quoi la configuration CI pour les tests E2E a changé avant et après l’intégration de pgmock dans le workflow.
    Je me demande également si la migration vers cette solution a été difficile.

  • Si vous dumpez les données de production, supprimez toutes les données sensibles, puis tronquez les tables inutiles comme les tables de logs, vous obtenez une bonne copie pour le développement.
    Il suffit ensuite de la répliquer vers le développement, la QA, l’E2E, etc. Ce dont l’E2E a besoin, ce sont précisément ces extensions, triggers, fonctions, vues, index et données.

  • Je me demande s’il ne suffirait pas d’utiliser Docker et une base de données de test séparée.
    Elixir fait comme ça, et le framework de test enveloppe chaque test dans une transaction puis effectue un rollback pour assurer l’isolation. Ce serait intéressant de savoir quel est l’avantage de cette approche.