ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Perspective Python ClickHouse Virtual Server 实战:让 `<perspective-viewer>` 通过 WebSocket 直查 ClickHouse

Perspective Python ClickHouse Virtual Server 实战:让 `<perspective-viewer>` 通过 WebSocket 直查 ClickHouse Perspective Python ClickHouse Virtual Server 实战让perspective-viewer通过 WebSocket 直查 ClickHouse【免费下载链接】perspectiveA data visualization and analytics component, especially well-suited for large and/or streaming datasets.项目地址: https://gitcode.com/GitHub_Trending/pe/perspectivePerspective 为 ClickHouse 提供了一套内置的虚拟服务器Virtual Server实现允许perspective-viewer客户端在不把全量数据载入 Perspective 内存引擎的前提下通过 WebSocket 直接对 ClickHouse 发起查询。本文以 ClickHouse Virtual Server 官方文档 为主线结合仓库中的 Python 实现源码与可运行示例讲解从安装、服务端搭建、浏览器连接到特性能力矩阵与底层查询生成原理的完整链路。读完本文你将能够独立搭建一个基于 Tornado clickhouse-connect的 Perspective 数据服务并理解 ClickHouse 虚拟服务器在聚合、过滤、窗口函数与类型映射上的具体能力边界。一、背景Virtual Server 如何让 Perspective 直查外部数据库在正式进入 ClickHouse 之前先理解 Perspective 虚拟服务器的定位。根据 Virtual Servers 解释文档虚拟服务器允许 Perspective 直接查询 DuckDB、ClickHouse 等外部数据源而无需将整个数据集载入 Perspective 内置数据引擎。其核心思想是Perspective 把自身的查询操作group by、sort、filter 等翻译成外部数据源能够原生执行的 SQL并且只传输当前视图所需的数据切片。┌──────────────────────────────────────────────────┐ │ perspective-viewer │ └──┬───────────────────────────────────────────────┘ │ ┌──────────────────────────────────────────────────┐ └──►│ Perspective Virtual Server Handler │ └──┬───────────────────────────────────────────────┘ │ ┌──────────────────────────────────────────────────┐ └──►│ External DB (DuckDB, ClickHouse, …). │ └──────────────────────────────────────────────────┘该架构适用于以下场景数据集过大无法放入浏览器内存或单个进程数据已经存在于数据库中希望避免重复拷贝希望利用数据库原生的查询优化能力希望从浏览器端或 Python 服务端直查位于远端或本地的 ClickHouse 实例。虚拟服务器通过实现一个处理器接口来工作处理器把 Perspective 的视图配置翻译为外部系统的查询语言通常是 SQL执行查询再以列式数据返回结果。由于处理器遵循标准的 Perspective Client 协议它可以运行在进程内、Web Worker 或远程服务器上。二、安装依赖ClickHouse 虚拟服务器的 Python 端需要两个包perspective-python提供虚拟服务器与 Tornado 处理器clickhouse-connect是官方推荐的 ClickHouse 客户端驱动。pip install perspective-python clickhouse-connect若还需要 WebSocket 服务端需额外安装tornadoperspective-python的 handlers 模块依赖它。三、快速开始搭建 ClickHouse 虚拟服务器3.1 服务端连接 ClickHouse 并挂载到 WebSocket以下代码完整来自 官方文档创建一个把 ClickHouse 表暴露给浏览器客户端的 WebSocket 服务import clickhouse_connect import tornado.web import tornado.ioloop from perspective.virtual_servers.clickhouse import ClickhouseVirtualServer from perspective.handlers.tornado import PerspectiveTornadoHandler # 连接 ClickHouse client clickhouse_connect.get_client(hostlocalhost) # 创建由 ClickHouse 支撑的虚拟服务器 server ClickhouseVirtualServer(client) # 通过 WebSocket 对外服务 app tornado.web.Application([ (r/websocket, PerspectiveTornadoHandler, {perspective_server: server}), ]) app.listen(8080) tornado.ioloop.IOLoop.current().start()关键点说明clickhouse_connect.get_client(hostlocalhost)默认连接本机 8123 端口的 ClickHouse可继续传入port、username、password、database等参数ClickhouseVirtualServer(client)接受一个clickhouse_connect客户端实例用于后续所有 SQL 查询的执行PerspectiveTornadoHandler通过{perspective_server: server}关键字参数绑定虚拟服务器路由/websocket即浏览器端连接的端点。3.2 浏览器端连接 WebSocket 并加载表服务端启动后前端使用 Perspective 的 WebSocket 客户端连接const websocket await perspective.websocket(ws://localhost:8080/websocket); const table await websocket.open_table(my_table); document.getElementById(viewer).load(table);其中open_table(my_table)的my_table必须是 ClickHouse 中实际存在的表名——虚拟服务器会通过SHOW TABLES校验并列出可用的托管表。四、可运行的完整示例加载数据到 ClickHouse 并搭建服务官方文档附带的示例位于 examples/python-clickhouse-virtual/server.py它演示了一条完整链路把本地 Parquet 文件导入 ClickHouse → 创建虚拟服务器 → 启动 Tornado 同时提供 WebSocket 与静态页面。这个示例比快速开始更贴近真实部署值得逐段解读。4.1 将 Arrow/Parquet 数据写入 ClickHouse示例从node_modules/superstore-arrow中读取superstore.parquet依据 Arrow schema 推断 ClickHouse 列类型并建表、灌数def arrow_type_to_clickhouse(arrow_type): t str(arrow_type) if t.startswith(int) or t.startswith(uint): return Int64 if t in (float, double, halffloat): return Float64 if t.startswith(timestamp): return DateTime if t.startswith(date): return Date return String随后创建MergeTree表并插入数据client.command( fCREATE TABLE data_source_one ({, .join(cols)}) ENGINE MergeTree() ORDER BY tuple() ) client.insert_arrow(data_source_one, arrow_table)这里把可空列声明为Nullable(Int64)等类型与后文提到的类型映射逻辑Nullable(...)会被剥壳直接对应。4.2 挂载 WebSocket 与静态资源示例的 Tornado 应用同时注册了三个路由WebSocket 处理器、node_modules静态目录、以及站点根目录app tornado.web.Application( [ (r/websocket, PerspectiveTornadoHandler, {perspective_server: virtual_server}), (r/node_modules/(.*), StaticFileHandler, {path: ../../node_modules/}), (r/(.*), StaticFileHandler, {path: ./, default_filename: index.html}), ], websocket_max_message_size100 * 1024 * 1024, ) app.listen(3000)其中websocket_max_message_size100 * 1024 * 1024将 WebSocket 单条消息上限放宽到 100MB避免大结果集如大数据量的view_get_data响应被 Tornado 默认 1MB 限制拦截——这是直查大数据源时容易被忽视的关键配置。五、能力矩阵ClickHouse 虚拟服务器支持哪些操作ClickhouseVirtualServerHandler.get_features()见 rust/perspective-python/perspective/virtual_servers/clickhouse.py向 UI 声明该实现支持的能力perspective-viewer会据此启用或禁用对应控件特性字段值说明group_byTrue支持分组聚合split_byFalse不支持 split-by二次透视UI 会隐藏该控件sortTrue支持排序expressionsTrue支持表达式列group_rollup_mode[rollup, flat, total]分组下钻/扁平/总计三种模式unorderedTrueClickHouse 没有稳定的rowid不支持自然序窗口filter_ops见下表各类型支持的过滤操作符aggregates见下表各类型支持的聚合函数window_aggregates见下表各类型支持的窗口函数5.1 过滤操作符FILTER_OPS所有类型integer、float、string、boolean、date、datetime统一支持 ! LIKE IS DISTINCT FROM IS NOT DISTINCT FROM 注意Python 端未启用is null/is not null且字符串类型没有单独的扩展操作符集合而浏览器端的 ClickhouseHandler 则额外拼接了begins with、contains、ends with、matches、in、ILIKE、NOT ILIKE等字符串专有操作符这说明 Python 与 JS 两个实现的能力并不完全对齐。5.2 聚合函数数值类型integer/float支持NUMBER_AGGS中的 23 个聚合sum count any_value arbitrary array_agg avg bit_and bit_or bit_xor bitstring_agg bool_and bool_or countif favg fsum geomean kahan_sum last max min product string_agg sumkahan字符串/布尔/日期/时间类型只支持STRING_AGGScount any_value arbitrary first countif last string_agg5.3 窗口函数与帧类型窗口聚合定义于WINDOW_AGGREGATES帧类型为FRAMES [rows, range, cumulative]数值类型sum、avg、count、min、max、stddev_samp、var_samp结果为float以及带 offset 的lag、lead、diff和仅支持range帧的rate字符串/布尔/日期/时间类型WINDOW_AGGREGATES_ANY仅count、min、max、lag、lead。源码注释同时明确指出clickhouse.py 的窗口函数段这组窗口函数是从 DuckDB 处理器继承而来尚未针对真实 ClickHouse 全面审计——ClickHouse 原生的导航函数是lagInFrame/leadInFrame排名函数集合也与 DuckDB 不同。因此在使用窗口聚合前建议先用目标 ClickHouse 版本验证这些函数的实际可用性再在 UI 中开放对应能力。六、源码级原理查询如何被翻译并执行6.1 虚拟服务器接口与请求流程Python 端处理器继承自VirtualServerHandler接口定义见 rust/perspective-python/perspective/virtual_servers/init.py其工作流是按表名选择表通过get_hosted_tables()校验ClickHouse 实现执行SHOW TABLES并返回表名列表UI 请求模型用某个查询config创建一张临时表——table_make_view()调用 SQL 构建器生成CREATE VIEW语句执行UI 按需查询临时表的切片矩形视口、整列或全量——view_get_data()返回列式数据UI 通过view_delete()删除临时表即使偶发失败UI 也会自动恢复。会话层由ClickhouseVirtualSession包装每个会话内部持有perspective.VirtualServer(ClickhouseVirtualServerHandler(db))把客户端的请求消息透传给处理器再通过回调返回结果。6.2 SQL 构建GenericSQLVirtualServerModel处理器在构造时初始化 SQL 构建器clickhouse.pyself.sql_builder perspective.GenericSQLVirtualServerModel( {create_entity: VIEW, grouping_fn: GROUPING} )该模型的 Rust 实现位于 rust/perspective-js/src/rust/generic_sql_model.rs负责把 Perspective 的ViewConfig翻译成 SQLtable_make_viewgeneric_sql_model.rs 第 109 行生成带GROUP BY、ORDER BY、过滤、窗口子句的CREATE VIEW语句view_get_datageneric_sql_model.rs 第 132 行生成带LIMIT/OFFSET视口切片的查询浏览器端 JS 版本在构造时还额外配置了column_separator: |、backslash_escaped_literals: true、regex_fn: match用于拼接复合列名与正则过滤语法。以 ClickHouse 为例table_make_view中如果配置了窗口config.get(windows)处理器会先取表 schema 以便为range帧生成正确的列类型clickhouse.py 第 197-201 行。6.3 类型映射ClickHouse 类型 → Perspective 列类型clickhouse_type_to_psp()clickhouse.py 第 239-260 行把 ClickHouse 的数据类型收敛为 Perspective 的六类视觉相关类型ClickHouse 类型Perspective 列类型Nullable(...)剥壳后继续映射取决于内部类型Array(...)stringInt64/UInt64/Float64floatStringstringDateTimedatetimeDatedate其他抛出ValueError: Unknown type ...table_schema()在构建 schema 时跳过以__开头的列虚拟服务器生成的内部辅助列view_column_size()通过查询system.columns计算列数并扣除分组列与分组辅助列保证 UI 中显示的列数正确。6.4 查询执行与列式回填所有 SQL 都经由run_query()clickhouse.py 第 263-286 行执行写操作走db.command(query)读操作走db.query(query)取result_rows出错时记录错误与 SQL 并抛出成功时以datetime.now()计时输出 debug 日志。view_get_data()拿到结果后通过data.set_col(dtype, col, ridx, value, grouping_id)把行式结果按列回填进PerspectiveColumn推送式序列化 API其中非字符串值在目标类型为string时统一转成字符串。七、与浏览器端 ClickhouseHandler 的差异Python 端虚拟服务器面向「Python 进程持有clickhouse-connect客户端、通过 Tornado WebSocket 对外服务」的部署形态仓库同时提供了纯浏览器端的 ClickhouseHandler对应 JavaScript ClickHouse 指南使用clickhouse/client-web的createClient直连 ClickHouse 的 HTTP 接口默认http://localhost:8123配合perspective.createMessageHandler(handler)与perspective.worker(messageHandler)在 Worker 中运行。两者差异包括字符串过滤JS 端支持ILIKE、contains、matches、in等扩展操作符与is null/is not nullPython 端未开启Decimal 处理JS 端在viewGetData中把Decimal[...]类型按 scale 换算为 JS 数字convertDecimalToNumberPython 端未做专门处理并发控制JS 端用全局Lock串行化所有查询clickhouse/client-web会话限制Python 端直接依赖驱动自身能力SQL 构建器配置JS 端额外设置column_separator、backslash_escaped_literals、regex_fn。八、运行与验证启动完整示例前先确保本机有可用的 ClickHouse 服务cd examples/python-clickhouse-virtual npm install # 拉取 superstore-arrow 数据与前端依赖 python server.py # 需要 clickhouse-connect、perspective-python、tornado、pyarrow脚本启动时会自动完成「删旧表 → 建data_source_one→ 灌入 superstore 数据」的初始化然后监听http://localhost:3000浏览器打开后即可在perspective-viewer中看到 ClickHouse 表数据且所有聚合、过滤、排序均由 ClickHouse 原生执行只把当前视图所需的数据切片传回前端。若使用自己编写的服务可通过 WebSocket 端点验证连接是否建立成功。九、已知限制与注意事项不支持自然序窗口ClickHouse 没有稳定的rowidget_features()中unordered: True因此依赖行序的窗口操作不可用clickhouse.py 第 155-157 行窗口函数未全面审计WINDOW_AGGREGATES继承自 DuckDB 实现ClickHouse 原生函数名为lagInFrame/leadInFrame排名函数集合亦不同使用前需自行验证源码注释类型映射有限clickhouse_type_to_psp只识别Int64、UInt64、Float64、String、DateTime、Date与Array/Nullable包装其他 ClickHouse 类型如Decimal、UUID、Enum等会直接抛错split_by 不支持UI 中不会出现 split-by次级透视控件消息大小上限大结果集场景需调大 Tornado 的websocket_max_message_size示例中使用 100MB。如需为其他数据源实现类似能力可参考 Python 自定义虚拟服务器指南 与VirtualServerHandler接口定义复用本文所述的会话、SQL 构建与列式回填的完整模式。【免费下载链接】perspectiveA data visualization and analytics component, especially well-suited for large and/or streaming datasets.项目地址: https://gitcode.com/GitHub_Trending/pe/perspective创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进