Ghost 的 Tinybird Mock 数据生成实践:从 `numbers(ROWS)` 查询到 `fixtures/` 落地的完整工作流
Ghost 的 Web 分析链路(文章阅读量、活跃访客、KPI 等)由 Tinybird 数据管道承载,开发阶段经常需要向本地 Data Source 灌入可复现的样例数据来验证 Pipes 与 Endpoints。本文以 mock-data.md 这份 Agent 规则文档为主体,讲清 CLI 4.0 下替代已移除的 tb mock 的完整 Mock 数据工作流:如何用 SQL 生成确定行数的 mock 行、如何以 fixtures/<datasource_name>.ndjson 的形式固化样例数据、如何通过 tb datasource append 写入 Tinybird Local,以及隔离区(quarantine)与 mode=create 报错等典型故障的排查方法。读完后你可以独立地为任意一个 Ghost Tinybird 数据源构造可重复的本地测试数据。
背景:CLI 4.0 移除了 tb mock,fixtures 目录成为官方替代方案
Tinybird CLI 4.0 移除了 tb mock 命令。cli-commands.md 中明确给出了新的标准做法:
tb mockwas removed in CLI 4.0 Use thefixtures/folder and agent skills to generate sample data, then append withtb datasource append
即:用 fixtures/ 目录承载样例数据文件,配合仓库中的 Agent 技能(skill)规则生成 mock 行,最后用 tb datasource append 写入数据源。mock-data.md 正是这一流程在 tinybird-cli-guidelines 技能中的具体规则文件,它与 SKILL.md 中"Generating mock data"的适用场景相互对应,定义了 Agent(以及人工开发者)执行 mock 数据生成时的固定步骤、SQL 约束和错误处理策略。
七步 Mock 数据生成流程
mock-data.md 给出的标准流程共 7 步,以下完整保留原文步骤并补充每步的操作细节:
- 构建一条返回 mock 行的 SQL 查询。查询的字段必须与目标数据源的
SCHEMA完全对应(类型、字段名一致),否则 append 会被 Tinybird Local 拒绝。 - 本地执行查询并指定格式与行数:使用
tb --output=json|csv '<sql>' --rows-limit <rows>命令,--output决定输出为 JSON 还是 CSV,--rows-limit限制实际取回的行数。 - 预览生成的输出,确认字段结构与随机值分布符合预期(例如时间戳是否落在合理的分区范围内)。
- 确认要创建的 fixture 文件,路径为
fixtures/<datasource_name>.ndjson或fixtures/<datasource_name>.csv,文件名与数据源名一一对应。 - 写入 fixture 文件。
- 确认追加(append)操作即将执行的命令与目标数据源。
- 把 fixture 数据 append 到 Tinybird Local 中的数据源,命令形式见 append-data.md:
tb datasource append [datasource_name] --file /path/to/local/file
流程中刻意保留了"预览→确认→写入→确认→追加"的节奏:mock 数据一旦 append 进入 MergeTree 数据源,回滚成本远高于生成成本,因此每一步都要求显式核对后再执行。
编写 Mock 查询:完整示例与两条 SQL 约束
规则文档给出了一条可直接运行的示例查询(来自 mock-data.md):
SELECT
rand() % 1000 AS experience_gained,
1 + rand() % 100 AS level,
rand() % 500 AS monster_kills,
concat('player_', toString(rand() % 10000)) AS player_id,
rand() % 50 AS pvp_kills,
rand() % 200 AS quest_completions,
now() - rand() % 86400 AS timestamp
FROM numbers(ROWS)
这条查询体现了 mock 查询的几个惯用写法:
rand() % N生成[0, N)区间内的伪随机整数,用一行表达式模拟出一个数值字段的分布(示例中的experience_gained、level等);1 + rand() % 100则是把取值范围平移到[1, 100]。concat('player_', toString(rand() % 10000))生成带前缀的字符串主键(如player_8412),toString负责整型到字符串的类型转换。now() - rand() % 86400生成"最近 24 小时内"的时间戳(86400秒为一天的秒数),保证 mock 数据的时间分布落在当前活跃分区里,而不是集中在极早的历史分区。
文档同时给出两条硬性约束(mock-data.md Notes):
- 查询必须通过
FROM numbers(ROWS)恰好返回ROWS行。numbers(N)是 ClickHouse/Tinybird 的标准系统表,返回0..N-1共 N 行,配合上面的SELECT表达式逐行物化出随机值;把行数写成字面量ROWS占位,执行时再替换为--rows-limit指定的值,可保证"请求多少行就生成多少行"。 - mock 查询本体中不要追加
FORMAT子句,也不要写结尾分号。格式化与截断的职责全部交给 CLI 的--output=json|csv与--rows-limit参数处理,SQL 文本保持纯查询语句,才能同时用于预览和落盘。
在 Ghost 仓库中落地:fixtures/ 目录与真实数据源对照
Ghost 仓库中这套约定有真实的落点:Tinybird 项目根目录位于 ghost/core/core/server/data/tinybird/,其 fixtures/ 子目录中已经存在一个符合命名规则的数据集:
- fixture 文件:analytics_events.ndjson(
analytics_events数据源 +.ndjson扩展名,完全对应第 5 步的命名规则); - 对应数据源定义:analytics_events.datasource。
对照两者的结构可以清楚看到"mock 行必须贴合 SCHEMA"这条约束的实际含义。数据源声明了 7 个字段:
SCHEMA >
`timestamp` DateTime `json:$.timestamp`,
`session_id` String `json:$.session_id`,
`action` LowCardinality(String) `json:$.action`,
`version` LowCardinality(String) `json:$.version`,
`payload` String `json:$.payload`,
`site_uuid` LowCardinality(String) `json:$.payload.site_uuid`,
`inserted_at` DateTime64(3) DEFAULT now64() `json:$.inserted_at`
而 fixture 中的每一行都是一条与之对应的 JSON 记录(NDJSON 格式,每行一个对象),例如首行的字段为 timestamp、session_id、action: "page_hit"、version: "1"、嵌套的 payload(内含 site_uuid、post_uuid、user-agent、pathname、utm_* 等字段,与 json: 提取路径一一对应)以及 inserted_at。可以推断该 fixture 的作用正是为本地分析与 Admin 后台的 stats 视图提供可复现的事件行。
另外两个仓库内的旁证值得留意:
- 分区键影响 mock 数据的时间分布。analytics_events.datasource 声明了
ENGINE_PARTITION_KEY "toYYYYMM(timestamp)"与ENGINE_SORTING_KEY "site_uuid, timestamp"。这意味着 mock 行的timestamp如果全部落在一个月前,查询走到的分区与真实流量分布会不同——示例查询中"最近 24 小时"的写法正是对这类约束的正面回应。 - 开发容器以
tb --local build构建项目。docker/tb-cli/entrypoint.sh 在启动时执行tb --local build部署 Tinybird 文件并提取 workspace token 供 Ghost 与分析服务连接,说明本地迭代以 Tinybird Local 为默认目标(与 cli-commands.md 中"--local是 append 的默认目标,--cloud才指向 Cloud"的说明一致)。 - 前端测试侧的 mock 是另一条独立路径。Admin 后台的验收测试并不依赖真实 Tinybird,而是用 MSW 直接伪造 pipe 的 HTTP 响应与
GET /tinybird/token/令牌,见 apps/admin/test-utils/acceptance/tinybird.ts。它演示了"声明的行原样返回、断言查询参数"的测试契约,与本文的 fixtures 工作流互补:前者面向前端请求层,后者面向数据层。
生成后的写入:append 到 Tinybird Local
预览与 fixture 文件确认无误后,按第 7 步把数据 append 进数据源。append-data.md 给出三种入口,按数据来源选择:
# 本地文件(mock 数据的主路径)
tb datasource append [datasource_name] --file /path/to/local/file
# 远程 URL
tb datasource append [datasource_name] --url https://example.com/data.csv
# 直接传 JSON 事件
tb datasource append [datasource_name] --events '{"a":"b", "c":"d"}'
要点:
- 该命令追加到已存在的数据源,不会清空既有数据;
- 默认目标是 Tinybird Local,需要操作 Cloud 时显式加
--cloud前缀; - 若后续要做数据替换或删除,注意 data-operations.md 的告警:
replace不应作用于正在持续写入的分区,delete --sql-condition异步执行且不会级联到下游物化视图。
错误处理:隔离区与 mode=create 报错
mock-data.md 的 Error Handling Notes 定义了两类典型故障的标准处置方式:
数据源进入隔离区(quarantine)
当 append 的行与 schema 不符等校验失败时,Tinybird Local 会把数据源置入 quarantine 状态。处置方法是:查询 <datasource_name>_quarantine 这个隔离数据源,并向上呈现前 5 行。隔离行保留了"被拒数据原样 + 失败原因"的元信息,前 5 行足够定位是字段类型不匹配、缺失必填字段还是时间格式问题,修正 mock 查询后重新生成 fixture 即可。
append 报错 must be created first with 'mode=create'
该错误表示 CLI 侧认为数据源尚未经 mode=create 流程创建。规则给出的处置是:重新构建项目后重试。对照仓库内开发容器的启动脚本,"重建项目"对应的正是 docker/tb-cli/entrypoint.sh 中的 tb --local build——重新校验并按文件构建整个 Tinybird 项目后再执行 append。
小结与延伸阅读
本文以 mock-data.md 为主线,覆盖了三块可独立复用的能力:
- 可复现的 mock 查询写法:
FROM numbers(ROWS)控制精确行数,rand() % N/concat/now() - rand() % 86400控制字段分布,SQL 本体不带FORMAT与分号; - fixtures 落地规范:
fixtures/<datasource_name>.ndjson|.csv命名,字段严格对齐数据源SCHEMA,可对照 analytics_events 数据源与 fixture 的真实样例; - 写入与排障:
tb datasource append --file追加、quarantine 前 5 行定位、mode=create报错重建重试。
如需继续深入,可参考仓库内以下资料:
- Tinybird CLI 命令总览(含
tb sql、tb local系列命令) - 数据替换与删除操作规则
- Tinybird 本地开发工作流(
tb local start/tb dev/ UI 调试) - Ghost Tinybird 项目说明 与 管道架构文档
- 后端的 post-analytics 代码文档
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00