- 后端
- 前端
- 企业应用
【免费下载链接】papermark
Papermark is the open-source DocSend alternative and secure data rooms with built-in analytics and custom domains.
导读
本文聚焦 Papermark 开源项目中lib/tinybird/目录承载的实时分析基础设施,完整讲解两件开发者高频实操:如何把新增的 Tinybird Pipe 推送到云端(tb push),以及如何在“危险区”按条件精确删除数据源中的记录(tb datasource delete)。读完本文,你将掌握 Tinybird CLI 的核心操作姿势、Papermark 五类埋点数据源与十五个分析端点的组织方式,以及避免误删线上数据的防御性操作套路。
Tinybird 在 Papermark 中的角色
Papermark 是开源的 DocSend 替代品,提供文档与数据房的链接分享、内置分析与自定义域名能力。其分析能力由 lib/tinybird/ 目录承载:这是一个完整的 Tinybird 项目,包含数据源定义(datasources)、SQL 分析端点(endpoints)、发布与查询的 TypeScript 客户端(publish.ts、pipes.ts),并以 README.md 记录了两类核心运维命令。
整个模块通过@chronark/zod-bird库与 Tinybird 云端交互,初始化代码位于 pipes.ts 与 publish.ts:
const tb = new Tinybird({ token: process.env.TINYBIRD_TOKEN!, // Tinybird is region-hosted; zod-bird defaults to the US API host. baseUrl: process.env.TINYBIRD_BASE_URL, });对应需要配置两个环境变量:TINYBIRD_TOKEN(必填,用于认证)与TINYBIRD_BASE_URL(可选,用于指定 Tinybird 区域主机,默认指向美国 API Host)。仓库的.env示例中已预留TINYBIRD_TOKEN=项。
新增 Pipe 并推送到 Tinybird
核心命令
当你基于现有 Pipe 复制出一个新分析端点、或在本地调试 SQL 后,需要将本地 Pipe 文件同步到 Tinybird 云端。官方操作只有一条命令(出自 README.md):
tb push lib/tinybird/endpoints/<PIPENAME>.pipe其中<PIPENAME>替换为 endpoints/ 目录下的实际 Pipe 文件名,例如:
# 推送“按查看者统计文档时长”的端点 tb push lib/tinybird/endpoints/get_document_duration_per_viewer.pipe # 推送“单次查看的点击事件”端点 tb push lib/tinybird/endpoints/get_click_events_by_view.pipe前提与环境准备
- 安装 Tinybird CLI(
tb),并在终端完成tb auth登录; - 确认当前工作区绑定正确的工作区(
tb workspace use <workspace>); - 确保 Pipe 文件引用的数据源(datasource)已经发布到云端,或在同一
tb push前先推送数据源文件(tb push lib/tinybird/datasources/*.datasource)。
Pipe 文件结构:以get_total_average_page_duration.pipe为例
以仓库中 get_total_average_page_duration.pipe 为例,一个端点文件由版本声明与 SQL 组成:
VERSION 5 NODE endpoint SQL > % WITH DistinctDurations AS ( SELECT versionNumber, pageNumber, viewId, SUM(duration) AS distinct_duration FROM page_views__v3 WHERE documentId = {{ String(documentId, required=true) }} AND time >= {{ Int64(since, required=true) }} AND time <= {{ Int64(until, 9999999999999) }} AND linkId NOT IN splitByChar(',', {{ String(excludedLinkIds, required=True) }}) AND viewId NOT IN splitByChar(',', {{ String(excludedViewIds, required=True) }}) GROUP BY versionNumber, pageNumber, viewId ) SELECT versionNumber, pageNumber, AVG(distinct_duration) AS avg_duration FROM DistinctDurations GROUP BY versionNumber, pageNumber ORDER BY versionNumber ASC, pageNumber ASC关键点:
VERSION N是 Pipe 的版本号,tb push时会与云端版本对比,相同版本需加--force才能覆盖;{{ String(param, required=true) }}是 Tinybird 的参数模板语法,Int64(until, 9999999999999)表示带默认值(9999999999999 即“不限截止时间”)的可选参数;splitByChar(',', {{ String(excludedLinkIds) }})演示了如何在 SQL 中消费逗号分隔的排除 ID 列表——这与下方 TypeScript 客户端里excludedLinkIds: z.string()的声明一一对应。
客户端与 Pipe 的“锁步”约定(重要实践)
lib/tinybird/pipes.ts 顶部有一段非常值得注意的注释(L13-L18):
这里声明的参数只是客户端契约:Tinybird 会静默忽略部署后的 Pipe SQL 中未引用的查询参数。因此在下方添加
until之前,必须先推送匹配的endpoints/*.pipe(tb push --force)。两者必须保持同步——SQL 里不存在的until会让人误以为时间过滤生效,实则返回的是全时间段数据。
这意味着一个极易踩坑的陷阱:zod-bird 客户端的参数声明与 Pipe SQL 中{{ }}参数不同步时,错误不会报出来,只会悄悄返回错误结果。正确的改动顺序是:
- 修改 endpoints/ 下对应
.pipe文件的 SQL,加入参数; - 执行
tb push lib/tinybird/endpoints/<PIPENAME>.pipe(版本未变时加--force); - 再同步更新 pipes.ts 中对应
buildPipe的parametersschema。
推送后可用tb pull反向验证云端 Pipe 与本地文件是否一致。
客户端侧的参数契约示例
在 pipes.ts 中,每个端点对应一个强类型查询客户端,例如按文档聚合的总平均时长(L19-L33):
export const getTotalAvgPageDuration = tb.buildPipe({ pipe: "get_total_average_page_duration__v5", parameters: z.object({ documentId: z.string(), excludedLinkIds: z.string().describe("Comma separated linkIds"), excludedViewIds: z.string().describe("Comma separated viewIds"), since: z.number(), until: z.number().optional(), }), data: z.object({ versionNumber: z.number().int(), pageNumber: z.string(), avg_duration: z.number(), }), });注意 Pipe 名带__v5后缀,与.pipe文件内的VERSION 5对应,避免不同版本共存时互相覆盖。全仓库共有 15 个此类端点,覆盖:页面时长、文档/链接/查看者/团队总时长、查看完成度、UA 信息、视频事件、点击事件、Webhook 事件与数据房文档统计等场景。
数据写入侧:Ingest Endpoint
除了查询,publish.ts 定义了 5 个数据写入端点(buildIngestEndpoint),分别对应 5 个数据源:
| 写入函数 | 数据源 | 用途 |
|---|---|---|
publishPageView | page_views__v3 | 用户浏览文档的翻页事件 |
recordWebhookEvent | webhook_events__v1 | Webhook 触发事件(含 QStash message ID) |
recordVideoView | video_views__v1 | 视频播放事件(播放率、音量、静音/聚焦/全屏状态) |
recordClickEvent | click_events__v1 | 文档内链接点击事件 |
recordLinkViewTB | pm_click_events__v1 | 访客打开链接事件(含大陆/国家等地理信息) |
这些函数同样基于 zod schema 做运行时校验与默认值填充,例如publishPageView中versionNumber限制在 1–65535、device默认"Desktop"、地理字段默认"Unknown"、referer默认"(direct)"。
Danger Zone:按条件删除数据源中的记录
官方命令
这是 README.md 中“危险区”的唯一命令,用于从指定数据源中按 SQL 条件精确删除数据:
tb datasource delete page_views__v3 --dry-run --sql-condition "viewId='VIEWID' and CAST(pageNumber AS UInt8) = PAGENUMBER" --wait参数拆解:
page_views__v3:目标数据源名,必须与云端实际名称一致(注意版本后缀__v3);--sql-condition:删除条件,用单引号包裹一段 ClickHouse SQL WHERE 子句,只有满足条件的行会被删除;--dry-run:只统计将删除的行数而不实际删除,是上线前的必做演练;--wait:等待删除任务完成并返回最终结果,避免异步任务未落库就退出。
命令本身即演示了一个典型场景:清理某次查看(viewId)在指定页号(pageNumber)上误采集的脏数据。CAST(pageNumber AS UInt8)说明pageNumber字段在 page_views.datasource 中声明为LowCardinality(String),与数字比较前需显式转型。
安全操作流程(建议顺序)
- 先 dry-run 评估影响面:对线上数据源执行
--dry-run,确认返回的待删除行数符合预期; - 核对条件字段:确认
--sql-condition引用的字段名与目标数据源 SCHEMA 完全一致(大小写敏感),并与 Pipe 查询里使用的过滤字段对齐(如viewId、documentId、time); - 去掉
--dry-run正式执行,保留--wait等待任务完成; - 用查询端点复核:通过对应的 Pipe(如
get_page_duration_per_view)查询该viewId,确认数据已消失。
为什么需要这样的删除能力
Papermark 的 page_views.datasource 采用ENGINE "MergeTree",排序键为linkId, documentId, viewId, versionNumber, pageNumber, time, id。ClickHouse 的 MergeTree 系列引擎本身不支持按条件 UPDATE/DELETE 语义(主路径只能整表 TRUNCATE),因此“按条件删行”必须借助 Tinybird 提供的tb datasource delete能力完成。这解释了为什么该命令被放在 README 的Danger Zone章节——它是为数不多能直接改写线上数据的操作,一旦--sql-condition写得过宽,就可能误删整表数据。建议删除前先在副本数据源(如page_views__v3_dryrun)上验证条件语义。
各数据源删除条件参考
删除条件的字段以数据源 SCHEMA 为准。仓库共 5 个数据源:
- page_views.datasource:
id、linkId、documentId、viewId、dataroomId、versionNumber、time(Int64 Unix 时间戳)、duration、pageNumber、地理与 UA 字段、bot、referer; - click_events.datasource:
event_id、session_id、link_id、document_id、view_id、page_number、version_number、href,按月分区(ENGINE_PARTITION_KEY "toYYYYMM(timestamp)"); - pm_click_events.datasource:访客打开链接事件,含
continent等地理字段; - video_views.datasource:
event_type、start_time、end_time、playback_rate(存储为 100/150/200 而非 1.0/1.5/2.0)、volume(0–100 而非 0.0–1.0)、is_muted/is_focused/is_fullscreen、ip_address; - webhook_events.datasource:
event_id、webhook_id、url、event、http_status、request_body、response_body、message_id。
示例删除条件:
# 删除某个 link 在指定时间窗口之前的全部浏览事件(谨慎!先 dry-run) tb datasource delete page_views__v3 --dry-run --sql-condition "linkId='link_xxx' and time < 1700000000" --wait # 删除某次查看的点击事件 tb datasource delete click_events__v1 --dry-run --sql-condition "view_id='view_xxx'" --wait常见问题与排查
tb push提示版本冲突:Pipe 的VERSION与云端相同。若是真正覆盖,使用tb push --force;若应发布为新版本,则更新.pipe文件内的VERSION号,并同步修改 pipes.ts 中pipe:名称的__vN后缀。- 查询返回空结果但客户端未报错:优先检查 pipes.ts 参数与 Pipe SQL
{{ }}模板是否同步(锁步约定),特别是新增until这类可选参数时,务必先推送 Pipe 再改客户端。 - 删除命令疑似影响过大:永远先执行
--dry-run,并根据输出行数与预期比对;条件字段务必对照目标.datasource文件的 SCHEMA 核对类型(如pageNumber为字符串需CAST)。 - 区域/认证错误:检查
TINYBIRD_TOKEN与TINYBIRD_BASE_URL环境变量是否配置正确,后者用于多区域部署时指定非美国区主机。
小结
Papermark 的 Tinybird 集成把“埋点写入”与“分析查询”全部收敛在 lib/tinybird/ 一个目录内:.datasource定义存储结构,.pipe定义 SQL 端点,pipes.ts 与 publish.ts 分别提供类型安全的查询与写入客户端。日常开发中,tb push lib/tinybird/endpoints/<PIPENAME>.pipe是发布新端点的标准动作,而tb datasource delete --dry-run --sql-condition "..." --wait则是清理脏数据时务必小心的危险操作——坚持“先 dry-run、后执行、再用 Pipe 复核”三步走,即可在享受实时分析能力的同时守住数据安全底线。
- 后端
- 前端
- 企业应用
【免费下载链接】papermark
Papermark is the open-source DocSend alternative and secure data rooms with built-in analytics and custom domains.
相关推荐
实时文档分析功能:Papermark集成Tinybird数据流处理
实时文档分析功能:Papermark集成Tinybird数据流处理 引言:文档分析的痛点与解决方案 在当今数字化时代,文档分享与协作已成为日常工作中不可或缺的一
后端前端企业应用Argilla 数据集记录操作实战:FeedbackRecord 的添加、更新与删除完整指南
Argilla 数据集记录操作实战:FeedbackRecord 的添加、更新与删除完整指南 在 Argilla 中, 记录(Record)是数据标注流程的核心
数据标注人工智能NLPMLOpsRAGLightRAG删除操作:按文档ID和实体名删除数据
LightRAG删除操作:按文档ID和实体名删除数据 引言:为什么需要精确的数据删除功能? 在RAG(Retrieval Augmented Generatio
人工智能RAG大模型知识图谱本地部署
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考