Superset 自助式 BI 平台

以 Apache Superset 4.x 为基准讲清架构组件、部署与元数据库选型、数据集语义层、行级安全、缓存与异步查询、告警订阅,以及把 ClickHouse 等 OLAP 引擎接进来时的性能与治理实践。

引言

Superset 是 Apache 基金会的开源 BI 平台,定位在「分析师能自己拖拽出图表、不用等数据团队排期」这个场景。它的核心不是图表渲染(渲染用的是 ECharts 与 deck.gl),而是把查询、权限、缓存、调度这四件事平台化:谁能在哪个数据集上查什么、查询结果能不能复用、慢查询怎么异步化、指标异常怎么自动推送。

工程落地的主要难点有四类。元数据治理:数据集字段的中文名、计算列、指标定义散落在各处,没有统一的语义层,同一个「活跃用户」在不同图表里算法不同。权限粒度:行级安全(RLS)要求把「华南区销售只能看华南数据」这类规则落到 SQL 层,而不是靠应用层过滤。查询性能:Superset 自身不做计算,所有压力都转嫁给底层数据库,接一个未经优化的 MySQL 会直接拖垮生产库。缓存失效:数据更新后缓存必须及时失效,否则会出现「报表显示的是昨天的数」。

本文按「架构 → 部署 → 语义层 → 权限 → 性能 → 运维」的顺序展开,以 Superset 4.x 为基准,配置片段基于 superset_config.py 的真实结构。

目录

  1. Superset 的架构与组件
  2. 部署方式与元数据库选型
  3. 数据库连接与驱动配置
  4. 数据集与语义层建模
  5. 图表类型与可视化插件
  6. 仪表盘与原生的过滤器
  7. 行级安全与权限模型
  8. 查询缓存与异步执行
  9. 告警与报表订阅
  10. SQL Lab 与性能诊断
  11. 与 OLAP 数仓的集成要点
  12. 生产运维与版本升级

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
元数据库PostgreSQLSQLite生产环境本地评估
部署Docker ComposeKubernetes单机/小团队弹性伸缩/多租户
数据源直连物理表虚拟数据集表已物化需多表关联
语义层数据集内定义指标图表内写表达式需要口径统一一次性探索
权限RLS 行级过滤角色级可见性同表不同行完全隔离的报表
缓存Redis 共享缓存禁用缓存读多写少RLS 复杂/实时性高
慢查询异步执行优化底层 SQL查询确实慢加索引/物化视图可解

常见坑清单

  1. 用 SQLite 当元数据库——Celery Beat 与 Web 并发写锁库报错;生产必须用 PostgreSQL。
  2. SECRET_KEY 随机生成——重启后会话失效、加密连接串无法解密;用固定密钥。
  3. 数据源账号有写权限——SQL Lab 可执行任意 SQL,误操作改生产数据;必须用只读账号。
  4. 不设 SQL_MAX_ROW——用户 SELECT * 拉爆内存与连接池;强制行数上限。
  5. 指标散落在图表里——同名指标算法不同,口径混乱;集中到数据集层定义。
  6. RLS 用子查询过滤——每次查询多一次关联,慢且碎片化缓存;尽量用固定值或映射表 JOIN。
  7. 缓存超时长于数据更新周期——报表显示旧数据;超时设为略小于更新周期。
  8. 告警条件当绝对阈值——> 0.2 是变化幅度而非数值;按「相对上次值的变化」理解。
  9. 截图任务缺浏览器依赖——报表订阅静默失败;镜像里装 Playwright 并给足内存。
  10. 仪表盘图表过多——首屏并发 20 个查询导致部分超时;控制在 12 张以内并开缓存。

小结

Superset 的价值是把「查询、权限、缓存、调度」平台化,让分析师自助出图而不用排队等数据团队。它的架构决定了性能上限来自底层数据库,因此正确用法是把聚合与关联全部下沉到数仓,Superset 只做展示层的过滤与呈现。

工程落地上有四条硬规则:元数据库必须是 PostgreSQL、连接账号必须只读、指标必须集中在数据集层、RLS 必须考虑缓存碎片化。这四条一旦违反,会在生产环境以「莫名报错」「口径不一致」「越权可见」「缓存穿透」的形式暴露出来。

下一步可以对照 Metabase 轻量级 BI 实践 看更轻量的自助分析方案,或按底层引擎进入 可视化与 OLAP 数仓集成 与 ClickHouse 架构与 MergeTree ;平台内多图表组合的设计原则见 仪表盘与数据大屏设计 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「数据可视化」更多文章

  1. WebGL 与三维数据可视化
  2. 数据叙事与图表沟通
  3. 嵌入式分析与白标集成