Публикация: запрос витрины из DuckDB-WASM в браузере
Пайплайн почти готов: сырые файлы прошли staging, превратились в партиционированную витрину mart_daily_zone в DuckLake и обновляются инкрементально через MERGE. Осталась последняя миля — доставить витрину потребителю. В капстоуне потребитель — это интерактивный дашборд, который работает прямо в браузере, без серверного API.
Здесь смыкаются два урока: DuckDB-WASM из модуля 15 (DuckDB в браузере) и наш пайплайн. Серверная часть готовит данные, браузерная — раздаёт по ним интерактивные запросы. Этот урок — про то, как соединить их: что и как экспортировать из витрины и как DuckDB-WASM это запрашивает на стороне клиента.
Принцип: тяжёлое на сервере, лёгкое в браузере
Прежде чем писать код, зафиксируем правильное разделение. Браузер — ограниченная среда: память вкладки невелика, у клиента может быть слабый телефон. Тащить в браузер всю витрину из сотен тысяч строк, а тем более 41 миллион сырых поездок, неправильно.
Разделение такое: всё тяжёлое — сканирование сырых данных, join, агрегацию — делает серверный пайплайн на DuckDB. Он публикует компактный результат. А браузерный DuckDB-WASM делает только лёгкое — интерактивные срезы и фильтры поверх этого компактного результата.
Этот принцип — прямое продолжение того, что мы видели в бенчмарке и в уроке про DuckDB-WASM: инструмент применяется там, где совпадает с масштабом задачи. Серверный DuckDB — для гигабайт сырья, браузерный DuckDB-WASM — для мегабайт готовой витрины.
Экспорт витрины в компактный Parquet
Витрина mart_daily_zone живёт в DuckLake. Для публикации её нужно выгрузить в обычный одиночный Parquet-файл — формат, который DuckDB-WASM читает по HTTP идеально. Делается это командой COPY.
Ключевое слово — компактный. Не нужно публиковать витрину целиком со всеми колонками за все годы. Для дашборда обычно достаточно недавнего периода и только тех колонок, которые он показывает:
-- Экспорт компактного среза витрины для браузера
COPY (
SELECT trip_date, borough, zone_name, trips, revenue
FROM lake.mart_daily_zone
WHERE trip_date >= '2026-01-01' -- только нужный период
) TO 'publish/dashboard.parquet'
(FORMAT parquet, COMPRESSION zstd);
-- Результат:
-- 68_240 строк, файл dashboard.parquet ~1.9 МБ.
-- Сравните: исходные 41 млн сырых поездок — десятки ГБ.
Что здесь сделано осознанно:
- Отбор колонок. В файл идут только 5 колонок, нужных дашборду, а не вся ширина витрины — projection уменьшает размер.
- Отбор периода.
WHERE trip_date >= ...оставляет только актуальный диапазон. История остаётся в лейкхаусе, но в браузер не едет. - Сжатие
zstd. Parquet и так колоночный, zstd дожимает сильнее — меньше байт по сети до браузера.
Результат — файл около двух мегабайт вместо десятков гигабайт сырья. Такой файл DuckDB-WASM скачает мгновенно, а с учётом HTTP range requests подтянет даже не весь.
Размер опубликованного файла — это бюджет загрузки браузера. Полезное правило: публикуйте предагрегированную витрину, а не сырьё, и только нужный срез. Если дашборду нужны помесячные итоги, незачем класть в браузер поездочный уровень детализации — пусть финальная агрегация уже сделана на сервере. Чем компактнее файл, тем быстрее холодный старт дашборда.
Запрос витрины из DuckDB-WASM
Теперь браузерная сторона. Опубликованный dashboard.parquet лежит как статический файл на CDN. DuckDB-WASM, инициализированный на странице, запрашивает его по URL — так же, как нативный DuckDB читает локальный Parquet.
-- Внутри DuckDB-WASM на странице: запрос к опубликованной витрине
-- Файл подтягивается по HTTP range requests, не целиком
-- Срез для дашборда: топ зон по выручке за период
SELECT borough, zone_name, sum(revenue) AS revenue, sum(trips) AS trips
FROM 'https://cdn.example.com/publish/dashboard.parquet'
WHERE trip_date BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY ALL
ORDER BY revenue DESC
LIMIT 10;
-- Результат считается целиком в браузере, без обращения к серверу API:
-- borough | zone_name | revenue | trips
-- Manhattan | Midtown Center | 2104881.40 | 112033
-- Manhattan | Upper East Side | 1442200.05 | 88714
-- Queens | JFK Airport | 1390442.80 | 31002
-- ...
Каждый клик пользователя в дашборде — смена периода, выбор borough, переключение метрики — это новый SQL-запрос к тому же файлу, исполняемый DuckDB-WASM на вкладке. Сервера в этом цикле нет: после загрузки .wasm-модуля и Parquet-файла дашборд работает автономно. Отклик на фильтр — миллисекунды, потому что нет сетевого round-trip.
Если дашборд работает с одним фиксированным датасетом постоянно, его можно положить в OPFS (из урока про DuckDB-WASM) — тогда файл переживёт перезагрузку вкладки и не будет скачиваться заново.
Полный путь данных капстоуна
Урок про публикацию замыкает пайплайн. Соберём весь путь данных от сырого файла до пикселя в браузере — это и есть архитектура капстоуна целиком.
Обратите внимание на сквозную линию: DuckDB как движок присутствует на обоих концах. На сервере он читает сырьё, чистит, моделирует, материализует витрину в DuckLake. В браузере его WASM-сборка раздаёт интерактивные запросы. Один движок, один SQL-диалект, от десятков гигабайт сырья до интерактивного клика — это и есть «DuckDB everywhere» в применении к реальному пайплайну.
Попробуй сам
Понадобится DuckDB 1.5.x с витриной из предыдущих уроков модуля и любой способ отдать файл по HTTP (локальный статический сервер подойдёт).
Задания:
- Выгрузите компактный срез витрины через
COPY (SELECT ... WHERE trip_date >= ...) TO 'dashboard.parquet' (FORMAT parquet, COMPRESSION zstd). Сравните размер файла с объёмом исходных сырых данных. - Поэкспериментируйте с компактностью: выгрузите витрину со всеми колонками и за весь период, потом — узкий срез. Сравните размеры файлов и сформулируйте, как отбор колонок и периода влияет на бюджет загрузки браузера.
- Отдайте
dashboard.parquetпо HTTP (например, локальным статическим сервером) и откройтеshell.duckdb.org. Выполните в браузерной оболочке агрегирующий запрос к файлу по его URL — убедитесь, что расчёт идёт в браузере. - Откройте вкладку Network в DevTools и проверьте, что DuckDB-WASM скачал не весь файл, а диапазоны байт. Затем нарисуйте полный путь данных капстоуна от сырого файла до браузерного запроса и подпишите роль DuckDB на каждом шаге.
Parquet row groups: как HTTP range requests работают с Parquet