4 Punkte von GN⁺ 2024-04-08 | 1 Kommentare | Auf WhatsApp teilen
  • pgmock ist ein In-Memory-Mock-Server für PostgreSQL für Unit- und E2E-Tests und läuft ohne externe Abhängigkeiten in Node.js und im Browser per WebAssembly
  • Nutzer von node-postgres können sich über ein Konfigurationsobjekt verbinden, das keinen Port öffnet; dieser Ansatz funktioniert auch im Browser
  • Im Browser können Webapps keine TCP-Ports öffnen, aber PostgresMock.createSocket und die node-postgres-Konfiguration lassen sich verwenden; wenn Bundler statische Imports analysieren, können Warnungen zu optionalen Node.js-Modulen auftreten
  • Die Implementierung führt derzeit einen PostgreSQL-Server in einem x86-Emulator aus und priorisiert die Vermeidung von Verhaltensunterschieden zwischen Tests und Produktion gegenüber Performance
  • Langfristig ist geplant, beide Ansätze anzubieten, sobald ein nativer PostgreSQL-WASM-Fork ausgereift ist, und später natives WASM zum Standard zu machen

Was pgmock bietet

  • pgmock ist ein In-Memory-Mock-Server für PostgreSQL für Unit- und E2E-Tests
  • Es benötigt keine externen Abhängigkeiten und läuft sowohl in Node.js als auch im Browser innerhalb von WebAssembly
  • Die Installation ist über npm möglich
npm install pgmock

Grundlegender Ablauf

  • Der In-Memory-Server wird mit PostgresMock.create() erstellt; mit listen(5432) erhält man einen Connection String
import { PostgresMock } from "pgmock";

const mock = await PostgresMock.create();
const connectionString = await mock.listen(5432);
  • Bei Verwendung von node-postgres liefert mock.getNodePostgresConfig() ein Konfigurationsobjekt, mit dem man sich ohne Port-Listening verbinden kann
  • Nach Abschluss der Arbeit wird empfohlen, mock.destroy() aufzurufen, um Ressourcen freizugeben
mock.destroy();

Browser-Unterstützung und Unterschiede zu pglite

  • pgmock unterstützt die Browser-Umgebung vollständig
  • Webapps können keine TCP-Ports öffnen, aber PostgresMock.createSocket und die node-postgres-Konfiguration können verwendet werden
  • Wenn Bundler Imports statisch analysieren, können Warnungen wegen fehlender optionaler Node.js-Module auftreten; ein Webpack-Konfigurationsbeispiel findet sich in examples/web-demo/next.config.mjs
  • Wenn man im Browser nur eine Datenbank ausführen möchte, kann pglite eine Option sein
    • pglite ist schneller und schlanker, hat aber einen eingeschränkten Funktionsumfang
    • pgmock ist darauf ausgelegt, in Testumgebungen die gewünschte Funktionsgleichheit mit Produktions-PostgreSQL zu erreichen

Wie PostgreSQL in WebAssembly ausgeführt wird

  • Es gibt zwei Möglichkeiten, PostgreSQL in WebAssembly auszuführen
  • Der native WASM-Fork-Ansatz ist schneller und verbraucht deutlich weniger Speicher, unterstützt aber nur den Single-User-Modus und keine Verbindungen oder Erweiterungen
  • pgmock nutzt derzeit den x86-Emulator-Ansatz
    • Ziel ist es, Abweichungen zwischen Tests und Produktion zu vermeiden
    • Denn in Tests ist Performance in der Regel kein großes Problem
  • Mittelfristig ist geplant, beide Optionen anzubieten, sobald der native PostgreSQL-WASM-Fork ausgereift ist
  • Danach soll natives WASM zum Standard werden; abgesehen von der internen API PostgresMock.subtle werden nicht viele größere Breaking Changes erwartet

