引言
Superset 是 Apache 基金会的开源 BI 平台,定位在「分析师能自己拖拽出图表、不用等数据团队排期」这个场景。它的核心不是图表渲染(渲染用的是 ECharts 与 deck.gl),而是把查询、权限、缓存、调度这四件事平台化:谁能在哪个数据集上查什么、查询结果能不能复用、慢查询怎么异步化、指标异常怎么自动推送。
工程落地的主要难点有四类。元数据治理:数据集字段的中文名、计算列、指标定义散落在各处,没有统一的语义层,同一个「活跃用户」在不同图表里算法不同。权限粒度:行级安全(RLS)要求把「华南区销售只能看华南数据」这类规则落到 SQL 层,而不是靠应用层过滤。查询性能:Superset 自身不做计算,所有压力都转嫁给底层数据库,接一个未经优化的 MySQL 会直接拖垮生产库。缓存失效:数据更新后缓存必须及时失效,否则会出现「报表显示的是昨天的数」。
本文按「架构 → 部署 → 语义层 → 权限 → 性能 → 运维」的顺序展开,以 Superset 4.x 为基准,配置片段基于 superset_config.py 的真实结构。
目录
- Superset 的架构与组件
- 部署方式与元数据库选型
- 数据库连接与驱动配置
- 数据集与语义层建模
- 图表类型与可视化插件
- 仪表盘与原生的过滤器
- 行级安全与权限模型
- 查询缓存与异步执行
- 告警与报表订阅
- SQL Lab 与性能诊断
- 与 OLAP 数仓的集成要点
- 生产运维与版本升级
1. Superset 的架构与组件
Superset 是单体 Python 应用(Flask + Flask-AppBuilder),进程角色分四种。
Web 服务:处理 HTTP 请求、渲染前端(React SPA)、执行图表查询的协调逻辑。可以水平扩展多个副本。
Celery Worker:执行异步任务,包括异步查询(async_queries)、告警与报表(reports)、缓存预热(cache_warmup)、缩略图(thumbnails)。
Celery Beat:定时调度器,按 cron 触发上面的任务。
元数据库:存图表定义、数据集、用户、权限、日志。默认 SQLite,生产必须换成 PostgreSQL——SQLite 在并发写入下会锁库,Celery Beat 与 Web 同时写会直接报错。
浏览器 ──▶ Superset Web ──┬──▶ 元数据库 PostgreSQL(图表/数据集/权限)
├──▶ 缓存 Redis(查询结果、异步任务中转)
└──▶ 业务数据源(ClickHouse / PG / MySQL)
Celery Beat ──▶ Celery Worker ──▶ 异步查询 / 告警报表 / 缓存预热
关键认知是:Superset 不存储业务数据,也不做聚合计算。它把图表定义翻译成 SQL 发给底层引擎,把结果画成图。所以 Superset 的性能几乎完全取决于底层数据库。
2. 部署方式与元数据库选型
三种部署路径的取舍很清楚:Docker Compose 适合单机与评估;Kubernetes(Helm chart) 适合生产与弹性伸缩;pip 安装只适合二次开发。
# docker-compose 关键片段:Web 与 Worker 共用镜像与配置
x-superset-image: &img apache/superset:4.1.1
x-superset-env: &env
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY} # 必须固定,否则重启后会话失效
DATABASE_HOST: postgres
DATABASE_DB: superset
REDIS_HOST: redis
CELERY_BROKER_URL: redis://redis:6379/0
services:
superset:
image: *img
environment: *env
ports: ["8088:8088"]
command: ["/app/docker/entrypoints/run-server.sh"]
superset-worker:
image: *img
environment: *env
command: ["celery", "--app=superset.tasks.celery_app:app", "worker", "--pool=prefork", "-c", "4"]
superset-worker-beat:
image: *img
environment: *env
command: ["celery", "--app=superset.tasks.celery_app:app", "beat", "--pidfile", "/tmp/celerybeat.pid"]
redis: { image: redis:7-alpine }
postgres:
image: postgres:16-alpine
environment: { POSTGRES_DB: superset, POSTGRES_PASSWORD: superset }
三个必配项。SUPERSET_SECRET_KEY 必须固定(用 openssl rand -base64 42 生成),随机生成的密钥在重启后会导致所有会话失效、加密的连接密码无法解密。Worker 的 -c 并发数要按 CPU 核数与查询类型调整,IO 密集的查询可以设高,CPU 密集的设低。元数据库要单独备份,它丢了等于所有图表定义丢失。
3. 数据库连接与驱动配置
Superset 通过 SQLAlchemy 连接数据源,URI 格式是 dialect+driver://user:password@host:port/db。生产环境需要额外装驱动并在 superset_config.py 里注册。
# superset_config.py
from superset.db_engine_specs.clickhouse import ClickHouseEngineSpec # 4.x 已内置
# 允许的文件上传格式与大小
CSV_UPLOAD_EXTENSIONS = ["csv", "tsv", "xlsx"]
UPLOAD_FOLDER = "/app/superset_home/uploads/"
# 查询超时与行数上限,防止单条查询拖垮数据库
SQLLAB_TIMEOUT = 300 # SQL Lab 查询超时(秒)
SUPERSET_WEBSERVER_TIMEOUT = 300
SQL_MAX_ROW = 100000 # 单次查询最大返回行数
DISPLAY_MAX_ROW = 10000 # 前端展示上限
# 强制所有查询带 LIMIT,避免 SELECT * 拉爆内存
PREVENT_UNSAFE_DB_CONNECTIONS = True
# 隐藏敏感字段:禁止在 SQL Lab 里执行某些语句
SQLLAB_CTAS_NO_LIMIT = False
连接串示例:
# ClickHouse(推荐用原生驱动,比 HTTP 快)
clickhouse+http://default:pass@clickhouse:8123/analytics?protocol=https
# PostgreSQL
postgresql+psycopg2://readonly:pass@pg:5432/warehouse
# MySQL 8(注意时区参数,否则时间字段会偏移)
mysql+pymysql://ro:pass@mysql:3306/dw?charset=utf8mb4
连接账号必须是只读的。Superset 允许在 SQL Lab 里执行任意 SQL,若账号有写权限,一个误操作就能改生产数据。此外要设 SQL_MAX_ROW 与 SQLLAB_TIMEOUT,否则用户一个 SELECT * FROM huge_table 就能把数据库连接池占满。
4. 数据集与语义层建模
数据集(Dataset)是 Superset 的语义层核心。它把一个物理表或一段 SQL 包装成带元数据的逻辑表:字段的中文名、类型、是否可分组、是否可聚合、计算列、指标定义都挂在这里。
物理表 dw.fact_orders → 数据集「订单事实表」
列: order_id(订单ID, 不可分组) / region(区域) / channel(渠道)
order_date(下单日期) / gmv(成交额, 可聚合)
计算列: is_new_customer = CASE WHEN user_order_seq = 1 THEN 1 ELSE 0 END
指标: GMV = SUM(gmv)
客单价 = SUM(gmv) / COUNT(DISTINCT user_id)
新客GMV = SUM(CASE WHEN is_new_customer = 1 THEN gmv ELSE 0 END)
指标必须集中在数据集层定义,不能每个图表各写一遍。这是避免「同名指标不同算法」的唯一手段。指标定义可以写成 SQL 表达式,也可以引用其他指标(Superset 支持指标嵌套)。
数据集还支持虚拟数据集——直接写一段 SQL 作为数据源,适合需要多表关联的场景:
-- 虚拟数据集:把宽表逻辑固化在数据集里,图表层直接用
SELECT
o.order_date,
o.region,
o.channel,
o.gmv,
u.user_level,
CASE WHEN u.first_order_date = o.order_date THEN 1 ELSE 0 END AS is_new_customer
FROM dw.fact_orders o
JOIN dw.dim_user u ON o.user_id = u.user_id
WHERE o.order_date >= '2024-01-01'
虚拟数据集的代价是每次查询都要重新执行这段 SQL。如果底层表已经在数仓里物化成了宽表,优先直连宽表,虚拟数据集只用于无法物化的场景。
5. 图表类型与可视化插件
Superset 内置约 40 种图表类型,覆盖时间序列、分布、比例、关系、地理几大类。它们的渲染层分工明确:常规图表用 ECharts,地图用 deck.gl 或 Mapbox,表格用自研的 Grid。
图表选择上有几条 Superset 特有的经验:
时间序列图(Line/Area/Bar)依赖数据集里标记为 is_dttm 的时间列。没有时间列就无法使用时间粒度聚合与时间范围过滤器,这是最常见的配置遗漏。
表格(Table)支持条件格式、列聚合、行级汇总。大表建议用「Pivot Table」而非「Table」——Pivot 在服务端做聚合,传输量小得多。
Big Number 配合趋势迷你图(sparkline)是仪表盘 KPI 区的标准组件。它支持同比/环比对比,比手写数字卡片省事。
地理图需要数据集里有经纬度列或 GeoJSON 编码列。Superset 4.x 推荐用 deck.gl 的 Scatterplot 与 Polygon 图层,旧的 deck_scatter 已弃用。
自定义插件:Superset 支持用 superset-frontend 的插件脚手架开发自定义 Viz 类型,打包后通过 superset_config.py 的 DASHBOARD_CROSS_FILTERS 与 VIZ_TYPE 注册。开发成本不低(要写 React 组件 + 注册元数据),只在确实需要内置类型无法表达的图形时才做。
6. 仪表盘与原生的过滤器
仪表盘(Dashboard)是图表的容器,核心能力是原生过滤器(Native Filters)——在仪表盘层定义一组过滤条件,应用到多个图表。
# 原生过滤器的典型配置(在 UI 里配置,此处用 YAML 表达结构)
- { name: 时间范围, type: time_range, default: "过去 30 天", scope: 6 个图表 }
- name: 区域
type: value
dataset: 订单事实表
column: region
multiSelect: true
default: [华东, 华南]
- name: 渠道
type: value
dataset: 订单事实表
column: channel
multiSelect: true
cascadeParentIds: [区域] # 级联:渠道选项随区域变化
三条实践建议。其一,过滤器数量控制在 5 个以内,过多会让仪表盘首屏查询变慢(每个过滤器都要拉一次候选值)。其二,默认值要合理,把最常用的时间范围设为默认,避免用户每次都要手动选。其三,用级联(cascade)减少无效选项,区域筛选后渠道只显示该区域存在的渠道。
仪表盘的加载性能取决于图表数量与查询并发。一个 20 张图的仪表盘首屏会并发 20 个查询,如果底层数据库连接数不够,会出现部分图表超时。解决办法是开启缓存(见第 8 节)并限制单仪表盘的图表数量(建议 12 张以内)。
7. 行级安全与权限模型
Superset 的权限分两层:角色权限(谁能访问哪些数据集/图表/仪表盘)与行级安全 RLS(同一张表,不同用户看到不同的行)。
角色模型包含 Admin、Alpha、Gamma、Public 四个内置角色。Gamma 是最常用的业务角色——只能看被授权的图表,不能编辑数据集或执行 SQL Lab。
RLS 通过在数据集上挂「行级安全过滤器」实现,每条规则关联一个角色或一组用户,过滤条件会被拼进 SQL 的 WHERE 子句:
-- 规则 1:销售角色只能看自己区域的订单
-- 关联角色: Sales_Region
region IN (
SELECT region FROM dw.dim_user_region WHERE username = '{{ current_username() }}'
)
-- 规则 2:管理层看全部(不加过滤)
-- 关联角色: Management,clause 留空表示不过滤
-- 规则 3:按用户属性过滤,用 Jinja 取当前用户
tenant_id = {{ current_user_tenant_id() }}
关键机制有两点。其一,多条规则的组合逻辑:Superset 支持 Regular 模式(多条规则 AND 连接)与 Base 模式(作为基础过滤,其他规则在其上叠加)。默认是 Regular,多个角色匹配时取并集。其二,RLS 与缓存的冲突:带 RLS 的查询结果不能跨用户共享缓存,否则会泄漏数据。Superset 会为这类查询禁用共享缓存,代价是缓存命中率下降。
RLS 的过滤条件会被拼进 SQL,因此条件的写法直接影响查询性能——用子查询过滤会产生关联开销,用固定值列表(region IN ('华东','华南'))性能更好。规则数量多时建议在数仓侧建一张「用户-可见范围」映射表,RLS 只做一次 JOIN。
8. 查询缓存与异步执行
缓存是 Superset 性能的关键。默认用内存缓存(SimpleCache),生产必须换成 Redis。
# superset_config.py
from cachelib.redis import RedisCache
CACHE_CONFIG = {
"CACHE_TYPE": "RedisCache",
"CACHE_DEFAULT_TIMEOUT": 300, # 默认 5 分钟
"CACHE_KEY_PREFIX": "superset_",
"CACHE_REDIS_URL": "redis://redis:6379/1",
}
# 图表数据的独立缓存,超时更长
DATA_CACHE_CONFIG = {
**CACHE_CONFIG,
"CACHE_DEFAULT_TIMEOUT": 3600, # 1 小时
"CACHE_REDIS_URL": "redis://redis:6379/2",
}
# 缩略图与元数据缓存
THUMBNAIL_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_REDIS_URL": "redis://redis:6379/3"}
# 异步查询:超过阈值的查询丢给 Celery Worker
FEATURE_FLAGS = {
"GLOBAL_ASYNC_QUERIES": True,
"DASHBOARD_RBAC": True,
"ALERT_REPORTS": True,
}
GLOBAL_ASYNC_QUERIES_JWT_SECRET = "${ASYNC_JWT_SECRET}"
GLOBAL_ASYNC_QUERIES_REDIS_CONFIG = {"host": "redis", "port": 6379, "db": 5}
缓存粒度是「数据集 + 查询 SQL + 用户角色」。同一个图表,不同角色的用户因为 RLS 不同会走不同缓存。缓存失效有两个触发点:手动在数据集页面点「清除缓存」,或配置 CACHE_DEFAULT_TIMEOUT 自然过期。没有自动感知底层数据变化的能力——如果数据每小时更新,应把超时设为略小于一小时。
异步执行解决的是慢查询阻塞 Web 进程的问题。开启后,超过 GLOBAL_ASYNC_QUERIES_POLLING_DELAY 的查询会被转到 Worker,前端轮询结果。这需要 Redis 作为结果中转,且 Worker 数量要足够,否则慢查询会在队列里堆积。
# 缓存预热:对高频图表在数据更新后主动刷新
# celery beat 定时任务
beat_schedule = {
"cache-warmup-hourly": {
"task": "superset.tasks.cache.warm_up_cache",
"schedule": 3600.0,
"kwargs": {"chart_ids": [1, 2, 3], "db_id": 1},
},
}
9. 告警与报表订阅
ALERT_REPORTS 功能让 Superset 能按定时任务把图表截图或数据推送到邮件、Slack、Webhook。它由 Celery Beat 调度、Worker 执行。
# 告警配置示例(在 UI 里创建,此处表达结构)
name: "GMV 日环比告警"
type: alert
chart: "日 GMV 趋势"
condition: "> 0.2" # 变化幅度超过 20% 触发
schedule: "0 9 * * *" # 每天 9 点
recipients: ["data-team@example.com"]
# 支持 Slack / Webhook / 企业微信(需自定义通知渠道)
name: "周报订阅"
type: report
dashboard: "经营看板"
crontab: "0 8 * * 1" # 每周一 8 点
format: PNG # 或 CSV / PDF
三个落地要点。截图依赖无头浏览器:Superset 用 Playwright/Selenium 渲染仪表盘截图,部署时要装浏览器依赖并给足内存,否则会静默失败。告警条件是「相对于上一次值的变化」,不是绝对阈值——设 > 0.2 表示变化超过 20%,而不是值大于 0.2。通知渠道需要配置 superset_config.py 的 EMAIL_* 或 Slack token,企业微信/钉钉需要自己写 BaseNotification 子类。
10. SQL Lab 与性能诊断
SQL Lab 是给分析师写 SQL 的界面,它也是诊断性能问题的入口。三条诊断路径。
查询历史(Query History)记录每条查询的 SQL、耗时、执行用户、是否命中缓存。按耗时降序排,能直接定位最贵的查询。
查询计划:Superset 支持在 SQL Lab 里查看执行计划(PostgreSQL 的 EXPLAIN、ClickHouse 的 EXPLAIN),用于确认是否走了索引或分区裁剪。
-- 在 SQL Lab 里诊断:看查询是否命中分区裁剪
EXPLAIN SELECT region, SUM(gmv)
FROM dw.fact_orders
WHERE order_date >= '2026-01-01' AND order_date < '2026-02-01'
GROUP BY region;
-- ClickHouse 的查询日志表,用于定位慢查询
SELECT query_duration_ms, read_rows, query
FROM system.query_log
WHERE type = 'QueryFinish' AND query_duration_ms > 3000
ORDER BY event_time DESC LIMIT 20;
元数据同步:数据集的列信息是缓存的,表结构变了要手动或定时同步。Superset 的 sync_datasets 定时任务会重新拉取表结构,但不会自动删除已删列的指标,改表结构后要检查数据集定义。
11. 与 OLAP 数仓的集成要点
Superset 的定位是「查询前端」,因此它和数仓的分工必须明确:聚合、关联、去重、窗口计算全部下沉到数仓,Superset 只做最终的过滤与展示。
接 ClickHouse 时的几条经验。其一,用 clickhouse+http 或 clickhouse+native 驱动,不要走通用的 ODBC,性能与类型支持都差。其二,数据集尽量直连已经预聚合的物化视图,让 Superset 的查询只做简单的 GROUP BY。其三,注意 FINAL 与去重的代价——MergeTree 的 FINAL 查询在大表上很慢,应该在数仓侧用物化视图做好去重。
-- 数仓侧:预聚合物化视图,供 Superset 直连
CREATE MATERIALIZED VIEW dw.mv_daily_gmv
ENGINE = SummingMergeTree()
ORDER BY (region, channel, order_date)
AS SELECT
region, channel, toDate(order_date) AS order_date,
sum(gmv) AS gmv, count() AS order_cnt
FROM dw.fact_orders
GROUP BY region, channel, order_date;
Superset 直接查 dw.mv_daily_gmv,查询退化成一次带过滤的 SELECT,响应时间从秒级降到毫秒级。这与 可视化与 OLAP 数仓集成
中讨论的整体分工一致,ClickHouse 侧的物化视图与引擎选型可参考 ClickHouse 架构与 MergeTree
。
12. 生产运维与版本升级
四条运维实践。
元数据备份:定时 pg_dump 元数据库,并在升级前额外备份一次。Superset 升级会跑数据库迁移(superset db upgrade),迁移失败时只能靠备份回滚。
镜像固化:不要在容器启动时 pip install 额外驱动,应该构建自定义镜像把驱动装进去,否则启动时间不可控且每次重启都在拉包。
FROM apache/superset:4.1.1
USER root
RUN pip install --no-cache-dir clickhouse-connect==0.7.19 psycopg2-binary==2.9.9
USER superset
升级顺序:先升级元数据库 schema(superset db upgrade),再滚动重启 Web 与 Worker。Worker 与 Web 的版本必须一致,混跑会出现任务序列化不兼容。
监控指标:Web 的请求延迟与错误率、Celery 队列长度、缓存命中率、慢查询数量。队列长度持续增长说明 Worker 不够或某个查询异常耗时。缓存命中率低于 60% 说明超时设得太短或 RLS 规则过多导致缓存碎片化。
权衡取舍
| 决策点 | 选项 A | 选项 B | 何时选 A | 何时选 B |
|---|---|---|---|---|
| 元数据库 | PostgreSQL | SQLite | 生产环境 | 本地评估 |
| 部署 | Docker Compose | Kubernetes | 单机/小团队 | 弹性伸缩/多租户 |
| 数据源 | 直连物理表 | 虚拟数据集 | 表已物化 | 需多表关联 |
| 语义层 | 数据集内定义指标 | 图表内写表达式 | 需要口径统一 | 一次性探索 |
| 权限 | RLS 行级过滤 | 角色级可见性 | 同表不同行 | 完全隔离的报表 |
| 缓存 | Redis 共享缓存 | 禁用缓存 | 读多写少 | RLS 复杂/实时性高 |
| 慢查询 | 异步执行 | 优化底层 SQL | 查询确实慢 | 加索引/物化视图可解 |
常见坑清单
- 用 SQLite 当元数据库——Celery Beat 与 Web 并发写锁库报错;生产必须用 PostgreSQL。
- SECRET_KEY 随机生成——重启后会话失效、加密连接串无法解密;用固定密钥。
- 数据源账号有写权限——SQL Lab 可执行任意 SQL,误操作改生产数据;必须用只读账号。
- 不设 SQL_MAX_ROW——用户
SELECT *拉爆内存与连接池;强制行数上限。 - 指标散落在图表里——同名指标算法不同,口径混乱;集中到数据集层定义。
- RLS 用子查询过滤——每次查询多一次关联,慢且碎片化缓存;尽量用固定值或映射表 JOIN。
- 缓存超时长于数据更新周期——报表显示旧数据;超时设为略小于更新周期。
- 告警条件当绝对阈值——
> 0.2是变化幅度而非数值;按「相对上次值的变化」理解。 - 截图任务缺浏览器依赖——报表订阅静默失败;镜像里装 Playwright 并给足内存。
- 仪表盘图表过多——首屏并发 20 个查询导致部分超时;控制在 12 张以内并开缓存。
小结
Superset 的价值是把「查询、权限、缓存、调度」平台化,让分析师自助出图而不用排队等数据团队。它的架构决定了性能上限来自底层数据库,因此正确用法是把聚合与关联全部下沉到数仓,Superset 只做展示层的过滤与呈现。
工程落地上有四条硬规则:元数据库必须是 PostgreSQL、连接账号必须只读、指标必须集中在数据集层、RLS 必须考虑缓存碎片化。这四条一旦违反,会在生产环境以「莫名报错」「口径不一致」「越权可见」「缓存穿透」的形式暴露出来。
下一步可以对照 Metabase 轻量级 BI 实践 看更轻量的自助分析方案,或按底层引擎进入 可视化与 OLAP 数仓集成 与 ClickHouse 架构与 MergeTree ;平台内多图表组合的设计原则见 仪表盘与数据大屏设计 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。