Ghost Tinybird Copy Pipe 编写规范:定时导出数据管道的约定、语法与示例解析
本文围绕 Ghost 仓库中 copy-files.md 这份 Tinybird 规则文件展开,完整覆盖它对 Copy Pipe 文件的全部编写约定(创建位置、必备键、copy_mode 取值等),并结合 tinybird skill 的 SKILL.md、同目录下的 pipe-files.md、materialized-files.md、sink-files.md 以及仓库内真实的 .pipe 文件,解析示例中每个键的含义与取值,读完你可以直接按 Ghost 的规范写出一个合法的 COPY 类型管道文件。
一、规则文件的定位:Ghost 的 Tinybird 项目本地文件体系
Ghost 的 Web 流量分析由 Tinybird 支撑。docs/codebase/post-analytics.md 中明确写道:"Web traffic comes from Ghost's Tinybird integration"。对应的 Tinybird 项目本地文件集中在 ghost/core/core/server/data/tinybird/ 目录下,按资源类型分放在 pipes/、endpoints/ 等子目录中,当前仓库共有 35 个 .pipe 文件与 5 个 .datasource 文件。
在这个体系之上,仓库为 AI Agent 与开发者沉淀了一套 Tinybird 最佳实践技能,即 .agents/skills/tinybird/SKILL.md。该 skill 声明了适用场景(创建或更新 .datasource、.pipe、.connection 资源、编写或优化 SQL、设计 endpoint schema 与数据模型、处理物化视图或 copy pipes 等),并在 "Rule Files" 一节中列出了全部规则文件清单,其中就包括本文主题的 rules/copy-files.md(同列的还有 pipe-files.md、materialized-files.md、sink-files.md、sql.md、endpoint-optimization.md、tests.md 等)。
从源码结构看,skill 把管道文件按 TYPE 拆成了四类规范:endpoint(默认查询型)、materialized(物化视图)、sink(外部系统导出)、copy(定时复制到另一数据源)。copy-files.md 就是第四类的约定文件。
二、Copy Pipe 的核心编写约定
copy-files.md 全文由 6 条规则和 1 个完整示例构成,逐条含义如下:
| 约定 | 原文规则 | 说明 |
|---|---|---|
| 按需创建 | "Do not create by default unless requested." | Copy Pipe 属于非默认资源,只有在明确需要时才新建,与 materialized、sink 两类规则中的同款措辞一致 |
| 目录位置 | "Create under /copies." |
新建文件统一放在 Tinybird 项目的 /copies 目录下,与 pipes/、endpoints/ 等目录同级组织 |
| 调度键节制 | "Do not include COPY_SCHEDULE unless explicitly requested." | 不主动添加调度键,仅当明确需要定时触发时写入 |
| 必备键 | "Use TYPE COPY and TARGET_DATASOURCE." | 文件必须声明 TYPE COPY 并通过 TARGET_DATASOURCE 指定复制目标数据源 |
| copy_mode 取值 | "The default copy_mode is append; but it's better if you set it explicitly. The other option is replace" |
默认行为是 append,但规则建议显式声明;另一可选值为 replace |
| 语法示例 | 见下文示例 | 给出一个带模板参数的完整 COPY 管道文件 |
其中两个键的语义值得展开:
TYPE COPY:声明该管道为复制型管道,与TYPE MATERIALIZED、TYPE SINK、TYPE endpoint并列。pipe-files.md 也印证了这一点,它规定允许的 TYPE 值就是endpoint、copy、materialized、sink四种。TARGET_DATASOURCE:复制的目标数据源名称。对比 materialized-files.md 可以发现命名差异——物化管道写的是DATASOURCE <name>,而复制管道写的是TARGET_DATASOURCE <name>,前者是物化结果落地表,后者强调"复制到哪里"。COPY_SCHEDULE:cron 表达式,决定管道按什么周期执行复制。示例中COPY_SCHEDULE 0 * * * *表示每小时整点执行一次。COPY_MODE:append表示每次执行向目标数据源追加新行,replace表示替换目标数据。规则建议即使打算用默认的append也要显式写出,避免依赖隐式默认值。
三、完整示例逐行解析
copy-files.md 给出的标准示例如下(原文完整保留):
DESCRIPTION Copy Pipe to export sales hour every hour to the sales_hour_copy Data Source
NODE daily_sales
SQL >
%
SELECT toStartOfDay(starting_date) day, country, sum(sales) as total_sales
FROM teams
WHERE day BETWEEN toStartOfDay(now()) - interval 1 day AND toStartOfDay(now())
and country = {{ String(country, 'US')}}
GROUP BY day, country
TYPE COPY
TARGET_DATASOURCE sales_hour_copy
COPY_SCHEDULE 0 * * * *
COPY_MODE append
按管道文件语法逐段解读:
DESCRIPTION:单行描述,说明该管道"每小时把 sales hour 数据导出到 sales_hour_copy 数据源"。描述应能让人不看 SQL 就明白管道的用途与调度节奏。NODE daily_sales:节点声明。注意 pipe-files.md 的通用约束——管道名必须唯一,节点名必须与管道名及任何资源名不同,且属性名(DESCRIPTION、NODE、SQL、TYPE 等)不允许缩进。SQL >后的%:SQL >开启多行 SQL 块,块首的%是多行标记。SQL 主体是一个典型的日粒度聚合:按天(toStartOfDay(starting_date))和国家对sales求和,并用WHERE限定窗口为"昨天到今天"。- 模板参数
{{ String(country, 'US') }}:这是 Tinybird 的参数化查询语法——country是一个 String 类型参数,默认值'US'。该规则要求参数化而不是把值硬编码进 SQL,具体参数处理规则由同 skill 的rules/sql.md约定(SKILL.md 中概括为 "SQL is SELECT-only with Tinybird templating rules and strict parameter handling")。 TYPE COPY:将本文件声明为复制型管道,而不是 endpoint 或物化视图。TARGET_DATASOURCE sales_hour_copy:复制目标数据源。执行时管道会把 SQL 结果写入该数据源。COPY_SCHEDULE 0 * * * *:cron 表达式,每小时第 0 分钟执行。对照 sink-files.md 中 sink 管道的EXPORT_SCHEDULE "*/5 * * * *"(每 5 分钟)可以看出两者都采用标准 5 段 cron 语法,只是键名不同。COPY_MODE append:显式声明追加模式。按照规则第二条的意图,这里是演示"即便等于默认值也建议写明"的最佳实践。
一个值得注意的细节:规则要求"除非明确请求,否则不要包含 COPY_SCHEDULE",而示例中却带有该键。从规则文件的组织方式看,示例承担的是完整语法演示的职责(展示每个键的书写格式),并不等于推荐在创建文件时默认加上调度;实际建文件时应遵循"默认不带 COPY_SCHEDULE"的克制原则。
四、对照仓库中的真实管道文件:materialized 与 sink 的写法差异
理解 COPY 型管道最快的方式是和仓库里真实存在的管道做对比。Ghost 当前的 ghost/core/core/server/data/tinybird/pipes/ 目录下全是 materialized 与 endpoint 两类管道,例如 mv_hits.pipe:
NODE mv_hits_1
SQL >
SELECT
site_uuid,
timestamp,
...
FROM mv_hits_0
TYPE MATERIALIZED
DATASOURCE _mv_hits
其末尾(mv_hits.pipe#L185-L186)的 TYPE MATERIALIZED + DATASOURCE _mv_hits 与 copy 管道的 TYPE COPY + TARGET_DATASOURCE 形成直接对照:物化管道用状态修饰符(sumState、uniqExactState)做增量物化,落地到 AggregatingMergeTree 引擎的目标表;而复制管道做的是"按周期把 SQL 结果搬运到另一个数据源"。materialized-files.md 还要求物化目标表的维度按基数从低到高放入 ENGINE_SORTING_KEY、聚合列使用 AggregateFunction 类型——这些键在 copy 管道中完全用不到,两类管道的文件骨架因此差异明显。
mv_daily_pages.pipe 则展示了物化管道中状态修饰符的实战用法:uniqExactState(session_id) as visits, countState() as hits,配合 GROUP BY 把原始 hit 明细预聚合成日粒度行,注释里写明目的是"reducing query scans from millions of raw hits to thousands of daily rows"。这类"预聚合降低扫描量"的思路,与 copy 示例中"每小时只复制昨日聚合结果"的轻量导出定位是同一设计哲学的两个侧面。
而 sink-files.md 定义的 sink 管道则是第三种形态:它同样"默认不创建"、要求放在 /sinks 目录,但导出方向是外部系统(Kafka、S3、GCS),键名体系也完全不同(EXPORT_CONNECTION_NAME、EXPORT_BUCKET_URI、EXPORT_FORMAT、EXPORT_COMPRESSION、EXPORT_STRATEGY 等),并且同样遵循"不主动包含调度键"的原则(其对应项是 EXPORT_SCHEDULE)。三者对照可以归纳为:
| 维度 | materialized | copy | sink |
|---|---|---|---|
| 用途 | 增量物化落地本仓数据源 | 定时复制 SQL 结果到另一数据源 | 定期导出到 Kafka/S3/GCS 等外部系统 |
| 目录约定 | /materializations |
/copies |
/sinks |
| 类型键 | TYPE MATERIALIZED |
TYPE COPY |
TYPE SINK |
| 目标键 | DATASOURCE |
TARGET_DATASOURCE |
EXPORT_CONNECTION_NAME 等一组 EXPORT_* 键 |
| 调度键 | — | COPY_SCHEDULE(默认不写) |
EXPORT_SCHEDULE(默认不写) |
| 特殊约束 | 状态修饰符 + AggregatingMergeTree 目标表 | COPY_MODE append/replace |
依赖 connection,尽量复用已有 connection |
五、在 Ghost 仓库中定位 Copy Pipe:现状与适用场景
在当前仓库中检索 TYPE COPY、COPY_MODE、TARGET_DATASOURCE 均无命中:35 个 .pipe 文件全部是 endpoint 或 materialized 类型。这与 copy-files.md 的第一条规则"默认不创建,除非被请求"是自洽的——Copy Pipe 在 Ghost 中属于预留能力,而非默认数据链路的一部分。从规则文件与真实管道的对应关系可以推断,它的典型适用场景是:
- 需要把某个聚合口径(如按天/小时的汇总)周期性搬运到另一个数据源,供下游报表或其他管道消费;
- 目标数据源需要在"全量刷新"与"追加"两种写入语义之间选择,此时通过
COPY_MODE replace或COPY_MODE append显式声明; - 希望管道保持参数化(如示例中的
{{ String(country, 'US') }}),以便同一管道服务不同站点或区域。
六、编写 Copy Pipe 文件的自检清单
综合 copy-files.md 及其引用的通用规则,新建一个 copy 管道文件前可对照检查:
- 是否确实被请求创建?未被明确请求时不创建;
- 文件是否放在
/copies目录下,且管道名全局唯一、节点名不与管道名/资源名冲突? - 属性名(DESCRIPTION、NODE、SQL、TYPE、TARGET_DATASOURCE 等)是否均无缩进?
- 是否声明了
TYPE COPY与TARGET_DATASOURCE? - 是否显式写出了
COPY_MODE append或COPY_MODE replace,而不是依赖默认值? - 是否仅在明确需要时才写入
COPY_SCHEDULE(5 段 cron)? - SQL 是否遵守 Tinybird 模板与参数规则(SELECT-only、参数化取值),复杂聚合是否遵循 SKILL.md Quick Reference 中"filter early, select only needed columns, push complex work later"的原则?
以上各条均可在 .agents/skills/tinybird/ 的规则文件组中逐条找到出处,配合 ghost/core/core/server/data/tinybird/ 下真实的 materialized/endpoint 管道作为参照,即可写出与 Ghost 仓库风格一致的 Copy Pipe 文件。
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 StartedRust0630
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
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