Grafana 可观测性仪表盘构建实战:Pollinations 平台的陷阱清单与最佳实践
AI 应用媒体生成后端前端【免费下载链接】pollinationsYour Friendly Open-Source Gen-AI Platform项目地址https://gitcode.com/gh_mirrors/po/pollinations点击查看免费下载本文是 Pollinations 开源仓库中 operations/observability/AGENTS.md 的深度展开。它以 pollinations.ai 生产级 Observability DashboardGrafana OSS Tinybird/ClickHouse的实战经验为骨架系统总结仪表盘 JSON 配置、SQL 查询、业务指标建模中的高频陷阱与正确做法。读完本文你将掌握 timeseries 叠加、百分比单位、图例计算、双 Y 轴、override 属性、ClickHouse 聚合查询、基于队列的转化指标建模等一套可直接复制到生产环境的 Grafana 构建方法论。一、背景这套经验从哪来用在哪儿本文全部经验来自 Pollinations 平台的 Observability Dashboard其完整架构记录在 operations/observability/README.mdLocal: Browser - localhost:4000 - Pollinations session gateway - private Grafana at 127.0.0.1:4001 Prod: Browser - observability.pollinations.ai - Cloudflare Worker - private Grafana container (Auth Proxy) - Tinybird生产环境的核心链路是浏览器 → Cloudflare Worker同源认证代理→ 私有 Grafana 容器 → TinybirdClickHouse。其中 Grafana 容器闲置 10 分钟后休眠、冷启动时从全新磁盘加载因此所有仪表盘必须通过 git 文件方式 provisioned而不能依赖 Grafana 内部数据库持久化。数据源配置见 operations/observability/provisioning/datasources/datasources.yaml类型为grafana-clickhouse-datasourceUID 固定为PAD1A0A25CD30D456host 指向clickhouse.europe-west2.gcp.tinybird.co密码通过环境变量$__env{TINYBIRD_READ_TOKEN}注入。该仪表盘只读生产环境数据staging 流量位于独立的pollinations_enter_stagingworkspace如需 staging 分析必须使用独立的读 token。当前仓库 provisioned 的仪表盘清单见 operations/observability/provisioning/dashboards/包括platform-usage-rebuild、core-api-rebuild、byop-apps-rebuild、community-models-rebuild、users-balances-rebuild、websites-rebuild六份 JSON。下文所有示例都能在这六份真实配置中找到对应实现。二、面板类型与可视化combo 图必须用 timeseries而不是 barchart反模式不要用barchart类型做组合图combo chart。Bar chart 面板无法叠加折线——如果你需要柱状 折线的混合呈现barchart 类型根本不支持强行拼接只会得到错误或残缺的渲染。推荐做法使用timeseries类型并通过fieldConfig.overrides按序列名覆盖custom.drawStyle。同一个 timeseries 面板中不同序列可以分别渲染为 bars 和 line{ type: timeseries, fieldConfig: { overrides: [ { matcher: { id: byName, options: my_bar_series }, properties: [ { id: custom.drawStyle, value: bars }, { id: custom.fillOpacity, value: 80 } ] }, { matcher: { id: byName, options: my_line_series }, properties: [ { id: custom.drawStyle, value: line }, { id: custom.axisPlacement, value: right } ] } ] } }这在仓库中不是理论而是大量实际使用在 platform-usage-rebuild.json 的 Generation volume by workload 面板中默认drawStyle为bars、fillOpacity为85、stacking为{group: A, mode: normal}而 Active users and BYOP apps 面板则把 BYOP apps 序列 override 为custom.drawStyle: line、fillOpacity: 0、lineWidth: 2并将axisPlacement设为right实现了柱状活跃用户 右轴折线BYOP 应用的经典 combo 图见 platform-usage-rebuild.json。三、百分比格式化杜绝二次乘法的单位错乱反模式不要在 SQL 里既返回小数又乘 100。如果 SQL 返回0.85十进制就不要再在 SQL 中乘以 100Grafana 的percentunit单位本身就会完成百分比换算。二次乘法会导致数值被放大 100 倍。SQL 返回Unit 设置显示结果0.85percentunit85%✅85percentunit8500%❌0.85percent0.85%❌推荐做法让 SQL 输出与单位类型严格匹配。SQL 返回0–1 小数→ 使用unit: percentunitSQL 返回0–100 数值→ 使用unit: percent同时设置边界percentunit设max: 1percent设max: 100仓库中 Models failing after the balance check 表格面板即按此规范实现SQL 中round(failed / nullIf(failed served, 0), 3)产出 0–1 小数override 中配套设置unit: percentunit与decimals: 1见 platform-usage-rebuild.json。四、图例计算Legend Calcs百分比/比率别求和反模式对百分比做 sum。把每日百分比直接相加会产生无意义的大数字例如4922%既误导读者也破坏图例可读性。推荐做法按指标类型选用合适的 calcs。legend: { calcs: [sum, mean], displayMode: table }sum适合累加型指标如 Pollen 消耗总量、调用次数mean适合百分比和比率类指标。实际配置中Active users and BYOP apps 面板对两个近似去重计数序列使用legend.calcs: [mean, max]并以displayMode: table展示见 platform-usage-rebuild.json而 Generation volume by workload 这类累加流量面板则使用calcs: [sum]、displayMode: list、placement: bottom见 platform-usage-rebuild.json。两种场景的取舍与文档建议完全一致。五、X 轴标签用间隔代替逐点标注反模式显示每一个数据点的标签。当数据跨度达到 60 天时逐日标签必然互相重叠整条轴变成无法阅读的黑块。推荐做法增大标签间距。options: { xTickLabelRotation: 0, xTickLabelSpacing: 200 }xTickLabelSpacing: 200的效果是大约每 7 天才显示一个标签约每周一个而不是每天一个。这是纯展示层配置不影响数据密度与 tooltip 精度。六、面板描述保持单行别用\n反模式在 JSON 的description字段里使用\n换行。Grafana 的 tooltip 不会渲染转义换行符\n会原样显示为字面量看起来非常业余。推荐做法描述保持简洁的单行文本。description: Daily Pollen: Paid (green) vs Quest (orange).仓库中所有面板描述均遵守此约定例如 platform-usage-rebuild.json 的仪表盘级 description以及各面板如 Daily successful generations by event workload from generation_usage_hourly. Workload is the generation_event_v2 event_type; no health or error interpretation is applied.——全部是单行、信息完整、无转义换行。七、ClickHouse/Tinybird 查询规范7.1 生成事件只读 V2V1 已退役generation_eventV1 已经退役。所有可观测性仪表盘与 Tinybird 查询必须读取generation_event_v2永远不要恢复或添加 V1 回退。这与仓库事实一致Tinybird 侧的全部物化管道均以generation_events_classified基于 V2 的分类视图为数据源例如 generation_usage_hourly_mv.pipe 中的FROM generation_events_classified。7.2 产品路径归因BYOP 与 BYOM 是两个独立维度核心业务语义来自 AGENTS.mdBYOPBring Your Own Pollen开发者应用内的、以终端用户身份认证的消耗对应is_byopBYOMBring Your Own Model任何社区模型的消耗对应is_communityDirect API既不 BYOP 也不 BYOM 的直接 API 消耗。加和型产品路径图必须暴露四条互斥路径Direct API、BYOM direct、BYOP with Pollinations models、BYOP with BYOM同时把Paid 与 Quest 作为独立的资金维度funding dimension处理不要混入产品路径维度。仓库中的实现验证了这套四分类逻辑。Paid Pollen consumption by product path 面板在 SQL 中用multiIf把is_byop/is_community两个布尔量映射成唯一路径字符串multiIf( is_byop 1 AND is_community 1, BYOP · BYOM, is_byop 1, BYOP · Pollinations models, is_community 1, BYOM · Direct, Direct API ) AS product_path随后以sumMergeIf(gross_consumption, funding_source paid)与funding_source quest分别产出 Paid 与 Quest 两个资金流见 platform-usage-rebuild.json并通过partitionByValuesrenameByRegex转换把同一查询结果拆成两张图Paid / Quest复用查询、零额外 Tinybird 请求。而is_byop、is_community这两个维度本身就在物化管道中定义api_key_client_id NOT IN (, undefined) AS is_byop、model_provider_used community AS is_community见 generation_usage_hourly_mv.pipe。7.3 活跃用户与应用只统计 funded 请求关键陷阱generation_usage_hourly中的users状态与byop_app_daily中的apps状态会统计每一个请求包括因 Pollen 不足被 402 拒绝的请求。因此统计活跃用户、活跃应用时必须过滤funding_source ! unfundeduniqMergeIf(users, funding_source ! unfunded) uniqIfMergeIf(apps, funding_source ! unfunded)如果不加这个过滤WAU周活跃用户会比 KPI 应用显示的数值高出约 24%。这是文档中给出的实测偏差务必在查询层直接规避。仓库证据充分在 platform-usage-rebuild.json 的 Platform overview 统计面板中active_users与active_byop_apps分别用uniqMergeIf(users, funding_source ! unfunded)和uniqIfMergeIf(apps, funding_source ! unfunded)计算面板描述也明确注明 402 rejections excludedActive users and BYOP apps 时序面板使用同样的写法见 platform-usage-rebuild.json。7.4 不要猜测字段值反模式凭记忆写过滤条件。一个错误的字段值会直接返回零行而且很难排查。过滤某列之前先查一下线上真实存在的值。这一点在使用 ClickHouse/Tinybird 这类强类型列式库时尤其重要——它不会宽容地忽略未知枚举值。7.5 用funding_source拆分资金而不是 meter sluggeneration_usage_hourly与byop_app_daily两张表都带有funding_source列取值paid、quest、unfunded。它由各自的物化管道从selected_meter_slug映射而来见 generation_usage_hourly_mv.pipe 与 byop_app_daily_mv.pipemultiIf( selected_meter_slug IN (v1:meter:pack, local:pack), paid, selected_meter_slug IN (v1:meter:tier, local:tier), quest, unfunded ) AS funding_source仪表盘 SQL 一律过滤funding_source而不是 meter slug——meter slug 是底层实现细节未来新增/改名会破坏查询funding_source才是稳定的业务语义层。7.6 不要忘记时间过滤器反模式查询里没有$__timeFilter()。少了它无论仪表盘时间选择器选了什么区间查询都会返回全量数据——结果就是图表与时间选择器脱钩数据量也白白翻倍。推荐做法每个查询都带上 Grafana 时间宏并按环境过滤WHERE $__timeFilter(start_time) AND environment production仓库中每个面板的rawSql都遵循这一模式只是由于物化表按小时聚合并同时覆盖 staging 验证夹具实际写法为WHERE $__timeFilter(hour) AND environment IN (production, debug-prod-copy)例如 platform-usage-rebuild.json。debug-prod-copy是隔离的 staging 数据副本生产仪表盘用同一套查询同时校验 staging 与生产避免两套 SQL 分叉。八、双 Y 轴面板显式指定轴位反模式忘记设置custom.axisPlacement。不做显式配置时所有序列默认共用左轴双 Y 轴面板形同虚设且不同量纲如Pollen 数量与百分比叠加在同一条轴上会互相压扁。推荐做法在 override 里把第二条轴放到右侧并配齐轴标签、单位与边界{ matcher: { id: byName, options: percentage_series }, properties: [ { id: custom.axisPlacement, value: right }, { id: custom.axisLabel, value: % }, { id: unit, value: percentunit }, { id: min, value: 0 }, { id: max, value: 1 } ] }仓库实战platform-usage 中 Active users and BYOP apps 面板把active_byop_apps放在右轴并配custom.axisLabel: BYOP apps见 platform-usage-rebuild.jsonRequest outcomes 面板把unfunded_users折线放在右轴axisLabel: Users见 platform-usage-rebuild.json。另外值得注意测试套件强制所有线性 timeseries 的 y 轴从零开始日志轴和可负余额面板除外这保证了右轴百分比/计数不会被截断放大见 test/dashboards.test.ts。九、隐藏辅助列别让中间计算结果污染图例反模式展示仅用于计算的中间列。比如developer_total这类只用来算百分比的列会出现在图例和 tooltip 中干扰读者对真实指标的判断。推荐做法用 override 隐藏它们。{ matcher: { id: byName, options: developer_total }, properties: [ { id: custom.hideFrom, value: { legend: true, tooltip: true, viz: true } } ] }hideFrom的三个布尔位分别控制从图例、tooltip、可视化图形中隐藏。仓库中还有一种等价思路——用**转换transformations**在渲染前直接裁剪字段Paid Pollen 面板通过filterFieldsByName只保留匹配/^(time.*|paid_pollen.*)$/的字段再用renameByRegex把paid_pollen (路径名)重命名为路径名从而让开发人员看不到的中间列根本不会进入面板数据流见 platform-usage-rebuild.json。十、堆叠Stacking百分比折线与绝对值不要混堆反模式把百分比折线堆叠到 Pollen 绝对值柱状图上。两者量纲完全不同——百分比上限是 1或 100绝对值可能上千上万堆叠后比例线会被压缩成一条贴地的直线。推荐做法对叠加的覆盖线显式关闭堆叠{ id: custom.stacking, value: { mode: none } }mode支持normal同组堆叠与none不堆叠。仓库中 Request outcomes 面板是教科书式组合served/rejected_no_pollen/failed_after_balance_check三根柱状用stacking: {group: A, mode: normal}叠加成全部请求三部分之和见 platform-usage-rebuild.json而同面板的unfunded_users折线则 override 为custom.stacking: {group: A, mode: none}见 platform-usage-rebuild.json确保这条右轴用户曲线不被堆叠。十一、转化率与业务逻辑指标先建模再写 SQL11.1 反模式设计结构性恒为零的指标典型例子是Paid-only users纯付费用户在免费 tier 先消费、付费 pack 后消费的系统中任何用户都不可能只用 pack 而从不触碰 tier。业务规则来自 AGENTS.mdEveryone gets free Pollen → Tier consumed first → Pack consumed second.结果Paid-only users 恒等于 0——因为不使用 tier 就不可能用到 pack这个指标从定义上就是死指标。11.2 推荐做法按真实用户旅程建模——队列式转化用队列cohort式转化衡量从首次 tier 使用到首次 pack 使用的时间差而不是设计一个恒零的布尔指标-- Cohort conversion: users who converted within 7 days SELECT countIf(first_pack IS NOT NULL AND dateDiff(day, first_tier, first_pack) 7) as converted_7d FROM ( SELECT user_id, minIf(start_time, selected_meter_slug v1:meter:tier) as first_tier, minIf(start_time, selected_meter_slug v1:meter:pack) as first_pack FROM generation_event_v2 WHERE environment production AND total_price 0 GROUP BY user_id HAVING first_tier IS NOT NULL )这套 SQL 是可直接运行的完整模板minIf取每个用户各自旅程中的首次时间戳外层dateDiff计算转化窗口HAVING first_tier IS NOT NULL排除从未进入 tier 的用户。注意这里同时体现了本文的多个原则——V2 表、environment过滤、对 meter slug 的底层引用仅用于首次事件这种不可由funding_source表达的粒度。11.3 推荐做法用首次活动作为注册代理如果注册但从未使用产品的用户没有意义那么用首次事件时间作为注册时间而不是真实的注册时间戳。这样既避免了跨数据源 join注册数据在 D1事件数据在 Tinybird又把指标聚焦在活跃用户上。仓库中确实存在这一模式的独立物化实现user_first_activity_mv由 user_first_activity_populate.pipe 增量维护核心就是minState(start_time) AS first_activity_time并限定traffic_group regular。文档中的signup proxy建议与这条管道互为印证。十二、仪表盘变量与时间范围约定每个仪表盘默认展示最近 30 个完整 UTC 日JSON 配置固定为time: { from: now-30d/d, to: now-1d/d }, timezone: utc, refresh: 1h这样设计的三个理由见 AGENTS.md没有任何一天的生成数据是不完整的——to: now-1d/d排除了进行中的今天Grafana 的日期与 SQL 的 UTC 日期完全对齐——注册数面板的最后一根柱会在每日约 03:20 UTC 的d1_user同步完成后填充1 小时刷新足以跟进数据更新节奏又不会频繁打爆 Tinybird。实际配置验证于 platform-usage-rebuild.jsonrefresh: 1h、time: {from: now-30d/d, to: now-1d/d}、timezone: utc且templating.list为空数组。kiosk 模式与头部时间选择器应用以 kiosk 模式嵌入 Grafana隐藏其导航与时间选择器头部选择器改为通过 iframe URL 参数下发 7 / 30 / 90 天fromnow-nd/dtonow-1d/d默认 30 天。前端实现在 frontend/src/dashboards.tsRANGE_LABELS定义 7d/30d/90d与 dashboardSrc拼接?kioskhideLogofromnow-${range}/dtonow-1d/d。对应的 URL 解析与回退逻辑未知 range 一律回退 30d由 test/dashboards.test.ts 覆盖。为什么没有自定义过滤变量自定义 filter 变量被有意移除——保持仪表盘简单、聚焦于回答战略性业务问题而不是鼓励临时 ad-hoc 过滤。如果将来确实需要加变量请直接编辑仪表盘 JSON 中的templating.list数组注意provisioned 仪表盘通过 API 是只读的不能在 Grafana UI 里改。templating.list为空即当前所有仪表盘的状态见 platform-usage-rebuild.json。十三、工作流从改 JSON 到上线编辑provisioning/dashboards/下的仪表盘 JSON。Grafana 会拒绝通过 UI 保存 provisioned 仪表盘——文件才是唯一事实来源。配置侧的证据在 dashboards.yamltype: file、disableDeletion: true、allowUiUpdates: false、updateIntervalSeconds: 30文件改动后 30 秒内自动热加载。运行测试在operations/observability目录执行npm test。测试脚本定义于 package.jsonnode --experimental-strip-types --test test/*.test.tstest/dashboards.test.ts 会校验仪表盘列表排序与 URL 回退逻辑、kiosk 模式 URL 拼接、所有线性 timeseries 的 y 轴从零开始、以及仪表盘 SQL 绝不读取 Grafana 读 token 无权访问的内部 Tinybird pipes见 dashboards.test.ts。部署通过 .github/workflows/deploy-applications.yml 从production分支触发。workflow 按变更文件自动发现受影响应用operations/deployment/discover.cjs并使用独立的CLOUDFLARE_API_TOKEN_OBSERVABILITY凭据执行部署Grafana 容器启动时从磁盘加载provisioning/目录因此部署即生效。如果想在 UI 中快速建面板做原型README.md 给出了标准流程打开本地 Grafana → UI 编辑会拒绝保存→ Export →Export as code → Advanced → Model: Classic→ 复制 JSON 替换仓库中对应文件 →docker compose restart grafana验证。注意必须选Classic 模型默认的 V2 Resource JSON 无法被文件 provisioning 方式加载。十四、XY 散点图颜色维度会偷走一条 Y 轴反模式用颜色维度表达连续渐变。Grafana XY 图表对任何用作颜色维度的字段都会单独创建一条 Y 轴——即使你只是想给散点染色它也会把该字段当成第二条数值轴处理图表立刻失真。推荐做法散点图用固定颜色{ fieldConfig: { defaults: { color: { mode: fixed, fixedColor: green } } }, options: { dims: { x: tier_cost, y: pack_cost } } }如果确实需要颜色渐变表达第三维改用表格 单元格着色cellOptions/thresholds更合适而不是在 XY 图上硬塞颜色维度。十五、快速参考常用 Override 属性速查表属性可选值用途custom.drawStyleline,bars,points逐序列设置图表类型custom.fillOpacity0-100面积填充透明度custom.axisPlacementleft,right,hidden双 Y 轴控制custom.stacking{ mode: normal }或{ mode: none }堆叠开关custom.lineWidth1-10线条粗细custom.hideFrom{ legend: true, tooltip: true, viz: true }隐藏序列unitshort,percent,percentunit,currencyUSD数值格式化displayNamestring图例标签color{ fixedColor: green, mode: fixed }序列颜色这九项几乎覆盖了生产仪表盘 90% 的 override 场景全部可在 operations/observability/provisioning/dashboards/ 的六个 JSON 文件中找到实际用例。十六、本地开发与调试速查配合 README.md 的常用命令可以把上述配置规范落到本地验证# 本地启动/查看日志/停止 docker compose up -d docker compose logs -f grafana docker compose down # 重置 admin 密码 docker compose exec grafana grafana cli admin reset-admin-password NEW_PASSWORD # 测试数据源健康 curl -s -u admin:$GF_ADMIN_PASSWORD http://localhost:3000/api/datasources/uid/PAD1A0A25CD30D456/health常见的三类故障与排查方向见 README.md公网 URL 重定向到 Myceli originGF_SERVER_ROOT_URL必须配置为https://observability.pollinations.ai插件健康检查失败检查TINYBIRD_READ_TOKEN是否正确、容器能否连通 Tinybird仪表盘加载不出来核对 dashboard JSON 里的 datasource UID 与 provisioning 配置是否一致固定为PAD1A0A25CD30D456。结语这份指南的价值在于每一个反模式都来自 pollinations.ai 生产仪表盘的真实踩坑每一个推荐做法都能在仓库的六份 dashboard JSON、Tinybird 物化管道与自动化测试中找到落点。把funding_source过滤、V2-only 事件表、队列式转化建模、kiosk 时间约定、override 属性速查表这五件事做对你的 Grafana 仪表盘就能同时满足数字可信与可读性好两个要求。深入源码请从 operations/observability/provisioning/dashboards/、enter.pollinations.ai/observability/materializations/ 与 operations/observability/test/dashboards.test.ts 三个入口开始。赞分享AI 应用媒体生成后端前端【免费下载链接】pollinationsYour Friendly Open-Source Gen-AI Platform项目地址https://gitcode.com/gh_mirrors/po/pollinations点击查看免费下载相关推荐Pollinations Observability 仪表盘架构解析Grafana Tinybird Cloudflare 的可观测性实战指南Pollinations Observability 仪表盘架构解析Grafana Tinybird Cloudflare 的可观测性实战指南 PolAI 应用媒体生成后端前端LitmusChaos 控制平面可观测性实战Prometheus 指标采集与 Grafana 仪表盘搭建指南LitmusChaos 控制平面可观测性实战Prometheus 指标采集与 Grafana 仪表盘搭建指南 LitmusChaos 控制平面ChaosCe云原生运维可观测性awesome-kubernetes中的监控可视化Grafana仪表盘设计与最佳实践awesome kubernetes中的监控可视化Grafana仪表盘设计与最佳实践 在Kubernetes容器编排系统环境中有效的监控可视化是保障集群文档云原生上一篇Fusion Pixel Font3分钟掌握免费开源像素字体的终极使用指南下一篇OpenRocket火箭仿真从零开始的完整指南让航空航天设计变得简单有趣 创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考