Learning Platform
Глоссарий Troubleshooting
Урок 16.01 · 22 мин
Средний
duckdb-wasmwebassemblyopfsbrowser-analytics

DuckDB-WASM: аналитика в браузере

Обычная архитектура аналитического приложения выглядит так: браузер отправляет SQL на сервер, сервер исполняет запрос в базе, гонит результат обратно по сети. Каждый клик в дашборде — это сетевой round-trip, нагрузка на бэкенд и задержка на десятки или сотни миллисекунд. А ещё это сервер, который надо поднять, масштабировать и оплачивать.

DuckDB-WASM убирает сервер из этой схемы целиком. Это DuckDB, скомпилированный в WebAssembly — бинарный формат, который исполняется виртуальной машиной внутри браузера почти на скорости нативного кода. Движок, оптимизатор, storage-формат, чтение Parquet — всё это работает на вкладке пользователя. SQL-запрос не покидает машину клиента. В этом уроке разберём, как именно DuckDB попадает в браузер, почему он там быстрый, и как OPFS даёт ему настоящую персистентность — базу, которая переживает перезагрузку вкладки.


Что такое WebAssembly и зачем он DuckDB

JavaScript — единственный язык, который браузер исполняет напрямую, и для тяжёлых вычислений он не годится: динамическая типизация, сборка мусора, отсутствие контроля над памятью. DuckDB написан на C++ — переписать его на JavaScript значило бы потерять всю производительность.

WebAssembly решает эту проблему. Это низкоуровневый байткод со статической типизацией и линейной моделью памяти (один непрерывный массив байт). Компилятор C++ (в случае DuckDB — Emscripten поверх LLVM) транслирует исходники DuckDB в .wasm-модуль, а браузер исполняет этот байткод своей WASM-машиной — той же, что лежит в основе V8 или SpiderMonkey.

Путь DuckDB из C++ в браузер
Исходники C++Тот же код DuckDB, что компилируется для Linux и macOS. Отдельной кодовой базы для WASM нет.
Emscripten / LLVM
WASM-модульБинарный .wasm файл со статически типизированным байткодом и линейной памятью. Загружается браузером как обычный ресурс.
instantiate
WASM-машина браузераТот же движок WebAssembly, что в V8 или SpiderMonkey. Исполняет байткод близко к скорости нативного кода.

Производительность WASM — порядка 80-95% от нативного C++ на вычислительных задачах. Для DuckDB это критично: векторизованный движок, который мы разбирали в модуле о движке исполнения, остаётся векторизованным и в браузере. Vector size 2048, push-based исполнение, zonemaps — всё работает. WASM теряет немного на границах вызовов и на отсутствии некоторых SIMD-инструкций, но проигрыш умеренный.

При этом WASM наследует ограничения песочницы браузера. Самое заметное — нет прямого доступа к файловой системе диска: WASM-модуль не может открыть /home/user/data.parquet так, как это делает нативный DuckDB. Параллелизм возможен только через Web Workers, а разделяемая память между ними требует заголовков Cross-Origin-Opener-Policy и Cross-Origin-Embedder-Policy на странице. Эти ограничения формируют то, как DuckDB-WASM устроен.


Как данные попадают в браузерный DuckDB

Раз файловой системы нет, возникает вопрос: откуда DuckDB-WASM берёт данные? Есть несколько путей, и они закрывают большинство сценариев.

Первый — HTTP-доступ к удалённым файлам. DuckDB-WASM умеет читать Parquet и CSV прямо по URL, причём не целиком: для Parquet он использует HTTP range requests — запрашивает сначала footer файла с метаданными, по нему понимает, какие row groups нужны под текущий фильтр, и подтягивает только их байтовые диапазоны. Файл на 2 ГБ в публичном бакете S3 можно осмысленно запрашивать, скачав десятки мегабайт.

-- DuckDB-WASM читает Parquet по URL, подтягивая только нужные row groups
SELECT carrier, count(*) AS flights
FROM 'https://example-bucket.s3.amazonaws.com/flights-2026.parquet'
WHERE origin = 'JFK'
GROUP BY carrier
ORDER BY flights DESC;