Unterschiede zu bestehenden PostgreSQL-Projekten im Browser

  • pgmock bietet vollständige Funktionskompatibilität innerhalb einer JavaScript-Runtime und verlässt sich für die Kommunikation nicht auf einen Netzwerk-Proxy
  • Es simuliert den Netzwerk-Stack in JavaScript, sodass er sich wie ein echtes Netzwerk verhält, und kann TCP-Verbindungen auch auf Plattformen simulieren, die keinen Zugriff auf Raw Sockets erlauben

Erweiterbarkeit und verwandte Projekte

  • Andere Docker-Images oder Datenbanken könnten theoretisch ebenfalls ausgeführt werden, wurden aber nicht getestet
  • Als verwandte Implementierungen und zugrunde liegende Projekte werden genannt
    • v86: x86-Emulator
    • Supabase & Snaplet: Grundlage für den Ansatz, PostgreSQL innerhalb von WebAssembly auszuführen
    • Stackframe: Wird als Unternehmen erwähnt, das während der Entwicklung von pgmock Gehalt gezahlt hat

1 Kommentare

 
GN⁺ 2024-04-08
Meinungen auf Hacker News
  • Wir haben bei uns im Unternehmen über mehrere Monate hinweg eine In-Memory-Version von Postgres gebaut, die funktionsgleich mit unserer Produktionsdatenbank ist.
    Der Vorteil ist, dass kein externer Prozess und kein Proxy nötig sind. Wenn eine Plattform WASM ausführen kann, lässt sich pgmock auch in Node.js oder im Browser ausführen, und eine neue Datenbank mit Mock-Daten anzulegen ist so einfach wie das Erstellen eines JavaScript-Objekts.
    Es unterscheidet sich etwas von pglite, das uns dazu gebracht hat, pgmock als Open Source zu veröffentlichen. pgmock führt das originale Postgres in einem x86-Emulator aus, während pglite einen Postgres-Fork direkt zu nativem WASM kompiliert und dadurch schneller und schlanker ist.
    Allerdings unterstützt pglite nur den Single-User-Modus und einige Extensions, sodass man sich nicht mit normalen Postgres-Clients verbinden kann; für E2E-Tests ist das ziemlich wichtig.
    Theoretisch ließe es sich so umbauen, dass auf einer WebAssembly-Plattform jedes beliebige Docker-Image ausgeführt werden kann. Ich frage mich, ob es konkrete Ziele gibt, die ihr gern sehen würdet.

    • Tolle Arbeit. Es stimmt, dass PGlite derzeit nur Single-User unterstützt, und das kann in manchen Umgebungen für Integrationstests problematisch sein.
      Wir denken über ein paar Möglichkeiten nach, einen Modus mit mehreren Verbindungen hinzuzufügen, aber das wird wohl etwas dauern. PGlite hat außerdem weitere Einschränkungen im Zusammenhang mit dem Single-User-Modus; zum Beispiel wird pg_notify noch nicht unterstützt, und auch das wollen wir beheben.
      Dieses Projekt hier ist dagegen deutlich näher an echtem Postgres und wird daher wahrscheinlich einfach funktionieren. Solche In-Memory-Postgres-Projekte scheinen die Laufzeit von Tests auf ein Viertel oder weniger reduzieren zu können und haben im Testing-Bereich großes Potenzial.
      Das ist meine Einschätzung als jemand, der an PGlite arbeitet.
    • Die Idee, Docker-Images in WASM auszuführen, wirkt für viele Probleme vielversprechend.
      Kürzlich wollte ich clientseitig eine FFMPEG/SoX-Pipeline laufen lassen, aber wegen der vielen Abhängigkeiten war es nicht einfach, sie mit Emscripten neu zu kompilieren. Ich frage mich, ob dieser Ansatz auch in solchen Fällen helfen könnte.
    • Wenn die pgvector-Extension unterstützt werden könnte, wäre das eine sehr schnelle Vektordatenbank mit der ganzen Stärke von Postgres.
      Dank der relationalen Funktionen könnte man die reichhaltigen domänenspezifischen Metadaten hinzufügen, die normalerweise in relationalen Datenbanken liegen, und sie gemeinsam abfragen.
    • Zur Info: Die Online-Demo scheint bei Abfragen kaputtzugehen, die sie nicht mag.
      Wenn man select foo(); ausführt, erscheint Error.captureStackTrace is not a function; das passiert unter Firefox 124.0.2 auf Linux.
    • Großartig, aber bedeutet das Konzept von E2E-Tests nicht eigentlich, dass man die echte Umgebung verwendet, statt Komponenten durch Mocks zu ersetzen?
  • Ich frage mich, ob man nicht einfach die Postgres-Dateien auf eine ramdisk legen und es dort ausführen kann.
    Update: Offenbar kann es in Browser-/Node-Umgebungen laufen, sodass Tests es erzeugen, aktualisieren und löschen können. Ich bin wohl zu sehr Backend-Entwickler und erkenne den Vorteil gegenüber einer normalen Entwicklungsumgebung nicht ganz. Eine Erklärung, wo, wann und wie das besser ist, wäre hilfreich.

    • Im Emulator passiert im Grunde etwas Ähnliches. Die emulierte Platte ist ein In-Memory-9P-Dateisystem.
      Der Grund für WebAssembly ist, dass sich das Verhalten über Plattformen, Architekturen und bis hin zu Browser- oder Edge-Umgebungen portabler vereinheitlichen lässt und eine Konfiguration ohne externe Abhängigkeiten möglich wird, nicht einmal Docker ist nötig.
      Da der Emulator direkt in einen bereits laufenden Zustand booten kann, startet die emulierte Datenbank schneller als eine echte Datenbank oder ein Docker-Container. Das ist allerdings eher ein glücklicher Nebeneffekt als ein Designziel.
    • Ich weiß auch nicht so recht. Es wirkt, als sei da viel zu viel unnötiger Code dabei: Emulator, Netzwerk-Stack usw.
      Warum nicht etwas wie https://testcontainers.com/ verwenden? Ich frage mich, ob es wirklich so schlimm ist, dass eine Container-Engine eine externe Abhängigkeit ist.
    • Der Zweck von E2E-Tests ist es, das System in einem realen Zustand zu testen. Weil es eine Emulation der Produktionsumgebung ist, kann man auch prüfen, was passiert, wenn der Stecker gezogen wird oder die Platte voll ist.
      Sobald man dort Mocks hineinschiebt, wird es zu einem Unit-Test. Das ist nützlich, aber nicht dasselbe. Einer der Kernpunkte von E2E ist, dass man weiß, dass der Test korrekt ist, weil es keine Mocks gibt. Hier testet man nicht Postgres, sondern jedes Mal dieses System.
      Wenn man ein PG für Embedded-, Lightweight- oder leistungsschwache Systeme baut, ergibt es als Validierungstest vor langsamen echten E2E-Tests Sinn. Für so etwas hätte ich auch Verwendung.
      Ansonsten ist es ein cooles Projekt und sieht nach einem Werkzeug aus, das man nutzen kann, wenn man einen PG-Shim braucht.
    • Es kann für Test-Isolation nützlich sein. Als wir unser Redis-Backend in Tests auf FakeRedis umgestellt haben, wurde die Testsuite deutlich weniger verrauscht.
      Für Postgres verwenden wir savepoints, aber selbst auf einer ramdisk ist das nicht besonders schnell.
  • Früher haben wir in Tests alle möglichen selbstgebauten gefälschten In-Memory-Server laufen lassen. Heute starten wir mit https://testcontainers.com das echte Ziel.

  • Wenn Prisma-/Node.js-Entwickler für die lokale Entwicklung einfach nur „Postgres aus der Dose“ wollen, könnte die kürzlich erschienene Server-Version von PGlite, pglite-server, besser geeignet sein: https://github.com/kamilogorek/pglite-server
    Sie ist schneller und kann Daten im Dateisystem persistent halten, ist bei hoher Last aber weniger stabil als ein vollständiger E2E-Testserver auf Basis eines x86-Emulators. pglite-server nutzt nur 150 MB Speicher, während pgmock-server 830 MB verbraucht hat.
    Man muss per dotenv eine neue .env.local auschecken und in allen nextjs/prisma-Ausführungsskripten in package.json die DATABASE_URL ändern.
    DATABASE_URL="postgresql://postgres@localhost:5432/awesomeproject"
    "db:pushlocal": "dotenv -e .env.local -- pnpm prisma db push"
    Es lässt sich sehr leicht in jedes Projekt einbinden, und ich verstehe, warum Neon diesen Bereich sponsert.

  • Ich will kein Wasser in den Wein gießen, aber ich habe nicht vor, das zu verwenden.
    Für einfache Anwendungen könnte es funktionieren, aber sobald die Komplexität steigt – etwa durch Deadlock-Risiken oder Abhängigkeiten von der konkreten Datenbankform –, können schon kleine Verhaltensunterschiede zu gravierenden Problemen werden, wodurch der Nutzen sinkt.
    Heutzutage bevorzuge ich E2E-Umgebungen mit Ressourcenbeschränkungen, weil ein lokaler Test-Runner so die Chance hat, kaputtzugehen, wenn jemand extrem ineffizienten Code schreibt.
    Außerdem ist es sehr schnell, die Datenbank nach ein paar Sekunden als Snapshot zu erstellen und diesen Snapshot auf Test-Partitionen zu verteilen; das hat bei Test-Suites oft mehrere Minuten eingespart.
    Eine interessante Idee und sicher eine gute Lernerfahrung, aber ich denke, die Zielgruppe ist begrenzt.

  • Der Titel ist etwas verwirrend. Wenn es „bei der Arbeit gebaut“ wurde, würde ich unter der Annahme, dass Firmenressourcen genutzt wurden, vermuten, dass die geistigen Eigentumsrechte an diesem Projekt beim Arbeitgeber liegen.
    Dann frage ich mich, ob es technisch gesehen als Open Source veröffentlicht werden darf.

    • Da es ein Startup ist, war die Open-Source-Veröffentlichung so einfach, wie die Zustimmung der übrigen Teammitglieder einzuholen.
    • Das Repository gehört Stackframe, und in der LICENSE-Datei steht Copyright 2024 Stackframe.. Der Autor scheint bei Stackframe zu arbeiten.
  • Ich frage mich, wie sich das mit dem Postgres-Kompatibilitätsmodus von H2 vergleicht.

    • Ich kann mich irren, aber ich glaube, in H2 kann man keine PostgreSQL-Stored-Procedures verwenden.
  • Ziemlich cool. Falls du antworten kannst, hätte ich ein paar Fragen.
    Mich würde interessieren, was in der Firma der Anlass war, dieses Projekt zu bauen, und ob das Ausführen von Postgres in einem Docker-Container zu langsam war.
    Außerdem wüsste ich gern, wie sich eure CI-Konfiguration für E2E-Tests vor und nach der Integration von pgmock in den Ablauf verändert hat.
    Und ich frage mich, ob der Umstieg auf diese Lösung schwierig war.

  • Wenn man Produktionsdaten dumpt, alle sensiblen Daten entfernt und anschließend nicht benötigte Tabellen wie Log-Tabellen per truncate leert, erhält man eine gute Kopie für die Entwicklung.
    Diese kann man dann in Entwicklung, QA, E2E usw. replizieren. Was man für E2E braucht, sind genau diese Extensions, Trigger, Funktionen, Views, Indizes und Daten.

  • Ich frage mich, ob man nicht einfach Docker verwenden und eine separate Testdatenbank haben kann.
    Elixir macht das so, und das Test-Framework kapselt jeden Test in eine Transaktion und rollt sie zur Isolation zurück. Es wäre interessant zu erfahren, welchen Vorteil dieser Ansatz hat.