2 Punkte von GN⁺ 2024-10-16 | 1 Kommentare | Auf WhatsApp teilen
  • Ein Hub, der die WebAssembly- und JavaScript-API-Dokumentation an einem Ort bündelt, für Entwickler, die sqlite3 direkt verwenden möchten in aktuellen Browsern mit WASM-Unterstützung
  • Für den schnellen Einstieg beginnt man mit dem 3-Schritte-HOWTO; außerdem lassen sich Installations- und Bereitstellungspfade über Dokumente zu Downloads, npm-Modul, Build und Emscripten-Details nachvollziehen
  • Die API-Dokumentation reicht von Ladeverfahren und Client-Code-Rezepten über persistenten Speicher und C-Strukturen in der JS-API bis hin zum Schreiben virtueller Tabellen und Table-Valued Functions
  • Reale Einsatzbeispiele wie SQLime, Evolu, SQLiteNext, sqlite-wasm-esm und sqlite-wasm-http werden zusammen mit früheren Projekten wie sql.js, wa-sqlite und absurd-js aufgeführt
  • Verknüpft Materialien zu Browser-SQLite, OPFS, IndexedDB und WASI, sodass sich Implementierungsoptionen für den Einsatz von SQLite im Web schnell vergleichen lassen

Umfang der sqlite3-WASM-Dokumentation

Anwendungsfälle und angrenzendes Ökosystem

  • Drittprojekte, die dieses Projekt nutzen

    • SQLime: Bietet eine Datenbank-Browser-Oberfläche
    • Evolu: Eine Local-first-Plattform mit Fokus auf Privatsphäre, Benutzerfreundlichkeit und fehlenden Vendor Lock-in
    • SQLiteNext: Demo zur Integration dieses Projekts mit next.js
    • sqlite-wasm-esm: Zeigt, wie dieses Projekt im Vite-Toolchain eingesetzt wird
    • sqlite-wasm-http: SQLite-VFS, das schreibgeschützten Zugriff auf direkt per HTTP bereitgestellte Datenbanken bietet
  • Vorarbeiten und benachbarte Projekte

    • Alon Zakais sql.js: Gilt, abgesehen von WebSQL, als der erste Fall direkter sqlite3-Nutzung im Browser und lässt sich bis zu einem Commit von 2012 zurückverfolgen
    • Roy Hashimotos wa-sqlite: Gilt als erste Implementierung von OPFS storage für sqlite3-Datenbanken und umfasst auch Arbeiten rund um SQLite VFS auf der JS-Seite
    • James Longs absurd-js: Zeigt, wie sich sqlite3-Datenbanken in einer IndexedDB-Datenbank speichern lassen
    • postgres-wasm: Führt einen Postgres database server im Browser aus
    • Jaccwabyt: Eine kleine JS-Bibliothek zur Manipulation von in WASM gehosteten C-Strukturen per JS-Code; wurde zur Unterstützung der OPFS sqlite3_vfs-Implementierung dieses Projekts erstellt
    • CoWasm: „Collaborative WebAssembly for Servers and Browsers“ und enthält in der Demo-App einen WASM-Build der sqlite3-shell-Anwendung
    • absurder-js: Nicholas G. Piescos Neuinterpretation von absurd-js; dazu gibt es auch eine Vorstellung im SQLite forum
    • sqlite-wasm-rs: Bietet Rust-Bindings für SQLite für wasm32-unknown-unknown-Builds
  • Externe Dokumentation und Artikel

