DuckDB-Wasm 扩展
TL;DR: DuckDB-Wasm 用户现在可以加载 DuckDB 扩展,从而能够在浏览器中运行这些扩展。
在这篇博客文章中,我们将介绍两个令人兴奋的 DuckDB 功能:DuckDB-Wasm 客户端和 DuckDB 扩展。我将讨论这些原本独立的功能现在如何被整合起来协同工作。这些功能现已向 DuckDB-Wasm 用户开放,您可以在 shell.duckdb.org 试用它们。
DuckDB 扩展
DuckDB 的理念是拥有一个精简的核心系统,以确保其鲁棒性和可移植性。然而,一个相互竞争的设计目标是保持灵活性,并允许支持进行高级分析所需的广泛功能。为了适应这一需求,DuckDB 拥有一个扩展机制,用于在运行时安装和加载扩展。
本地运行 DuckDB 扩展
对于 DuckDB,这里有一个使用命令行界面的简单端到端示例
INSTALL tpch;
LOAD tpch;
CALL dbgen(sf = 0.1);
PRAGMA tpch(7);
该脚本首先从官方扩展仓库安装TPC-H 扩展,该扩展实现了流行的 TPC-H 基准测试。然后,它加载 TPC-H 扩展,并使用 dbgen
函数生成数据来填充数据库。最后,它运行 TPC-H 查询 7。
此示例展示了我们如何安装扩展来为 DuckDB 补充新功能(TPC-H 数据生成器),这些功能并非 DuckDB 基础可执行文件的一部分。相反,它是从扩展仓库下载,然后在 DuckDB 框架内本地加载和执行的。
目前,DuckDB 拥有多个扩展。这些扩展增加了对文件系统、文件格式、数据库和网络协议的支持。此外,它们还实现了新的功能,例如全文搜索。
DuckDB-Wasm
在 André Kohn 的带领下,DuckDB 于 2021 年被移植到 WebAssembly 平台。WebAssembly(也称为 Wasm)是近年来开发的一种 W3C 标准语言。可以将其视为一种独立于机器的二进制格式,您可以在网页浏览器的沙箱环境中执行它。
得益于 DuckDB-Wasm,任何人只需一个浏览器标签页即可访问 DuckDB 实例,所有计算都在您的浏览器中本地执行,且没有任何数据离开您的设备。DuckDB-Wasm 是一个可用于各种部署的库(例如,无需服务器即可在浏览器内运行的笔记本)。在这篇文章中,我们将使用 Web Shell,用户可以在其中逐行输入 SQL 语句,其行为模仿 DuckDB CLI shell。
DuckDB-Wasm 中的 DuckDB 扩展!
DuckDB-Wasm 现在支持 DuckDB 扩展。此支持带来了四个新的关键特性。首先,DuckDB-Wasm 库可以编译为支持动态扩展。其次,DuckDB 扩展可以编译成单个 WebAssembly 模块。第三,使用 DuckDB-Wasm 的用户和开发人员现在可以选择他们加载的扩展集。最后,DuckDB-Wasm shell 的功能现在与原生的 CLI 功能更加接近。
在 DuckDB-Wasm 中使用 TPC-H 扩展
为了演示这一点,我们将再次使用TPC-H 数据生成示例。要在您的浏览器中运行此脚本,请启动一个在线 DuckDB shell 并运行这些命令。该脚本将生成比例因子为 0.1 的 TPC-H 数据集,这相当于 100 MB 的未压缩 CSV 格式数据。
脚本完成后,您可以继续执行查询,甚至可以使用以下命令下载 customer.parquet
文件(1 MB)
COPY customer TO 'customer.parquet';
.files download customer.parquet
这会首先将 customer.parquet
复制到 DuckDB-Wasm 文件系统,然后通过您的浏览器下载它。
简而言之,您的 DuckDB 实例(*完全在您的浏览器中运行*)首先安装并加载了TPC-H 扩展。然后,它使用扩展逻辑生成数据并将其转换为 Parquet 文件。最后,您可以将 Parquet 文件作为常规文件下载到本地文件系统。
在 DuckDB-Wasm 中使用空间扩展
为了展示 DuckDB-Wasm 扩展所解锁的可能性并测试其能力,何不在 DuckDB-Wasm 中使用空间扩展呢?此扩展实现了地理空间类型和函数,使其能够处理地理空间数据和相关工作负载。
要在 DuckDB-Wasm 中安装和加载空间扩展,请运行
INSTALL spatial;
LOAD spatial;
使用空间扩展,以下查询利用纽约出租车数据集,并计算每个行政区出租车区域的面积
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;
您的本地 DuckDB 客户端和在线 DuckDB shell 都将执行相同的分析。
幕后原理
让我们深入了解这一切是如何运作的。下图显示了 DuckDB-Wasm 架构的概览。图中所示的两个组件都在网络浏览器中运行。
当您在浏览器中加载 DuckDB-Wasm 时,将设置两个组件:(1) 一个主线程包装库,它充当使用 DuckDB-Wasm 的用户或代码与驱动后台组件之间的桥梁。(2) 一个用于执行查询的 DuckDB 引擎。此组件位于 Web Worker 中,并通过消息与主线程组件通信。此组件有一个处理消息的 JavaScript 层,以及编译成单个 WebAssembly 文件的原始 DuckDB C++ 逻辑。
当我们加入扩展时会发生什么?
DuckDB-Wasm 的扩展由单个 WebAssembly 模块组成。这将编码扩展的逻辑和数据、要导入和导出的函数列表,以及一个编码元数据的自定义部分,该元数据允许验证扩展。
为了使扩展加载工作,DuckDB 引擎组件会阻塞、获取并验证外部 WebAssembly 代码,然后将其链接进来,连接导入和导出,之后系统将连接并设置为如同单个代码库一样继续执行。
实现这一功能的核心代码块如下
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()
);
}
在这里,我们依赖于 Emscripten 的两个强大功能,它是我们用于将 DuckDB 编译为 WebAssembly 的编译器工具链。
首先,EM_ASM
允许我们将 JavaScript 代码直接内联到 C++ 代码中。这意味着在运行时,当我们执行到该代码块时,WebAssembly 组件将返回到 JavaScript 环境,对诸如 https://extensions.duckdb.org/…/tpch.duckdb_extension.wasm 这样的 URL 执行一个阻塞的 XMLHttpRequest
,然后验证刚刚获取的包是否确实是一个有效的 WebAssembly 模块。
其次,我们利用 Emscripten 的 dlopen
实现,它使得兼容的 WebAssembly 模块能够被链接在一起,并作为一个单一的可组合代码库运行。
这些特性使得通过 SQL LOAD
语句触发时,能够实现扩展的动态加载。
开发者指南
我们看到使用 DuckDB-Wasm 扩展的开发者主要分为两大类。
- DuckDB-Wasm 开发者:如果您正在构建一个封装 DuckDB-Wasm 的网站或库,新的扩展支持意味着现在可以向您的用户公开更广泛的功能。
- DuckDB 扩展开发者:如果您已经编写了 DuckDB 扩展,或者正在考虑这样做,请考虑将其移植到 DuckDB-Wasm。DuckDB 扩展模板仓库包含了编译到 DuckDB-Wasm 所需的配置。
限制
DuckDB-Wasm 扩展有一些固有的限制。例如,无法与您机器上的原生可执行文件进行通信,这是某些扩展(例如 postgres
扫描器扩展)所必需的。此外,您所依赖的某些库可能目前不支持编译到 Wasm,或者由于浏览器施加的额外要求(特别是关于非安全 HTTP 请求),其能力可能无法与本地可执行文件完全一对一。
结论
在这篇博客文章中,我们解释了 DuckDB-Wasm 如何支持扩展,并演示了多个扩展:TPC-H、Parquet 和空间扩展。
得益于 DuckDB 的可移植性,这篇博客文章中展示的脚本同样可以在您的智能手机上运行。
如需了解最新进展,请关注此博客并加入我们的 Discord 中的 Wasm 频道。如果您有关于 DuckDB 扩展潜力的示例,请告诉我们!