-- Результат (запрос исполнен целиком в браузере):
-- carrier | flights
-- B6      | 41207
-- DL      | 38114
-- AA      | 22980

Второй путь — загрузка локального файла пользователем. Когда пользователь выбирает файл через <input type="file"> или перетаскивает его на страницу, JavaScript получает объект File. DuckDB-WASM регистрирует этот файл в своей виртуальной файловой системе, и дальше FROM 'имя_файла' работает так же, как с локальным файлом в нативном DuckDB. Данные при этом не уходят никуда за пределы вкладки.

Третий — прямая передача данных из JavaScript: можно вставить Arrow-таблицу или результат fetch() напрямую в DuckDB через его JS API.

NOTE

DuckDB-WASM поставляется в нескольких сборках (bundles): облегчённая стартует быстрее, полная поддерживает разделяемую память и многопоточность через Web Workers. Загрузчик @duckdb/duckdb-wasm сам выбирает подходящий bundle по возможностям браузера. Многопоточный bundle требует COOP/COEP-заголовков — без них DuckDB-WASM откатывается на однопоточное исполнение.


OPFS: настоящая персистентность в браузере

Чтение по HTTP и загруженные файлы хороши, но это эфемерные данные: закрыл вкладку — состояние потеряно. Долгое время браузерный DuckDB работал только in-memory. Это изменил OPFS — Origin Private File System.

OPFS — это часть Storage API браузера: приватная файловая система, выданная конкретному origin (паре «протокол + домен»). Она не видна пользователю как папки, не пересекается с данными других сайтов, и — ключевой момент — даёт WASM-коду быстрый синхронный доступ к файлам через специальные synchronous access handles в Web Worker. Именно синхронность важна: storage-движку DuckDB нужно читать и писать блоки предсказуемо, а не через цепочку промисов.

DuckDB-WASM использует OPFS как место хранения своего single-file storage-формата. Указываете путь со схемой opfs:// — и база живёт в OPFS:

-- Открыть (или создать) персистентную базу в OPFS
ATTACH 'opfs://analytics.db' AS local_db;

-- Включить запись каждого изменения сразу в OPFS, без откладывания
SET checkpoint_threshold = '0KB';

-- Создаём таблицу — она физически ложится в OPFS
CREATE TABLE local_db.events AS
  SELECT * FROM 'https://example-bucket.s3.amazonaws.com/events.parquet';

-- Результат:
-- 1_240_551 строк записано в opfs://analytics.db

После перезагрузки вкладки тот же ATTACH 'opfs://analytics.db' откроет базу с уже существующими данными. Это полноценная локальная аналитическая БД внутри браузера: с persistent-хранилищем, ACID-транзакциями, колоночным сжатием и storage-форматом, который мы разбирали в модуле о storage.

Эфемерное против персистентного хранения в DuckDB-WASM
In-memory базаДанные живут только в линейной памяти WASM. Быстро, но закрытие вкладки стирает всё. Сжатие к in-memory базам не применяется.
ATTACH 'opfs://...'
OPFS storage-файлSingle-file storage-формат DuckDB лежит в Origin Private File System. Переживает перезагрузку и закрытие вкладки. Доступ через синхронные access handles в Web Worker.

Параметр checkpoint_threshold = '0KB' стоит понимать точно. По умолчанию DuckDB накапливает изменения в WAL и сбрасывает их в основной файл (делает checkpoint), когда WAL вырастает до порога. Значение 0KB означает «делать checkpoint после каждого изменения» — состояние всегда полностью на диске OPFS. Это безопасно при внезапном закрытии вкладки, но создаёт I/O-нагрузку на каждый statement. Для приложения, где пользователь много читает и редко пишет, это разумный компромисс; для пакетной загрузки тысяч строк порог лучше оставить выше и сделать CHECKPOINT вручную в конце.

OPFS не безграничен. Браузер выделяет origin квоту, зависящую от свободного места на диске и эвристик, а при нехватке места может вычистить хранилище неактивного сайта. Для рабочих наборов в единицы и десятки гигабайт это рабочий инструмент; рассчитывать на него как на бесконечное хранилище нельзя.


Где DuckDB-WASM на месте, а где нет