1 Kommentare

 
GN⁺ 2024-10-16
Hacker-News-Kommentare
  • Es wäre interessant, In-Memory-SQLite im Browser laufen zu lassen und Kopien des WAL-Logs auf den Server zu streamen, ähnlich wie es Litestream und Cloudflare Durable Objects machen.
    Man könnte WebSocket verwenden, oder es scheint auch mit gelegentlichen fetch()-POSTs möglich. Bei späteren Besuchen würde dann die clientseitige SQLite-Datenbank anhand der serverseitigen Daten wieder befüllt.
    Allerdings fehlt laut diesem SQLite-Forum-Beitrag im Standard-SQLite-WASM-Build der WAL-Modus, daher wäre wohl ein Custom-Build nötig.

    • Dabei gibt es mehrere Ebenen von „so funktioniert das nicht“.
      In-Memory-SQLite verwendet kein WAL, und Browser-Wasm unterstützt Dinge wie die Shared-Memory-API nicht, die SQLite für seinen WAL-Modus erwartet.
      Litestream benötigt eine sehr genaue WAL-Konfiguration, die bei der nativen SQLite-Standardkonfiguration zufällig gut passt, sich in Wasm aber schwer nachbilden lässt.
      Cloudflare Durable Objects mögen von Litestream inspiriert sein, funktionieren tatsächlich aber ziemlich anders; dasselbe gilt für LiteFS oder Turso.
      Die Grundidee, SQLite-Änderungen zu streamen, ist an sich möglich, erfordert aber viel Arbeit und ist wegen des Nebenläufigkeitsmodells von Browser-Wasm schwierig umzusetzen.
      Ich habe den betreffenden Forumsbeitrag damals geschrieben und WAL in serverseitigen Wasm-SQLite-Builds zum Laufen gebracht, aber für den Browser war das keine praktikable oder brauchbare Option.
    • Im August 2022 habe ich einen ähnlichen Proof of Concept gebaut und eine Twitter-DM geschickt, aber keine Antwort erhalten. Mein Handle ist justjs14.
      Den Code von damals müsste ich erst suchen, aber er kompilierte SQLite mit VFS für den Browser nach Wasm, öffnete eine DB, nahm Änderungen vor und konnte das WAL zum Server pushen bzw. von dort pullen. Synchronisierung zwischen Browsern war ebenfalls möglich, manuell oder per WebRTC; wenn man dem Browser-Client ein GitHub-Token gab, lief es sogar auf GitHub Pages.
      Bei Interesse könnte ich es noch einmal von Grund auf lauffähig machen. Ich habe damals viel mit diesem Ansatz experimentiert und halte ihn zumindest für manche Anwendungen für nützlich.
      Eine Demo nur mit Pull-Funktion gibt es hier: https://just.billywhizz.io/sqlite/demo/#https://just.billywhizz.io/sqlite/demo/db/chinook.db
    • Anders als einige andere Gedanken hier halte ich Streaming von WAL-Kopien technisch für möglich. Es müsste allerdings innerhalb des VFS implementiert werden.
      „Shared Memory“ könnte SharedArrayBuffer sein, und wenn man jeweils nur eine DB-Verbindung öffnet, ginge auch ein normaler Buffer. Zum Beispiel kann man das in einem ohnehin bereits verbreiteten SharedWorker so machen.
      Die Implementierung wäre nicht trivial, aber definitiv möglich. Das größere praktische Problem ist, dass man Änderungen auf Blockebene erfasst, nicht auf Zeilenebene.
      Daher eignet es sich für die Replikation der gesamten DB, aber teilweise Synchronisierung, etwa nur einzelne Zeilen mit anderen Nutzern zu teilen, ist schwierig.
      Wenn man für Teilsynchronisierung Änderungen auf Zeilenebene braucht, sollte man Trigger oder die SQLite-Session-Extension verwenden: https://sqlite.org/sessionintro.html
      Bei PowerSync verwenden wir clientseitige Trigger und haben außer dem Wartungsaufwand für die Trigger keine großen Nachteile gefunden.
    • Meinst du etwas wie https://electric-sql.com/?
    • Ich habe diese „Hydration“-Idee nicht vollständig verstanden, aber in letzter Zeit interessiert mich, Teile der Backend-Logik mithilfe von Wasm im Browser auszuführen.
      Wenn man zum Beispiel allgemeines Template-Rendering oder einige Htmx-Endpunkte in Wasm laufen lässt, könnten auch normale serverseitige Web-Frameworks mit einem SQLite-Wasm-Speicher, wie ihn OP beschreibt, Offline-Funktionen bereitstellen.
      Vollständiges Django-Beispiel: https://github.com/m-butterfield/django_webassembly
      Minimales Rust-Beispiel mit Makros zur Trennung von Client/Server: https://github.com/richardanaya/wasm-service
      Clojure Electric macht Ähnliches ohne Wasm, dafür mit mehr Makros: https://github.com/hyperfiddle/electric
      Außerdem gibt es unter den neueren Web-Tools von Microsoft eines, das mit B anfängt.
      In den letzten Jahren hat ein überraschend großer Teil des Python-Ökosystems Wasm-Unterstützung bekommen und ist dadurch für diesen Ansatz nutzbar geworden. Nicht nur offensichtliche Machine-Learning-Bibliotheken, sondern auch Spielebibliotheken wie Pygame und Pyxel; und da SDL2 Unterstützung hinzugefügt hat, scheint auch Support für die Multitouch-UI-Bibliothek Kivy bald möglich.
      Es eröffnen sich viele neue Möglichkeiten, Code auf beliebigen Computern auszuführen und bei Bedarf innerhalb der Sandbox eines Webbrowsers Plattformbürokratie und Gebühren zu umgehen.
  • In der Dokumentation steht, dass diese Komponenten erstmals in 3.40 als öffentliche Beta erschienen sind und dass die API in 3.41 anhand von Community-Feedback stabilisiert werden sollte.
    Da das neueste SQLite-Release aber 3.46.1 vom 13.08.2024 ist, ist die API vermutlich bereits stabilisiert, nur die Dokumentation wurde wohl nicht aktualisiert.
    Es wäre ein guter Distributionsweg, wenn das SQLite-Team ein offizielles npm-Paket mit der WASM-Version veröffentlichen würde. Später habe ich gesehen: Laut Antwort gibt es das schon.
    Meine persönliche Lieblingsvariante von SQLite-in-WASM ist weiterhin die Pyodide-Variante. Sie gibt es schon viel länger als die offizielle SQLite-Implementierung, und wenn man Pyodide verwendet, kommt WASM-SQLite als Teil der Python-Standardbibliothek mit.
    So wird es auf https://lite.datasette.io/ verwendet, und man kann es auch unter https://pyodide.org/en/stable/console.html ausprobieren.
    Wenn man mit sqlite3.connect(':memory:') ein select sqlite_version() ausführt, erhält man 3.39.0 vom 25.06.2022; Pyodide scheint also ein Versionsupdate zu benötigen. Diese Version wird offenbar von emscripten geerbt: https://github.com/emscripten-core/emscripten/blob/main/tools/ports/sqlite3.py

    • Ja, die API ist stabilisiert. Wenn ich das nächste Mal am Rechner sitze, werde ich versuchen, die entsprechende Formulierung zu aktualisieren.
      Allerdings wird das SQLite-Team kein offizielles npm-Paket veröffentlichen. Wir liefern nur reines JS aus und weigern uns entschieden, uns in das Kaninchenloch der Tool-Unterstützung außerhalb der Sprache zu begeben. Keines der Projektmitglieder nutzt solche Tools.
      Wir unterstützen zwar einen „offiziell genehmigten“ npm-Build, den Thomas Steiner pflegt, aber wir nehmen kein JS-Framework aktiv als Entwicklungsziel ins Visier.
      Wenn wir ein bestimmtes Framework, einschließlich npm, direkt unterstützen, könnte der Eindruck entstehen, wir würden dieses Framework empfehlen, und wir vermeiden aktiv Empfehlungen für Drittanbieterprojekte.
    • Ich glaube, das SQLite-Team macht das schon seit einiger Zeit. Man kann in einem JS-Modul @sqlite.org/sqlite-wasm über jsdelivr importieren und sqlite3-bundler-friendly.mjs sowie sqlite3.wasm laden.
      Dann kann man die C-API von SQLite und die objektorientierte API oo1 nutzen, und es funktioniert sowohl mit normalen type=module-Script-Tags als auch in Deno.
      Beispiel-HTML gibt es hier: https://github.com/Ciantic/experimenting-sqlite-wasm/blob/main/standalone.html
    • https://github.com/sqlite/sqlite-wasm
      sqlite-wasm lädt deutlich schneller als Pyodide; wenn man also kein Python braucht, ist Ersteres die bessere Wahl.
    • DuckDB WASM lässt sich unabhängig von Pyodide nutzen und kann auch SQLite erweitern.
      Es scheint allerdings ziemlich eingeschränkt zu sein. Da es kein sqlite_version() gibt, konnte ich nicht einmal die Version prüfen, und die Version im Repository ist mit 3.38.1 ziemlich alt.
      Die aktuelle DuckDB-Web-Shell kann die SQLite-Erweiterung nicht laden, weil die SQLite-Erweiterung für die gestern erschienene Version 1.1.2 noch nicht veröffentlicht wurde. Die ältere Version funktioniert mit der kürzlich aktualisierten WASM-Edition.
      Man kann auch Spatial-Erweiterungen, GDAL, Vektorsuche usw. anbinden: https://github.com/duckdb/duckdb-wasm/releases/tag/v1.29.0
      Eine eigene „SQL-Web-Shell“ zu bauen war ebenfalls nicht allzu schwer, aber die Dokumentation war nicht vollständig genug.
      https://github.com/duckdb/sqlite_scanner/blob/main/src/sqlite/sqlite3.h
    • Es könnte helfen, die FAQ auf dieser Seite zu lesen.
  • Für die Nutzung mit Go gefällt mir ncruces’ Wasm-SQLite-Paket https://github.com/ncruces/go-sqlite3 wirklich gut.
    Auch das Go-Paket von cznic ist hervorragend, aber diese Wasm-Version funktioniert auch in Umgebungen wie OpenBSD gut.

  • WebSQL hätte einfach SQLite sein sollen; dann wäre das Ökosystem für Offline-first und App-Speicher insgesamt viel besser gewesen.
    Gibt es jetzt noch eine Chance? Statt SQLite zu abstrahieren und übermäßig zu spezifizieren: Könnte man nicht die Version der SQLite-API spezifizieren, die Browser unterstützen müssen, und diese Version regelmäßig anheben?

    • Ein Ansatz mit „regelmäßigen Versionsanhebungen“ im Browser dürfte ziemlich problematisch sein.
      Ein Kernprinzip des Webs ist, dass Bestehendes nach Möglichkeit weiter funktionieren soll, und solche Breaking Changes sind schwierig.
      Selbst wenn die Spezifikation mehrere Versionen auflistet und Webseiten auswählen lässt, müssten Browser die heikle Last tragen, alte SQLite-Versionen vorzuhalten, die möglicherweise gar nicht mehr unterstützt werden. Jede Version belegt zudem etwa 1 MB.
      Dann scheint es mir besser, einfach wie jetzt jede Website die Kosten ihrer eigenen Entscheidung tragen zu lassen.
      Wenn es darum geht, das wiederholte Herunterladen gemeinsamer Ressourcen zu vermeiden: Das ist ein Problem, das auch andere CDNs bereits akzeptiert haben. Eine Lösung für Shared Caching gemeinsamer Abhängigkeiten wäre deutlich wertvoller, als sie nur auf SQLite zu beschränken.
      Der aktuelle Ansatz wirkt besser als eine vom Browser bereitgestellte Version.
    • Ich mag SQLite, aber man muss anerkennen, dass es mehrere eigenartige Verhaltensweisen hat, die gegen eine Aufnahme in alle Browser sprechen. Eine SQLite-ähnliche API wäre daher wohl besser.
    • Ich finde es eher gut, dass sich WebSQL nicht durchgesetzt hat.
      Heute kann man bei Bedarf das aktuelle SQLite als 410 KB großes komprimiertes WASM-Blob laden. Wären wir an die vom Browser erzwungene SQLite-Version gebunden, würden wir inzwischen vielleicht eine zehn Jahre alte Version verwenden.
  • Beim schnellen Überfliegen der Seite ist mir der Anwendungsfall nicht ganz klar.
    Konkreter: Kann das ein Ersatz für IndexedDB sein? Werden die Daten persistent gespeichert, oder muss man die SQLite-Datei selbst über die File System API bzw. in IndexedDB/localStorage ablegen?

    • Laut Einführungsseite ist eines der Ziele dieses Projekts, soweit möglich clientseitige persistente Speicherung über nutzbare JS-APIs zu unterstützen.
      Derzeit umfasst das Speicherung über das Origin-Private FileSystem (OPFS) sowie, wenn auch stark eingeschränkt, Backends für window.localStorage und window.sessionStorage.
  • Ich interessiere mich in letzter Zeit stark für den Local-first-Bereich, aber verglichen mit den eingebauten Storage-APIs des Browsers, insbesondere IndexedDB, wirkt das Einbetten von SQLite ziemlich schwergewichtig.
    Auch die wichtigsten Open-Source-Bibliotheken scheinen meist die eingebauten APIs zu verwenden.
    Statt per WebAssembly noch eine weitere ausführbare Datei einzubinden, würde ich gern eine Open-Source-Lösung sehen, die auf dem nativen Browser-Storage eine SQLite-ähnliche API und Synchronisierung bereitstellt.

    • SQL kann man als bequemer nutzbar ansehen als die IndexedDB-API, und wenn man zum Beispiel statt if()-Filtern eine WHERE-Klausel verwendet, könnte das Speicher und CPU sparen.
    • TinyBase könnte für dich interessant sein. Das ist eine Bibliothek, die ich entwickelt habe.
  • Selbst wenn man es von der offiziellen Download-Seite lädt und alle mjs-Dateien sowie die „bundler-friendly“-Dateien entfernt, liegt die minimale SQLite-Wasm-Abhängigkeit bei etwa 1,3 MB.
    Für Browser-Apps wirkt das eher groß, aber da Wasm heutzutage auch an anderen Orten läuft, kann es in solchen Umgebungen sinnvoller sein.

    • Trotzdem lässt es sich gut komprimieren. sqlite3.js und Wasm sind im Original 1,3 MB groß, aber wenn man das JS minimiert und beide Dateien mit Brotli komprimiert, kommt man auf 410 KB.
    • Für moderne Web-Apps halte ich 1,3 MB für eine völlig vertretbare Größe. Vor allem wird es nach dem ersten Besuch der Website im Cache liegen.
      Wenn es nur um das Speichern von Nutzereinstellungen geht, muss man SQLite natürlich nicht herunterladen; aber wenn eine vollständige Datenbank für eine Aufgabe Vorteile bringt, sollte man wegen 1 MB nicht zu sehr besorgt sein und unnötig das Rad neu erfinden.
      Wenn der andere Kommentar stimmt, sind es übers Netzwerk ohnehin nicht einmal 1,3 MB.
    • Das sollte man zusammen mit dem Punkt betrachten, dass der Browser bereits IndexedDB eingebaut hat.
      Dennoch kann bei Browser-Apps, deren Anwendungscode und Daten ohnehin groß sind, etwa Figma- oder Photoshop-ähnlichen Apps oder Machine-Learning-Apps, 1,3 MB kaum zusätzliche Last bedeuten.
      Außerdem sollte man bedenken, dass Wasm deutlich schneller geparst wird als JS. Ich finde gerade keine Quelle für diese Behauptung, aber es gab mindestens einen guten Artikel dazu.
      https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
    • Man muss das Sandbox-Modell von WebAssembly berücksichtigen. Theoretisch kann ein Programm, hier also SQLite, überall dort laufen, wo seine Ausführung sinnvoll ist.
      Das kann lokal sein, auf einem zentralen Server oder an einer nahegelegenen Edge.
    • Serverseitig braucht man sehr wahrscheinlich einen eigenen Wasm-SQLite-Build, der Nebenläufigkeit und Dateisperren anders behandelt.
      Außerdem ist WASI noch weit davon entfernt, die Lösung zu sein. Die SQLite-Amalgamation lässt sich zwar gut mit WASI bauen, aber Nebenläufigkeit ist ein ungelöstes Problem.
      Um einen Wasm-basierten SQLite-Treiber brauchbar zu machen, musste ich ein VFS von Grund auf erstellen.
      https://github.com/ncruces/go-sqlite3/blob/main/vfs/README.md
  • Ich habe eine Weile versucht, das im Rust-Ökosystem zum Laufen zu bringen, aber die SQL-Wrapper von blessed.rs, rusqlite und sqlx, schienen das noch nicht nutzen zu können.
    Es war auch schwierig, selbst einen Wrapper zu schreiben, weil ich keinen Weg gefunden habe, Emscripten-Wasm-Code sauber mit wasm32-unknown-unknown zusammenarbeiten zu lassen.
    Am Ende braucht man in irgendeiner Form einen JS-Wrapper, und dann muss man die von diesen Crates erwarteten Interfaces in JS implementieren und bereitstellen. Wenn das in Rust gelöst wird, wäre das wirklich großartig.

    • Ich arbeite an etwas in dieser Richtung. Wenn du interessiert bist oder beitragen möchtest, kannst du dich hier beteiligen: https://github.com/rhashimoto/wa-sqlite/discussions/154
      Im Kern muss man in wasm-bindgen einen SQLite-Emscripten-Build über extern C-Header einbinden, mit Mehrthreadmodus kompilieren, damit Zugriff auf Shared Array Buffers möglich ist, und das VFS in Rust neu implementieren.
      Danach kann man auf SQLite-Zeilen als rohe Wasm-Bytes zugreifen und einen Rust-SQLite-artigen Wrapper oder eine Integration bauen.
      Komfortfunktionen wie Connection Pooling wird es weiterhin nicht geben, aber in Wasm ist es wahrscheinlich besser, die DB im Exklusivmodus zu verwenden.
    • Hier gibt es etwas, das diesen SQLite-Build zusammen mit Rusts Diesel SQLite zum Laufen bringt: https://github.com/xmtp/diesel-wasm-sqlite
      Diese Woche bereite ich das Release 0.2 vor und werde einige Probleme bereinigen. Abgesehen davon kann man das Diesel ORM wie nativ verwenden.
    • Beim sauberen Zusammenspiel von emscripten-Wasm-Code und wasm32-unknown-unknown gibt es gute Nachrichten.
      Es gibt einen Plan, Emscripten und Rust in Wasm gut zusammenarbeiten zu lassen: https://github.com/rustwasm/wasm-bindgen/pull/4014#issuecomment-2356908451
    • Ich warte auch schon ziemlich lange darauf. Es gibt ein paar PRs, die seit etwa einem Jahr auf Eis liegen, und ich habe Proofs of Concept gesehen, aber sie waren weit davon entfernt, praktisch nutzbar zu sein.
  • Ich habe mit dem Wasm-Build von SQLite und der Chicory-Runtime eine SQLite-Bibliothek gebaut, die auf einer reinen JVM läuft: https://github.com/dylibso/sqlite-zero
    Das ist eher ein Experiment als auf Produktionsreife ausgelegt, könnte aber nützlich sein, um ein abhängigkeitsfreies SQLite-Tool ins JVM-Ökosystem zu bringen.

    • Wie sieht der Zugriff auf das Dateisystem aus? WASI?
  • Allgemeine Frage: In welchen Situationen ist es vorteilhafter, die gesamte DB auszuliefern und den Browser die Queries verarbeiten zu lassen?
    Anders gesagt: Wann würde man das einsetzen, um die User Experience gegenüber dem traditionellen Modell einer servergehosteten DB zu verbessern?

    • Ich persönlich nutze es für eine statisch gehostete Website, daher war eine servergehostete Datenbank von vornherein keine Option.
      Mit dem passenden Treiber kann man statt der gesamten Datenbank nur die benötigten Datenbank-Chunks streamen: https://github.com/mmomtchev/sqlite-wasm-http
      So kann man SQLite-Volltextsuche nutzen, ohne die gesamte FTS-Datenbank herunterzuladen. Wenn der Suchbegriff kurz genug ist, bekommt man zwar den Großteil, aber nicht alles.
    • Als ich den Titel dieses Beitrags sah, kam mir intuitiv etwas wie die Interaktion von PouchDB und CouchDB in Offline-first-Apps in den Sinn.
      https://medium.com/offline-camp/couchdb-pouchdb-and-hoodie-as-a-stack-for-progressive-web-apps-a6078a985f18
    • Für Offline-Nutzung kann das beim Umgang mit großen Datenmengen gut geeignet sein.
      Das reicht von Audiobibliotheken bis zu 3D-Modellierungssoftware. Änderungen lassen sich lokal erstellen und persistieren und dann regelmäßig oder sobald man wieder online ist mit der Serverseite synchronisieren.
    • Ich baue Dashboards, die direkt mit mehreren Diensten kommunizieren. Auf meinem Server liegen nur sehr wenige Daten für Zugriffskontrolle und Token-Management; die eigentlichen Daten kommen nicht auf meinen Server.
      Solche Apps sind gute Kandidaten für eine clientseitig eingebettete DB.