2023-12-18

Erweiterungen für DuckDB-Wasm

Carlo Piovesan

In diesem Blogbeitrag geht es um zwei spannende DuckDB-Features: den DuckDB-Wasm-Client und DuckDB-Erweiterungen. Ich zeige, wie diese bisher getrennten Features jetzt zusammenarbeiten. Sie sind für DuckDB-Wasm-Nutzer verfügbar und können unter shell.duckdb.org ausprobiert werden.

DuckDB-Erweiterungen

DuckDBs Philosophie ist ein schlankes Kernsystem, damit Robustheit und Portabilität stimmen. Ein konkurrierendes Ziel ist Flexibilität und ein breites Funktionsspektrum für fortgeschrittene Analysen. Dafür hat DuckDB einen Erweiterungsmechanismus: Erweiterungen werden zur Laufzeit installiert und geladen.

DuckDB-Erweiterungen lokal ausführen

Für DuckDB ein einfaches End-to-End-Beispiel über die Kommandozeile:

INSTALL tpch;
LOAD tpch;
CALL dbgen(sf = 0.1);
PRAGMA tpch(7);

Das Skript installiert zuerst die TPC-H-Erweiterung aus dem offiziellen Erweiterungs-Repository; sie implementiert den bekannten TPC-H-Benchmark. Dann wird die TPC-H-Erweiterung geladen und mit dbgen Daten erzeugt. Zum Schluss läuft TPC-H Query 7.

Das Beispiel zeigt einen Fall, in dem eine Erweiterung DuckDB um ein Feature ergänzt (den TPC-H-Datengenerator), das nicht im Basis-Executable steckt. Stattdessen wird es aus dem Erweiterungs-Repository heruntergeladen, geladen und lokal im DuckDB-Rahmen ausgeführt.

Derzeit hat DuckDB mehrere Erweiterungen. Sie ergänzen Dateisysteme, Dateiformate, Datenbank- und Netzwerkprotokolle. Außerdem implementieren sie neue Funktionen, etwa Full-Text-Suche.

DuckDB-Wasm

Unter der Federführung von André Kohn wurde DuckDB 2021 auf die WebAssembly-Plattform portiert. WebAssembly, auch Wasm, ist eine in den letzten Jahren entwickelte W3C-Standardsprache. Stellen Sie sich ein maschinenunabhängiges Binärformat vor, das Sie in der Sandbox eines Webbrowsers ausführen können.

Dank DuckDB-Wasm hat jede und jeder Zugriff auf eine DuckDB-Instanz – nur einen Browser-Tab entfernt. Die gesamte Berechnung läuft lokal im Browser, keine Daten verlassen das Gerät. DuckDB-Wasm ist eine Bibliothek für verschiedene Deployments (z. B. Notebooks, die ohne Server im Browser laufen). In diesem Beitrag nutzen wir die Web-Shell: SQL-Statements werden zeilenweise eingegeben, das Verhalten orientiert sich an der DuckDB-CLI-Shell.

DuckDB-Erweiterungen, in DuckDB-Wasm!

DuckDB-Wasm unterstützt jetzt DuckDB-Erweiterungen. Dazu kommen vier neue Kernpunkte. Erstens: Die DuckDB-Wasm-Bibliothek lässt sich mit dynamischer Erweiterungssupport kompilieren. Zweitens: DuckDB-Erweiterungen können zu einem einzelnen WebAssembly-Modul kompiliert werden. Drittens: Nutzer und Entwickler, die mit DuckDB-Wasm arbeiten, können den Satz geladener Erweiterungen selbst wählen. Viertens: Die Features der DuckDB-Wasm-Shell liegen jetzt deutlich näher an der nativen CLI-Funktionalität.

Die TPC-H-Erweiterung in DuckDB-Wasm nutzen

Zur Demonstration wieder das TPC-H-Datengenerierungsbeispiel. Um das Skript im Browser auszuführen, starten Sie eine Online-DuckDB-Shell mit diesen Befehlen. Das Skript erzeugt den TPC-H-Datensatz mit Scale Factor 0.1, das entspricht 100 MB unkomprimiertes CSV.

Wenn das Skript fertig ist, können Sie weiter Abfragen ausführen oder die Datei customer.parquet (1 MB) mit folgenden Befehlen herunterladen:

COPY customer TO 'customer.parquet';
.files download customer.parquet

Zuerst wird customer.parquet ins Dateisystem von DuckDB-Wasm kopiert, dann über den Browser heruntergeladen.

Kurz: Ihre DuckDB-Instanz, die vollständig im Browser läuft, hat zuerst die TPC-H-Erweiterung installiert und geladen. Dann hat sie mit der Erweiterung Logik Daten erzeugt und in eine Parquet-Datei umgewandelt. Schließlich konnten Sie die Parquet-Datei als normale Datei auf Ihr lokales Dateisystem herunterladen.

Wasm-Shell mit der TPC-H-Erweiterung

Die Spatial-Erweiterung in DuckDB-Wasm nutzen

Um zu zeigen, was DuckDB-Wasm-Erweiterungen möglich machen, und die Grenzen auszutesten: Wie wäre es mit der Spatial-Erweiterung in DuckDB-Wasm? Diese Erweiterung implementiert geospatiale Typen und Funktionen für geospatiale Daten und passende Workloads.

Zum Installieren und Laden der Spatial-Erweiterung in DuckDB-Wasm:

INSTALL spatial;
LOAD spatial;

