深度解析 Lightdash Playground Bundle:基于 DuckDB 与 dbt 的教学仓库构建流水线
【免费下载链接】lightdashAgentic BI. Analytics at the speed of code ⚡️项目地址: https://gitcode.com/GitHub_Trending/li/lightdash
导读
scripts/playground-bundle是 Lightdash 仓库内一套独立的构建子系统,负责把示例项目examples/full-jaffle-shop-demo/dbt打包成可随产品分发的轻量教学数据包(Playground bundle)。它通过@duckdb/node-api加载 CSV seed、以 dbt-duckdb 物化模型,再借 Lightdash 后端的项目适配器编译 Explore,最终产出 DuckDB 数据库与预编译的explores.json、content.json。读完本文,你将掌握这套"源码 → 编译 → 校验 → 校验和发布"的完整链路,知道如何在content.ts/teachingContent.ts中维护教学样例,以及如何用pnpm test:playground-content在不重建仓库的情况下保证源定义与已发布产物的一致性。
Playground Bundle 是什么
Lightdash 的 Playground(试用/演示项目)在用户初始化时会用到一套预置的 Jaffle Shop 教学数据集。为了让 Playground 项目启动即用、无需现场执行昂贵的 dbt 编译,仓库维护了一套"版本化的预编译产物":
- 一个 Jaffle Shop 的 DuckDB 数据仓库文件;
- 预编译的 Lightdash Explore 列表;
- 预置的图表、仪表盘等教学内容定义。
scripts/playground-bundle/目录就是这套产物的构建源头。按照 scripts/playground-bundle/README.md 的说明,构建过程会:
- 用
@duckdb/node-api将每个 CSV seed 加载为物理表; - 用 dbt-duckdb 物化 dbt 模型;
- 通过 Lightdash 后端的项目适配器(
DbtLocalProjectAdapter)编译 Explore。
产物的消费端位于packages/backend/assets/playground/,包含 4 个提交进仓库的文件:
| 文件 | 内容 |
|---|---|
jaffle_shop.duckdb | 物化后的 DuckDB 数据仓库(含jaffleschema) |
explores.json | 预编译的 Explore 定义(单行 JSON) |
content.json | 教学内容的版本化定义(图表、仪表盘等,单行 JSON) |
SHA256SUMS | 上述三个产物的 SHA-256 校验和清单 |
为什么产物要"小而确定"
临时副本 + view 物化
构建时不会直接修改仓库中已检入的示例项目。build.ts会在系统临时目录创建两份隔离资源:一份 dbt 项目副本(通过cp复制examples/full-jaffle-shop-demo/dbt,并排除target目录),一份临时的 dbt-duckdb profile。
为了让最终二进制文件足够小,副本中所有模型默认物化方式由table改写为view。这一改写逻辑独立在 scripts/playground-bundle/projectYaml.ts 中:
export const replaceTableMaterializations = (projectYaml: string): string => { const tableMaterialization = ' materialized: table'; if (!projectYaml.includes(tableMaterialization)) { throw new Error( 'Playground dbt project must define a table materialization', ); } return projectYaml.replaceAll( tableMaterialization, ' materialized: view', ); };如果示例项目未来不再声明任何 table 物化,构建会直接抛错,防止静默失效。
CSV seed 确定性截断
CSV seed 保持为物理表,但被确定性地截断——每个 seed 最多 5000 行(build.ts中的const maxSeedRows = 5_000),避免高数据量演示 seed 撑大捆绑产物。加载时先创建jaffleschema,再对排序后的每个 CSV 执行:
CREATE TABLE jaffle."<table>" AS SELECT * FROM read_csv_auto('<seed路径>', header = true) LIMIT 5000同样在build.ts中,DuckDB 数据类型到 LightdashDimensionType的映射也有明确规则(typeFromDuckDb):BOOL→ 布尔,TIMESTAMP/TIME→ 时间戳,DATE→ 日期,INT/DECIMAL/NUMERIC/DOUBLE/FLOAT/REAL→ 数值,其余 → 字符串。
一次性环境搭建
按 README 的 Setup 章节,首次构建前需要创建隔离且被 gitignore 的 Python 环境(仅需一次):
python3 -m venv scripts/playground-bundle/.venv scripts/playground-bundle/.venv/bin/pip install \ 'dbt-core==1.10.0' 'dbt-duckdb==1.10.0' ln -sf dbt scripts/playground-bundle/.venv/bin/dbt1.10关键点在于版本被精确锁定:dbt-core 与 dbt-duckdb 都固定为1.10.0,与build.ts中传给DbtLocalProjectAdapter的dbtVersion: SupportedDbtVersions.V1_10保持一致。锁版本直接服务于"确定性产物"目标——只有输入不变,SHA256SUMS中的校验和才能保持稳定、可复现。
执行构建
在仓库根目录运行:
pnpm build:playground-bundle查看 package.json,该命令实际展开为:
pnpm formula:build && pnpm common-build && pnpm warehouses-build && \ LIGHTDASH_MODE=development LIGHTDASH_SECRET=playground-bundle-build-only \ S3_ENDPOINT=http://localhost S3_BUCKET=playground-build S3_REGION=local \ tsx scripts/playground-bundle/build.ts几个值得注意的环境细节:
- 前置构建
formula、common、warehouses三个包,为后续 TypeScript 导入@lightdash/common类型、@lightdash/warehouses的 DuckDB 客户端做准备; LIGHTDASH_MODE=development、LIGHTDASH_SECRET=playground-bundle-build-only是构建期占位值(明文提示这是"仅供构建"的临时 secret);S3_*环境变量同样为构建期占位,避免真实外部依赖。
构建成功后,build.ts末尾会输出一行摘要:Built playground bundle: ${seedCount} seeds, ${explores.length} explores,可直接核对 seed 与 Explore 数量。
构建流水线的源码级拆解
scripts/playground-bundle/build.ts(共 307 行)是整条流水线的实现主体,可划分为 6 个阶段:
1. 加载 DuckDB 依赖
loadDuckDb通过createRequire从packages/warehouses/package.json解析@duckdb/node-api,再以import(pathToFileURL(...))方式动态加载DuckDBInstance,创建数据库时显式指定default_block_size: '16384'。连接与关闭通过withDatabase辅助函数统一管理(finally中保证closeSync)。
2. 加载 seed(loadSeeds)
先删除旧数据库文件,然后递归读取示例项目data/目录下全部 CSV(排序保证顺序确定性),逐一创建表并截断到 5000 行。
3. 准备临时 dbt 环境
mkdtemp生成两个临时目录:profiles目录写入一份临时profiles.yml(type 为duckdb、指向输出数据库路径、schema 为jaffle、threads 为 4);- 项目目录复制自
examples/full-jaffle-shop-demo/dbt,随后执行replaceTableMaterializations改写物化方式; - 通过修改
process.env.PATH将.venv/bin置于最前,使execFile能直接找到 dbt 可执行文件; - 构造
DbtLocalProjectAdapter,传入DuckdbWarehouseClient与上述临时目录,dbt 版本固定为 V1_10。
4. 执行 dbt run
以execFile方式调用.venv/bin/dbt run,显式传入--profiles-dir、--project-dir、--target。异常时会把子进程的stdout/stderr转发到当前进程后重新抛出,便于排查 SQL 或配置错误。
5. 编译 Explore 与校验
getCatalog从information_schema.columns查询jaffleschema 下所有表与列,组装成 Lightdash 需要的仓库 catalog,注入adapter.cachedWarehouse.warehouseCatalog,随后调用adapter.compileAllExplores()得到全部 Explore(含可能出现的ExploreError)。这一步正是 README 所说"通过 Lightdash 后端项目适配器编译 Explore"的具体实现。
6. 输出与清理
三个产物分别以单行 JSON(末尾带换行)或二进制形式写入,并用createHash('sha256')计算校验和,生成SHA256SUMS。finally块负责还原PATH、销毁 adapter、删除两个临时目录——构建不留任何临时痕迹。
教学内容定义:content.ts 与 teachingContent.ts
教学内容由两份 TypeScript 源文件驱动,最终合并进content.json,其类型契约定义在 packages/backend/src/ee/services/ProjectService/playgroundContentTypes.ts 的PlaygroundContent中:
| 字段 | 说明 |
|---|---|
version | 内容 schema 版本(当前为1) |
space | 教学空间的名字与路径 |
charts | 预置图表定义(key+slug+ 指标查询 + 图表配置) |
dashboard | 预置仪表盘(tabs、tiles 布局) |
pinned | 主页置顶项(按 chart key / dashboard slug) |
comments | 图表上的教学评论 |
categories | 指标目录分类(可选yamlReference,有则与指标按引用匹配,无则是在应用内手工创建的分类) |
metricsTrees | 预置的指标树(节点含表名、指标名与坐标) |
dataApps | 预构建的数据应用(内置files与source两套文件) |
agent | 项目的 AI Agent(供 Ask AI 走查使用) |
deepResearch | 一次已完成深度研究的结果(问题、线程标题、报告 Markdown) |
预置图表示例
scripts/playground-bundle/content.ts 中定义了 3 张图表与 1 个仪表盘:
orders-over-time:按月订单量折线图(dimension 为orders_order_date_month,metric 为orders_unique_order_count);revenue-by-payment-method:按支付方式的收入条形图(flipAxes: true);top-customers:客户收入排名表(ChartType.TABLE,列顺序含customers_first_name、customers_last_name、payments_total_revenue)。
仪表盘jaffle-shop-overview定义了 3 个 tab(Orders trend / Revenue split / Top customers),每个 tab 内是HEADING标题块 +SAVED_CHART图表块,SAVED_CHART通过properties.chartKey(而非 savedChartUuid)引用上述图表 key——这是 Playground 内容独有的解耦方式,见PlaygroundDashboardChartTile类型。
大学教学样例(teachingContent.ts)
scripts/playground-bundle/teachingContent.ts 通过...teachingContent展开合并进content.json,包含 README 提到的全部"University 额外样例":
pinned:置顶仪表盘jaffle-shop-overview;comments:一条挂在orders-over-time图上的教学评论(提示检查 2025 年初促销尖峰是否为首次订单);categories:Sales、Revenue growth、Core、Experimental、Weekly review 等 5 个分类(含颜色);dataApps:预构建的jaffle-pulse单页应用——files下内联了完整的index.html(构建产物,运行时直接伺服),source下内联了src/App.tsx(源码,打包为源码归档供 CLI 下载),因此播种时无需触发任何沙箱构建;agent:Jaffle analyst,带完整instruction提示词;deepResearch:一份"春季退货为何上升"的完整研究报告(resultMarkdown,含#标题、##发现与##结论),并记录了durationMs: 412000与warehouseQueryCount: 9作为走查参考数据。
对应的类型注释(playgroundContentTypes.ts 中PlaygroundDataAppDefinition、PlaygroundDeepResearchDefinition等)明确解释了设计意图:预构建应用是为了避免播种时运行构建,预置 deep research 是为了让学习者无需真正发起一次运行就能阅读报告。
构建期的强校验
build.ts的validatePlaygroundContent在写入产物前执行三类检查:
- 图表 key 唯一性:重复 key 直接抛错;
- Explore 可用性:每个图表的
metricQuery.exploreName必须在刚编译出的 Explore 中存在,且不是ExploreError(即编译失败不可见); - 字段可用性:对图表的 dimensions、metrics 以及排序字段逐一用
findFieldByIdInExplore验证;若字段带有requiredAttributes/anyAttributes/tablesRequiredAttributes(受限属性字段),也会抛错,防止教学图表引用普通用户无权限访问的字段。
此外,仪表盘每个saved_charttile 的chartKey必须能命中图表集合。因此,任何引用了不存在 Explore、字段或图表的教学内容都会在构建阶段失败,而不是等到 Playground 运行时才暴露。
内容一致性回归测试
不重建数据仓库、不改动任何产物文件,也能完整校验"源定义 == 已发布产物":
pnpm test:playground-content该命令(package.json)等价于tsx scripts/playground-bundle/content.test.ts。scripts/playground-bundle/content.test.ts 做的事情非常直接:
- 读取
packages/backend/assets/playground/content.json并解析; assert.deepEqual对比playgroundContent对象与产物内容——覆盖每一个字段,包括预构建应用的 built/source 文件和完整的研究 Markdown;- 进一步断言
JSON.stringify(playgroundContent) + '\n'与产物文件的字节级一致,确保序列化字节也完全不变。
这套测试保证:即使某次重建遗漏了教学样例,CI 或本地运行pnpm test:playground-content也会立即报错,杜绝"重建悄悄丢掉教学样本"的风险。
校验和验证与日常维护
产物重建后,可用标准工具核验:
sha256sum --check packages/backend/assets/playground/SHA256SUMS在 dbt 版本锁定(1.10.0)且输入不变的前提下,已提交的校验和应保持稳定;一旦示例项目数据或教学内容变更,校验和会随之变化,此时需要将源文件与产物一并更新并提交(README 明确要求"Update the source and shipped bundle together")。
日常维护的教学内容改动应遵循以下分层:
- 图表、仪表盘、指标树:编辑 scripts/playground-bundle/content.ts;
- 置顶、评论、分类、预构建应用、Agent、深度研究报告:编辑 scripts/playground-bundle/teachingContent.ts;
- 两类源都会进入同一次构建,合并后写入
content.json; - 修改后同时运行
pnpm test:playground-content与pnpm build:playground-bundle,并更新SHA256SUMS。
小结
Playground bundle 是 Lightdash"以代码速度交付分析"理念在教学场景下的工程化落地:用临时 dbt 副本 + view 物化 + seed 截断控制产物体积,用锁定版本的 dbt 与确定性输入保证可复现,用编译期强校验和字节级 parity 测试守住内容一致性,最终让 Playground 项目一启动就拥有一套完整、可编辑、可走查的 Jaffle Shop 教学环境。
【免费下载链接】lightdashAgentic BI. Analytics at the speed of code ⚡️项目地址: https://gitcode.com/GitHub_Trending/li/lightdash
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考