DuckDB-WASM не замена серверной аналитике, у него своя ниша. Сильные стороны:

СценарийПочему DuckDB-WASM выигрывает
Интерактивный дашборд над фиксированным датасетомКаждый фильтр — локальный запрос, ноль сетевых round-trip, отклик в миллисекундах
Демо и playground SQLНикакого бэкенда, страница работает как статика на CDN
Приватные данные пользователяФайл не покидает браузер — важно для медицинских, финансовых данных
Edge/offline-приложенияПосле загрузки .wasm и данных работает без сети
Снижение нагрузки на серверАналитика уезжает на тысячи клиентских машин вместо одного бэкенда

Слабые стороны тоже определяются архитектурой. Объём ограничен памятью вкладки и квотой OPFS — терабайтный датасет в браузер не положить. Холодный старт включает скачивание .wasm-модуля (единицы мегабайт) и данных. Параллелизм упирается в число Web Workers и наличие COOP/COEP-заголовков. И каждый клиент исполняет запрос на своём железе — у кого-то это мощный ноутбук, у кого-то слабый телефон.

TIP

Хорошая ментальная модель: DuckDB-WASM — это «последняя миля» аналитики. Тяжёлую подготовку данных делает серверный пайплайн (в капстоуне это будет DuckLake), он публикует компактные Parquet-витрины, а DuckDB-WASM раздаёт по ним интерактивные запросы прямо в браузере. Связка «серверная обработка + браузерная подача» — то, ради чего этот движок и нужен.


Попробуй сам

DuckDB-WASM удобнее всего пощупать без единой строчки кода — через официальную браузерную оболочку DuckDB по адресу shell.duckdb.org. Она целиком построена на DuckDB-WASM: SQL исполняется в вашем браузере.

Задания:

  1. Откройте shell.duckdb.org и выполните SELECT version();. Затем FROM duckdb_extensions(); — посмотрите, какие расширения доступны в WASM-сборке и чем список отличается от нативного DuckDB.
  2. Найдите любой публичный Parquet-файл по HTTPS-URL и выполните над ним SELECT count(*) и агрегирующий запрос с GROUP BY. Откройте вкладку Network в DevTools и убедитесь, что браузер скачал не весь файл, а только диапазоны байт (HTTP range requests).
  3. Выполните ATTACH 'opfs://test.db' AS db;, создайте в ней таблицу через CREATE TABLE db.t AS SELECT range AS id FROM range(1000);, затем перезагрузите вкладку, снова сделайте ATTACH 'opfs://test.db' и проверьте, что таблица на месте.
  4. Сравните SET checkpoint_threshold со значениями по умолчанию и '0KB': вставьте в обоих режимах партию строк и подумайте, в каком случае состояние гарантированно на диске сразу после каждого statement.
DataFusion WASM: аналогичный embedded-движок в браузере
Проверка знанийKnowledge check
Что даёт OPFS браузерному DuckDB, чего не может дать in-memory режим, и почему для storage-движка важна именно синхронность доступа к OPFS?
ОтветAnswer
OPFS (Origin Private File System) даёт DuckDB-WASM настоящую персистентность: single-file storage-формат DuckDB физически лежит в приватной файловой системе, выданной браузером конкретному origin, и переживает перезагрузку и закрытие вкладки. In-memory режим хранит данные только в линейной памяти WASM-модуля — закрытие вкладки стирает всё, и колоночное сжатие к in-memory базам вообще не применяется. Синхронность важна потому, что storage-движок DuckDB читает и пишет блоки предсказуемым императивным кодом, а не через цепочки промисов; OPFS предоставляет synchronous access handles внутри Web Worker, что позволяет WASM-коду обращаться к файлу так же, как нативный DuckDB обращается к диску. Дополнительно: параметр checkpoint_threshold = '0KB' заставляет DuckDB-WASM сбрасывать каждое изменение в OPFS сразу, гарантируя, что состояние всегда полностью на диске, ценой I/O-нагрузки на каждый statement. OPFS при этом ограничен квотой origin и не является бесконечным хранилищем.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что такое DuckDB-WASM и за счёт чего он сохраняет производительность векторизованного движка в браузере?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6