Mit der Spatial-Erweiterung nutzt die folgende Abfrage den New-York-Taxi-Datensatz und berechnet die Fläche der Taxi-Zonen je Borough:

CREATE TABLE nyc AS
SELECT
borough,
st_union_agg(geom) AS full_geom,
st_area(full_geom) AS area,
st_centroid(full_geom) AS centroid,
count(*) AS count
FROM
st_read('https://raw.githubusercontent.com/duckdb/duckdb-spatial/main/test/data/nyc_taxi/taxi_zones/taxi_zones.shp')
GROUP BY borough;
SELECT borough, area, centroid::VARCHAR, count
FROM nyc;

Sowohl Ihr lokaler DuckDB-Client als auch die Online-DuckDB-Shell führen dieselbe Analyse aus.

Unter der Haube

Schauen wir uns an, wie das zusammenhängt. Die folgende Abbildung gibt einen Überblick über die Architektur von DuckDB-Wasm. Beide Komponenten in der Abbildung laufen im Webbrowser.

Überblick über die Architektur von DuckDB-Wasm

Wenn Sie DuckDB-Wasm im Browser laden, werden zwei Komponenten aufgebaut: (1) Eine Wrapper-Bibliothek im Main-Thread, die als Brücke zwischen Nutzern oder Code, der DuckDB-Wasm verwendet, und der Hintergrundkomponente dient. (2) Eine DuckDB-Engine zum Ausführen von Abfragen. Diese Komponente lebt in einem Web Worker und kommuniziert mit der Main-Thread-Komponente über Nachrichten. Sie hat eine JavaScript-Schicht für Nachrichten und die ursprüngliche DuckDB-C++-Logik, kompiliert in eine einzelne WebAssembly-Datei.

Was passiert, wenn Erweiterungen dazukommen?

Überblick über die Architektur von DuckDB-Wasm mit Erweiterungen

Erweiterungen für DuckDB-Wasm bestehen aus einem einzelnen WebAssembly-Modul. Es kodiert Logik und Daten der Erweiterung, die Liste importierter und exportierter Funktionen und einen eigenen Abschnitt mit Metadaten zur Verifikation der Erweiterung.

Damit das Laden funktioniert, blockiert die DuckDB-Engine-Komponente, holt und prüft externen WebAssembly-Code, verknüpft ihn, verdrahtet Import und Export – danach hängt das System zusammen und arbeitet weiter, als wäre es eine einzige Codebasis.

Der zentrale Codeblock, der das möglich macht:

EM_ASM(
{
const xhr = new XMLHttpRequest();
xhr.open("GET", UTF8ToString($0), false);
xhr.responseType = "arraybuffer";
xhr.send(null);
var uInt8Array = xhr.response;
// Check signatures / version compatibility left as an exercise
WebAssembly.validate(uInt8Array);
// Here we add the uInt8Array to Emscripten's filesystem,
// for it to be found by dlopen
FS.writeFile(UTF8ToString($1), new Uint8Array(uInt8Array));
},
filename.c_str(), basename.c_str()
);
auto lib_hdl = dlopen(basename.c_str(), RTLD_NOW | RTLD_LOCAL);
if (!lib_hdl) {
throw IOException(
"Extension \"%s\" could not be loaded: %s",
filename,
GetDLError()
);
}

Hier stützen wir uns auf zwei starke Features von Emscripten, der Compiler-Toolchain, mit der wir DuckDB nach WebAssembly kompilieren.

Erstens erlaubt EM_ASM, JavaScript direkt in C++ einzubetten. Zur Laufzeit kehrt die WebAssembly-Komponente an dieser Stelle nach JavaScript zurück, führt einen blockierenden XMLHttpRequest auf eine URL wie https://extensions.duckdb.org/…/tpch.duckdb_extension.wasm aus und prüft, dass das gerade geholte Paket tatsächlich ein gültiges WebAssembly-Modul ist.

Zweitens nutzen wir Emscriptens dlopen-Implementierung, mit der kompatible WebAssembly-Module verknüpft werden und als eine zusammensetzbare Codebasis wirken.

So wird dynamisches Laden von Erweiterungen umgesetzt, ausgelöst über das SQL-Statement LOAD.

Leitfaden für Entwickler

Wir sehen zwei Hauptgruppen, die Erweiterungen mit DuckDB-Wasm nutzen.

Einschränkungen

DuckDB-Wasm-Erweiterungen haben einige inhärente Grenzen. Zum Beispiel ist die Kommunikation mit nativen Executables auf Ihrer Maschine nicht möglich – das brauchen manche Erweiterungen, etwa die postgres-Scanner-Erweiterung. Außerdem wird die Kompilierung nach Wasm für manche Bibliotheken, auf die Sie sich stützen, derzeit nicht unterstützt, oder Fähigkeiten sind nicht eins zu eins mit lokalen Executables, weil der Browser zusätzliche Anforderungen stellt, insbesondere rund um unsichere HTTP-Anfragen.

Fazit

In diesem Beitrag haben wir erklärt, wie DuckDB-Wasm Erweiterungen unterstützt, und das an mehreren Erweiterungen gezeigt: TPC-H, Parquet und spatial.

Dank der Portabilität von DuckDB funktionieren die Skripte aus diesem Beitrag auch auf dem Smartphone:

Wasm-Shell mit der TPC-H-Erweiterung auf iOS

Für Neuigkeiten folgen Sie diesem Blog und treten Sie dem Wasm-Kanal in unserem Discord bei. Wenn Sie ein Beispiel haben, was mit Erweiterungen in DuckDB möglich ist, lassen Sie es uns wissen!