生态实践

把跨模查询搬进浏览器:KWDB Playground 零安装·查询即出图·可断点续跑

原创尚雷2026-10-02
21

1 概述

本章给出主要结论、问题界定与文档组织。

1.1 主要结论

结论一:Playground 缩短的是「复现鸿沟」,不是「数据库能力」。

它把一篇需要读者自行装 Docker、拉镜像、起服务的教程,压缩为一条安装命令加一次浏览器访问。以本文的跨模复现为例,读者从零到看到第一条跨模查询结果,所需的自有步骤从「装 Docker → 拉 KWDB 镜像 → 启动服务 → 建库建表 → 导入数据 → 连接查询」收敛为「装 Playground → 打开课程 → 在课程终端内执行查询脚本」。多的那一步(Playground 自身仍依赖 Docker)是它的前提,后面的步骤被它代办了。

结论二:同一个容器内「SQL 终端 + Code 终端」同页联动,是把查询结果转成一幅图的最短路径。

跨模查询返回的是「多个分组 × 多个聚合列」的表格,逐行读表很难看出趋势。在 Code 终端里用 Python 读取同一批结果并绘图,只需一次取数、一次渲染;因为 SQL 与 Code 跑在同一隔离环境内,不存在跨机器拷贝结果这一环。这一点的价值是可读性,而非性能。

结论三:隔离与多会话是「可复现」的两个工程属性,其收益集中在教学与分享场景。

隔离保证环境用完即弃、互不污染;多会话保证实验可暂停、可恢复,读者不必一次跑完。两者都不提升单条查询的速度,但显著降低「读者中途放弃」的概率。相应地,Playground 的适用边界是教学、演示与复测验证,而非生产部署或压测。

三条结论的口径并不相同:结论一讲可达性,结论二讲可读性,结论三讲适用边界。三者的证据来源与适用条件都不同,不应被合并为「Playground 更好」这类笼统判断。

1.2 问题界定

一份技术文档能否被复现,取决于读者需要自行补齐多少前置条件。前置条件越多,中途出错的概率越高,文章的可信度就越依赖作者的「权威」而非「可验证」。

Playground 的定位正是把这段前置条件收进平台。它不改变 KWDB 的任何查询语义,也不改变跨模查询的代价结构;它改变的是「谁来准备环境」。因此本文讨论的对象是实验环境的可达性,不是数据库内核的优劣。

这类复现失败的根因通常不在数据库本身,而在前置条件的准备顺序与版本口径:报错指向查询,原因却落在环境。把原因简单归给「读者不会用」,既不利于知识传播,也不利于工具改进;把差异显性化、把环境标准化,才是更可维护的做法。

1.3 文档组织与复现路径

第 3 章介绍 Playground 的架构与安装;第 4 章在 Playground 内复现官方跨模示例;第 5 章做三终端联动出图;第 6 章做工程复盘;第 7 章给出复测取证单。各章对应的脚本以两位编号落在 reproduce/ 目录。

图01-KWDB-Playground-架构与复现路径.png

阶段 名称 产物 章节
00 安装 Playground kwdb-playground 可执行文件 3.2
01 启动并进入跨模课程 隔离容器与已连接终端 4.1
02 建库建表与导入数据 db_monitor、monitor_r 两库及其表 4.2
03 执行跨模查询 四组查询回显 4.3
04 Code 终端出图 一张跨模聚合图 5.2
05 暂停 / 恢复校验 容器状态与数据留存 6.2

2 为什么技术分享需要「浏览器内可复现」的实验环境

本章讨论 Playground 这类工具要解决的工程问题。理解问题,才能判断它值不值得引入。

2.1 复现鸿沟:从「写得清」到「跑得通」的三道坎

一篇文章让读者「看懂」和让读者「跑通」,中间隔着三道坎:

  1. 环境坎:装什么版本、开什么端口、内存够不够。文档往往一句「请自行安装数据库」带过,读者在这一步的分歧最大。

  2. 数据坎:文章里的查询结果建立在某份数据之上,而读者手里没有这份数据。没有数据,SQL 写得再对也复现不出同一个结果。

  3. 连接坎:客户端与数据库协议、认证方式、库名与端口不一致,报错信息却指向查询本身,把读者引向错误方向。

Playground 对这三道坎的处理是:用容器代答环境坎;用「课程」把数据与查询打包,代答数据坎;用预置的终端会话代答连接坎。读者要做的,退化为「照抄命令、看结果」。

2.2 隔离、即弃、多会话:三个工程属性的价值

这三个属性经常被并列提及,但解决的问题不同,需要分开评估。

隔离:每个访客拿到独立环境。它解决的是「多人共用一台机器时互相踩」的问题——A 读者改了配置,不应影响 B 读者。

即弃:会话结束容器回收。它解决的是「实验残留」的问题——跑完不留脏数据,下次仍是干净的起点。这一点对可复现尤其关键:干净起点意味着「同样输入得到同样输出」的前提成立。

多会话:环境可暂停、可恢复。它解决的是「一次跑不完」的问题——建表导入这类耗时步骤只需做一次,之后可反复进入继续验证。

三者的共同点是:都不改变查询速度,只改变「读者跑通的难度」。三者之间也并非简单叠加:隔离与即弃解决的是起点问题,多会话解决的是过程问题。起点不干净,结论无法归因;过程不可中断,复现容易半途而废。分清这一点,才能判断在具体场景里该优先补哪一项,而不是遇到「跑不通」就把原因归给工具。

2.3 适用边界

需要如实说明 Playground 不适合做什么,否则会误导读者:

它不是生产部署工具。生产环境的持久化、高可用、备份恢复不在其设计目标内。

它不是压测平台。容器资源受限,得出的时延数字不能代表生产服务器的性能。

它仍需 Docker 作为前置。所谓「零安装」指的是读者不必手动装数据库,而不是连 Docker 都不用装。

把批评定位在「量程边界」而非「缺陷」,是这个工具定位本身决定的:它解决的是可达性,不是性能。

2.4 三个属性为什么共同指向「可复现」

复现失败的原因往往不是某一个错误,而是一类隐性的差异。读者以为自己的环境与作者一致,实际在版本、数据、配置上已经偏离。隔离的价值在于把「环境一致」从读者的责任转为平台的责任。这是根因层面的处理:它消除的不是某个具体故障,而是产生故障的那类不确定性。

即弃与多会话分别对应复现过程的两端。即弃保证起点干净:起点若被上一次实验污染,读者得到的差异就无法归因,究竟是数据不同还是语句不同,无从判断。多会话保证过程可中断:长实验一旦中断就要重来,重来又会引入新的差异,这是读者中途放弃的常见原因。

需要如实说明的是三者的代价。环境交给平台后,读者对底层参数的直接掌控变弱,排查问题时能动的旋钮变少。这个取舍是否值得,取决于目标:若目标是让不确定的读者稳定得到同一结论,代价可以接受;若目标是精细调优或压测,则应当改用本地容器。这条边界与第 6 章的结论一致——Playground 管可达性,不管极限性能。

3 KWDB Playground 是什么、怎么装

3.1 架构与三类终端

Playground 由三部分组成:浏览器前端、本机(或服务器)上的代理层、以及每个访客独享的 Docker 容器。代理层负责分配容器、转发终端输入输出;容器内运行 KWDB 实例与终端会话。

课程详情页按课程类型提供三类交互区域。经实测(v1.4.3),终端类型由课程元数据决定,并非每门课程都三件套齐全:

终端类型 用途 本文用到的能力 实测对应课程
Shell 命令行交互,起服务、查状态 启动 KWDB、建库导入(第 4 章) multi-mode(跨模查询)
SQL 执行 SQL 并查看结果 SQL 终端直连查询 sql(SQL 终端快速体验 KWDB)、data-query(数据查询)
Code 编写并运行 Python/Bash/Java 取数并绘图(第 5 章) python-kwdb(Python 连接 KWDB)、java-kwdb(Java 连接 KWDB)

图02-三终端联动流程.png

3.2 安装与启动

环境要求:本机已安装并运行 Docker(Docker Desktop 或 Docker Engine)。

# 一键安装(默认最新版本)
curl -fsSL https://kwdb.tech/playground.sh | bash
# 启动服务
kwdb-playground start

启动后,用浏览器访问 http://localhost:3006,进入课程列表。

提示:安装脚本支持 --version v1.4.3 指定版本、--source atomgit 指定下载源。网络访问 GitHub 不稳时,指定 AtomGit 源通常更可靠。

实测输出(阶段 00,实验机 kb-lab-01):

$ curl -fsSL https://kwdb.tech/playground.sh | bash
Starting KWDB Playground installation...
Attempting to fetch installation script from GitHub...
Resolving release version...
Latest release: v1.4.3 (from github)
Downloading https://github.com/KWDB/playground/releases/download/v1.4.3/kwdb-playground-linux-amd64...
(下载进度条 0%→100% 实时刷新,此处省略;二进制约 31 MB)
✅ KWDB Playground was successfully installed to /usr/local/bin/kwdb-playground!
To get started, simply run:
  kwdb-playground start
INSTALL_RC=0
$ ls -l /usr/local/bin/kwdb-playground
-rwxr-xr-x 1 root root 31760568 Oct 2 08:49 /usr/local/bin/kwdb-playground
$ kwdb-playground version
版本: 1.4.3 构建时间: 2026-09-03T03:25:45Z 提交: a48af60

回显显示:脚本自动解析出最新版本 v1.4.3 并从 GitHub Releases 下载二进制,安装至 /usr/local/bin/kwdb-playground(约 30 MB),退出码 0。版本号是判据的核心——后文所有"实测 v1.4.3"的表述均以此为准。

证据图01-Playground-安装完成.png

3.3 课程与环境隔离机制

进入课程后,Playground 为当前访客启动一个隔离容器,并把课程定义的初始数据与脚本注入其中。镜像来自多源(Docker Hub、ghcr.io、阿里云),启动前可做连通性测试并优选最快通道。

注意:请在启动容器前确认 Docker 守护进程处于运行状态,否则课程页会停留在「环境准备中」。

实测输出(阶段 01,实验机 kb-lab-01):

$ kwdb-playground start --no-open
守护进程启动成功,PID=32461,日志=logs/daemon.log,PID文件=tmp/kwdb-playground.pid
$ ss -lntp | grep 3006
LISTEN 0 4096 [::]:3006 [::]:* users:(("kwdb-playground",pid=32461,fd=8))
$ curl -sS -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3006/
HTTP 200

守护进程监听 3006,首页返回 HTTP 200,课程列表共 12 门。

镜像源可达性(实测,决定课程容器能否创建):

$ curl -o /dev/null -w 'HTTP %{http_code} (%{time_total}s)\n'
https://crpi-...aliyuncs.com/v2/
HTTP 401 (0.232s) # 阿里云 ACR:可达(401 为未鉴权的正常响应)
$ curl -o /dev/null -w 'HTTP %{http_code} (%{time_total}s)\n' https://ghcr.io/v2/
HTTP 401 (0.705s) # ghcr.io:可达
$ curl -o /dev/null -w 'HTTP %{http_code} (%{time_total}s)\n' https://registry-1.docker.io/v2/
curl: (7) Failed connect to registry-1.docker.io:443 HTTP 000 (8.006s)
                                  # Docker Hub:不可达

这是一处实测发现的必踩坑:实验机可正常访问 kwdb.tech,但 Docker Hub(registry-1.docker.io)超时。若按默认源启动课程,容器创建会失败。解决方式是在启动课程时指定可达镜像源(本文用阿里云 ACR):

curl -X POST http://127.0.0.1:3006/api/courses/multi-mode/start \
  -H 'Content-Type: application/json' \
  -d '{"image":"crpi-9ix5pwqh4t79xf4r.cn-shanghai.personal.cr.aliyuncs.com/kaiwudb/kwdb:latest"}'

启动成功后 docker ps 可见隔离容器:

kwdb-playground-multi-mode-1790890873878351895 | .../kaiwudb/kwdb:latest |
Up 6 seconds

证据图02-服务启动与课程列表.png

复测证据图 2 证明服务已启动、课程可进入、环境检查通过。(页面右上角显示「环境 3/3」检查项全绿)

4 在 Playground 里复现跨模查询

本章的 SQL 全部取自 KWDB 官方 SampleDB 的 multi-mode 示例,未做改写,以保证语法与语义与官方一致。

4.1 准备:进入课程容器终端

在课程列表中进入「跨模查询」(multi-mode)课程,点击「启动容器」,等待容器就绪后,右侧出现已连接的 Shell 终端(提示符形如 root@<容器ID>:/kaiwudb/bin#)。此时终端已连到一个可用的KWDB 实例。

注意(课程与终端类型的对应关系,实测 v1.4.3):课程元数据中「跨模查询」(multi-mode)为 Shell 终端课程(sqlTerminal=false、codeTerminal=false),本文的跨模 SQL 即在其 Shell 终端以 ./kwbase sql ... -e "..." 方式执行。具备 SQL 终端的是「SQL 终端快速体验 KWDB」(sql)与「数据查询」(data-query);具备 Code 终端的是「Python 连接 KWDB」(python-kwdb)与「Java 连接 KWDB」(java-kwdb)。若需 SQL/Code 终端,请选择上述对应课程。

场景说明:官方示例建模的是城市管网 IoT 场景。静态属性(业务实体、作业区域、场点、管线、测点)体量小,放关系引擎;实时测点数据体量大,放时序引擎。跨模查询的价值在于让两类数据在一条 SQL 内联合分析。

4.2 建库建表与导入数据

第一步建时序库与关系库。以下为官方示例的建表 SQL(保留原样):

-- 时序表:测点采集数据
CREATE TABLE db_monitor.t_monitor_point (
  k_collect_time timestamp NOT NULL,
  monitor_value double
) ATTRIBUTES (
    point_id varchar(64) NOT NULL,
    branch_id varchar(32),
    region_id varchar(16),
    site_id varchar(16),
    pipeline_id varchar(16) not null,
    monitor_type smallint,
    monitor_position varchar(64)
)
  PRIMARY TAGS(point_id)
  RETENTIONS 3h;
  -- 注:官方早期材料写作 ACTIVETIME 3h;KWDB 3.2.2 已改用 RETENTIONS。
  -- 照抄 ACTIVETIME 会报 `at or near "activetime": syntax error (42601)`。
  -- 逐变体实测见《复验报告》5.1 节。
-- 关系表:场点静态信息
CREATE TABLE monitor_r.site_info (
  site_id varchar(16) PRIMARY KEY,
  site_name varchar(80),
  region_id varchar(16),
  branch_id varchar(32),
  site_address varchar(64),
  site_desc varchar(128)
);
-- 关系表:作业区域信息
CREATE TABLE monitor_r.region_info (
  region_id varchar(16) PRIMARY KEY,
  region_name varchar(80),
  region_address varchar(64),
  region_desc varchar(128)
);
-- 关系表:管线信息
CREATE TABLE monitor_r.pipeline_info (
  pipeline_id varchar(16) PRIMARY KEY,
  pipeline_name varchar(60),
  pipe_start_point varchar(80),
  pipe_end_point varchar(80),
  pipe_spec varchar(30)
);
-- 关系表:测点静态信息
CREATE TABLE monitor_r.point_base_info (
  point_id varchar(64) PRIMARY KEY,
  signal_id varchar(120),
  signal_desc varchar(200),
  signal_type varchar(50),
  site_id varchar(16),
  pipeline_id varchar(16)
);

导入数据(官方示例以 CSV 方式生成并导入;脚本包在容器模式下会先 docker cp 再 docker exec 执行):

import into db_monitor.t_monitor_point CSV DATA
("nodelocal://1/t_monitor_point");
import into monitor_r.site_info CSV DATA
("nodelocal://1/site_info/site_info.csv");
import into monitor_r.region_info CSV DATA
("nodelocal://1/region_info/region_info.csv");
import into monitor_r.pipeline_info CSV DATA
("nodelocal://1/pipeline_info/pipeline_info.csv");
import into monitor_r.point_base_info CSV DATA
("nodelocal://1/point_base_info/point_base_info.csv");

注意:导入前请确认数据目录已就位。重复导入会叠加行数,导致后续聚合结果虚高。脚本包采用检测优先策略——目标库已有数据时只校验行数、不做 DROP;仅在显式设置 KWDB_FORCE_RELOAD=1 时才重建,以免误删存量数据。

实测输出(阶段 02,Playground 课程容器内):

root@16c43184e37d:/kaiwudb/bin# bash generate_data.sh
(生成 5 张关系表 + 1 张时序表 CSV 至 kwbase-data/extern/)
root@16c43184e37d:/kaiwudb/bin# ./kwbase start-single-node --insecure \
    --listen-addr=0.0.0.0:26257 --http-addr=0.0.0.0:8080 --store=./kwbase-data --background
root@16c43184e37d:/kaiwudb/bin# ./kwbase sql --insecure --host=127.0.0.1 -e
'SELECT version();'
KaiwuDB 3.2.2 (x86_64-pc-linux-gnu, built 2026/08/05 01:31:25, go1.21.13, gcc
7.4.0)
root@16c43184e37d:/kaiwudb/bin# ./kwbase sql --insecure --host=127.0.0.1 <
create_load.sql
(建库建表 + import into ... CSV DATA;job status = succeeded)

导入完成后核验行数:

$ ./kwbase sql --insecure --host=127.0.0.1 -e 'SELECT count(*) AS points FROM db_monitor.t_monitor_point;'
points
10000

课程容器的样本规模:时序表 10,000 行,关系表 point_base_info 1,500 行、site_info 436 行、region_info 41 行、pipeline_info 26 行、operation_branch 8 行——与官方课程说明的「8 实体 / 41 区域 / 436 场点 / 26 管线 / 1500 测点」口径一致。

对照:另一台裸 KWDB 实例(kb-lab-01,KaiwuDB CCL 3.2.2)——同一套建表语句、不同数据规模,用于后文双向印证:

$ kwbase sql --certs-dir=/etc/kaiwudb/certs \
    --url="postgresql://root@kb-lab-01:26257/defaultdb?sslmode=verify-full" <
02_verify.sql
database_name engine_type
db_monitor TIME SERIES
monitor_r RELATIONAL
tbl rows
db_monitor.t_monitor_point 2000000
monitor_r.site_info 436
monitor_r.region_info 41
monitor_r.pipeline_info 26
monitor_r.point_base_info 1500

两库分属时序引擎与关系引擎,五张表全部就位;裸实例的时序表承载 200 万行测点数据(关系表规模与课程容器相同)。完整回显见 evidence/02_verify.out。

提示:02_load.sh 采用"检测优先"策略——若目标实例已有数据,只校验行数、不做 DROP;仅当显式设置 KWDB_FORCE_RELOAD=1 时才重建。因此脚本可安全地对存量实例执行,不会误删数据。

4.3 四组跨模查询

以下四组 SQL 均来自官方 multi-mode/query.sql,覆盖从简单聚合到多表时间窗聚合的不同形态。

场景一:查询每个站点中采集类型为 1、采集数值高于 50、且采集条目多于 3 条的数据均值与条目数。

SELECT si.site_name,
       COUNT(t.monitor_value),
       AVG(t.monitor_value)
FROM monitor_r.site_info si,
     monitor_r.region_info wi,
     db_monitor.t_monitor_point t
WHERE wi.region_id = si.region_id
  AND si.site_id = t.site_id
  AND t.monitor_type = 1
  AND t.monitor_value > 50
GROUP BY si.site_name
HAVING COUNT(t.monitor_value) > 3
ORDER BY si.site_name;

场景二:按 10s 时间窗,查询三个区域内每个站点、某条管线在指定时间之后的各采集类型均值、最大最小值与条目数(关系表 4 张 + 时序表 1 张,共 5 表联结)。

SELECT wi.region_name,
       si.site_name,
       t.monitor_type,
       time_bucket(t.k_collect_time, '10s') AS timebucket,
       AVG(t.monitor_value) AS avg_value,
       MAX(t.monitor_value) AS max_value,
       MIN(t.monitor_value) AS min_value,
       COUNT(t.monitor_value) AS number_of_value
FROM monitor_r.site_info si,
     monitor_r.region_info wi,
     monitor_r.pipeline_info li,
     monitor_r.point_base_info pi,
     db_monitor.t_monitor_point t
WHERE li.pipeline_id = pi.pipeline_id
  AND pi.site_id = si.site_id
  AND si.region_id = wi.region_id
  AND t.point_id = pi.point_id
  AND li.pipeline_name = 'Pipe_3'
  AND wi.region_name in ('Area_8', 'Area_12', 'Area_16')
  AND t.k_collect_time >= '2023-08-01 01:00:00'
GROUP BY wi.region_name,
         si.site_name,
         t.monitor_type,
         timebucket;

场景三:按 1h 时间窗,查询每个区域中每条管线的条目数、累计值与均值。

SELECT
    time_bucket(t.k_collect_time, '1h') AS timebucket,
    s.region_id,
    w.region_name,
    pinfo.pipeline_name,
    COUNT(t.k_collect_time) AS measurement_count,
    SUM(t.monitor_value) AS total_measure_value,
    AVG(t.monitor_value) AS avg_measure_value
FROM
    db_monitor.t_monitor_point t,
    monitor_r.site_info s,
    monitor_r.region_info w,
    monitor_r.pipeline_info pinfo
WHERE
    t.region_id = s.region_id
    AND t.pipeline_id = pinfo.pipeline_id
    AND s.region_id = w.region_id
    AND t.k_collect_time BETWEEN '2023-01-04 14:31:00' AND '2030-01-04 14:31:00'
GROUP BY
    timebucket, s.region_id, w.region_name, pinfo.pipeline_name
ORDER BY
    timebucket, s.region_id
LIMIT 100;

场景四:按 5s 时间窗,查询某区域内每个站点、某条管线、每种采集类型的数值均值。

SELECT wi.region_name,
       si.site_name,
       t.monitor_type,
       time_bucket(t.k_collect_time, '5s') as timebucket,
       avg(t.monitor_value)
FROM monitor_r.point_base_info pi,
     monitor_r.pipeline_info li,
     monitor_r.region_info wi,
     monitor_r.site_info si,
     db_monitor.t_monitor_point t
WHERE pi.pipeline_id = li.pipeline_id
  AND pi.site_id = si.site_id
  AND si.region_id = wi.region_id
  AND pi.point_id = t.point_id
  AND li.pipeline_name = 'Pipe_9'
  AND wi.region_name = 'Area_7'
GROUP BY wi.region_name, si.site_name, t.monitor_type, timebucket;

实测输出(阶段 03,Playground 课程容器内执行四组查询):

场景一(52 行,节选)——返回 site_name / count / avg 三列:

site_name count avg
Station_0 19 590.1578947368421
Station_2 17 582.0588235294117
Station_3 12 526.4166666666666
Station_4 19 501.57894736842104
Station_5 19 445.05263157894734
...
(共 52 行)

场景二(16 行,节选)——5 表联结,返回 8 列(含 timebucket 与 avg / max / min / count):

region_name site_name monitor_type timebucket avg_value max_value
min_value number_of_value
Area_12 Station_d 2 2026-10-02 06:38:20+00:00 782 782 782 1
Area_12 Station_d 2 2026-10-02 05:49:50+00:00 793 793 793 1
Area_12 Station_d 2 2026-10-02 08:08:10+00:00 72 72 72 1
Area_12 Station_d 2 2026-10-02 08:27:00+00:00 249 249 249 1
...
(共 16 行)

场景三(100 行,节选,受语句末尾 LIMIT 100 约束)——返回 7 列:

timebucket region_id region_name pipeline_name measurement_count
total_measure_value avg_measure_value
2026-10-02 05:00:00+00:00 region_1 Area_1 Pipe_14 24 11892 495.5
2026-10-02 05:00:00+00:00 region_1 Area_1 Pipe_9 12 4740 395
2026-10-02 05:00:00+00:00 region_1 Area_1 Pipe_22 12 5184 432
2026-10-02 05:00:00+00:00 region_1 Area_1 Pipe_2 24 4080 170
...
(共 100 行)

场景四(6 行,节选)——官方固定取值 Pipe_9 + Area_7 在 Playground 的规范数据集上有效:

region_name site_name monitor_type timebucket avg
Area_7 Station_J 2 2026-10-02 06:18:25+00:00 453
Area_7 Station_J 2 2026-10-02 06:42:05+00:00 15
Area_7 Station_J 2 2026-10-02 07:46:30+00:00 596
Area_7 Station_J 2 2026-10-02 08:04:50+00:00 672
...
(共 6 行)

时间列为何是"当前时间":课程容器内的数据由 generate_data.sh 于本次导入时实时生成,因此 timebucket 落在执行当天(2026-10-02),而非官方材料的固定日期。这恰好是一处真实执行的可辨别特征——若你复测时看到的时间也是执行当日,说明数据确由本次运行生成。

四组结果对照(Playground 课程容器 vs 裸 KWDB 实例):

场景 Playground(10,000 行样本) 裸实例(200万行) 结构是否一致
一 52 行 54 行 ✅ 三列 site_name / count / avg
二 16 行 5,343 行 ✅ 八列,含 timebucket 与 avg / max/ min / count
三 100 行 100 行 ✅ 七列(两者均受 LIMIT 100 约束)
四 6 行 0 行(空集) ✅ 五列;取值有效性依数据集而异

注意(这是本文最想说明的一点):场景四的官方固定取值 Pipe_9 + Area_7,在 Playground的规范数据集下返回 6 行;而在裸实例那份派生数据下返回空集。同一条 SQL,两份数据集,结果不同——这不是语句问题,而是数据集分布差异。可复现承诺的边界正在这里:结构可复现,取值需按数据集核验。

为在裸实例上证明语句本身可返回非空,改用该实例中实际共现的一组取值 Pipe_26 + Area_35(先用 GROUP BY pipeline_name, region_name 统计确认交集存在),同一语句即返回10,778 行,见 evidence/03_query_s4b.out。同一场景在两地一空一满,正是"结构稳定、取值依数据"的双向印证。

提示:官方 create_load.sql 的时序表 TTL 子句写作 ACTIVETIME 3h,在 KWDB 3.2.2 上报syntax error (42601)——该关键字已被 RETENTIONS 取代。照抄官方原始脚本会卡在这一行;本文 4.2 节的建表 SQL 已按 3.2.2 写法给出,脚本包 02_load.sh 亦会自动完成替换。

四组完整回显见 evidence/03_query_pg_s1.out ~ 03_query_pg_s4.out(Playground)与03_query_s1.out ~ 03_query_s4b.out(裸实例)。"跨模联结在单实例内完成、返回融合结果集"这一结构性结论,由两个环境、四个场景一致证实。

证据图03-跨模查询执行结果.png

复测证据图 3 在 Playground 课程容器的 Shell 终端内批量执行四组查询,返回 52 / 16 / 100 / 6 行(截图中 场景N 数据行数 为去表头后的精确数据行数),证明跨模联结在同一实例内完成、返回融合结果集。

复测锚点:进入「跨模查询」课程 → 启动容器 → 在终端执行

for n in 1 2 3 4; do printf "场景%s 数据行数: " $n; \
./kwbase sql --insecure --host=127.0.0.1 --format=tsv < q$n.sql 2>&1 | tail -n +2 | grep -c .; done

期望输出依次为 52、16、100、6;q1.sql ~ q4.sql 由脚本包 03_query.sh 推入容器。

5 三终端联动:从 SQL 结果到一张图

5.1 为什么在同一页出图

跨模查询的返回是「分组 × 聚合列」的二维表。数据一多,读表效率骤降:几十行的时间桶、每桶若干统计量,人眼很难从数字里看出趋势。

传统做法是「在数据库侧导出结果 → 拷到本机 → 打开 Notebook 绘图」。这条链路有两个断点:导出与拷贝。导出格式、字段顺序、编码,任一处变化都会让图与查询脱钩,读者也就无法判断图到底对应哪一次运行。

Playground 的价值在于:取数与绘图都发生在它管理的容器内,读者不必把结果拷到自己的本机开发环境。图与查询出自同一份数据、同一套连接参数,可验证性由 evidence/ 下的回显保证。

5.2 Code 终端取数并绘图

本文在「Python 连接 KWDB」(python-kwdb)课程的 Code 终端内完成这一闭环。需先说明一处环境前提:该课程与「跨模查询」课程各自独立容器,跨模数据需先从 multi-mode 容器导出(create_load.sql + extern/ 数据目录),再 docker cp 进 python-kwdb 容器内导入——脚本包 reproduce/05_code_term.sh 已封装该步骤。

在 Code 终端编辑器中写入以下 Python 文件并点击「运行」(源码与脚本包 reproduce/plot_crossmode.py 一致):

# plot_crossmode.py —— 在 Playground 的 Code 终端内运行
import psycopg2
SQL = """
SELECT si.site_name,
       COUNT(t.monitor_value) AS cnt,
       AVG(t.monitor_value) AS avg_value
FROM monitor_r.site_info si,
     monitor_r.region_info wi,
     db_monitor.t_monitor_point t
WHERE wi.region_id = si.region_id
  AND si.site_id = t.site_id
  AND t.monitor_type = 1
  AND t.monitor_value > 50
GROUP BY si.site_name
HAVING COUNT(t.monitor_value) > 3
ORDER BY si.site_name
"""
conn = psycopg2.connect(host="127.0.0.1", port=26257,
                        user="root", dbname="defaultdb")
cur = conn.cursor()
cur.execute(SQL)
rows = cur.fetchall()
print("跨模查询返回行数:", len(rows))
for r in rows[:5]:
    print(" %-14s cnt=%-5s avg=%.4f" % (r[0], r[1], float(r[2])))
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
names = [r[0] for r in rows]
avgs = [float(r[2]) for r in rows]
plt.figure(figsize=(13, 5))
plt.bar(names, avgs, color="#2f6f9f")
plt.xticks(rotation=90, fontsize=8)
plt.ylabel("avg monitor_value")
plt.title("Cross-model query: avg monitor_value per site (type=1, value>50)")
plt.tight_layout()
plt.savefig("crossmode_avg.png", dpi=110)
print("已保存: crossmode_avg.png")
cur.close()
conn.close()
print("done")

注意(连接参数):课程容器默认以 --insecure 模式在 127.0.0.1:26257 提供 PG 线协议,用户 root、无密码;psycopg2 为镜像内置。跨模查询以全限定名(monitor_r.* / db_monitor.*)跨库访问,无需切换库。

提示(容器内缺库时):Code 终端支持 Python、Bash、Java 三种语言。若绘图所需的库缺失,可在容器内安装——注意镜像的 pip 受 PEP 668 约束,需加 --break-system-packages:

pip3 install matplotlib --break-system-packages

实测输出(阶段 04,Code 终端真实执行):

跨模查询返回行数: 52
前 5 行:
  Station_0 cnt=19 avg=590.1579
  Station_2 cnt=17 avg=582.0588
  Station_3 cnt=12 avg=526.4167
  Station_4 cnt=19 avg=501.5789
  Station_5 cnt=19 avg=445.0526
已保存: crossmode_avg.png
done

下图即上述 Code 终端运行原样产出的图(保存于容器内 /kaiwudb/bin/crossmode_avg.png,横轴为 52 个站点,纵轴为平均采集值)。

图04-跨模查询聚合结果.png

图 4 查询结果即图:同一容器内"取数—绘图"闭环的真实产物。

证据图04-Code终端取数与出图.png

复测证据图 4 证明「查询 → 取数 → 出图」在同一容器内闭环完成:右侧编辑器为上述 Python 源码(可见 matplotlib 绘图段),下方输出面板打印 跨模查询返回行数: 52 与 已保存: crossmode_avg.png。

复测锚点(可照抄):在「Python 连接 KWDB」课程 Code 终端运行 reproduce/plot_crossmode.py,期望输出面板出现 跨模查询返回行数: 52 与 已保存: crossmode_avg.png;原始回显见 evidence/ch5-2-code-run_output.txt。

6 工程复盘:隔离式实验环境 vs 本地容器

6.1 一张对照表

图03-本地容器与Playground对照.png

维度 本地容器部署 KWDB Playground
前置依赖 装 Docker + 拉 KWDB 镜像 装 Playground(仍依赖 Docker)
启动到可运行 分钟级,受镜像体积影响 分钟级,智能选源加速
环境隔离 需手动管理容器 每访客独立、用完即弃
多会话续跑 需自行 commit 或卷挂载 原生暂停/恢复(实测 v1.4.3;官方文档记其于 v0.5.0 引入)
查询即出图 另开 Notebook 对接 SQL 终端 + Code 终端同页联动
可移植分享 分发镜像与脚本 浏览器即开即给链接
适用边界 生产、压测、调优 教学、演示、复测验证

6.2 复盘结论

第一,选择取决于目的。若目标是评估生产部署形态,本地容器(或直接部署)不可替代;若目标是让读者复现一次查询,Playground 的前置条件更少。

第二,两种方式的代价与风险不同。 本地容器把环境留在本机,便于反复试验,但要自己承担清理与版本管理;Playground 把环境交给平台,省去清理,但容器资源受限,不适合做性能结论。

第三,多会话带来的收益与实验长度成正比。 建表导入耗时越长,「暂停/恢复」的价值越大。短实验下这个属性几乎无感。

实测输出(阶段 05,暂停 / 恢复,实验机 kb-lab-01):

$ curl -X POST http://127.0.0.1:3006/api/courses/multi-mode/pause
$ docker ps -a --format "{{.Names}} | {{.Status}}"
kwdb-playground-multi-mode-1790890873878351895 | Up 31 minutes (Paused)
# —— 稍后恢复 ——
$ curl -X POST http://127.0.0.1:3006/api/courses/multi-mode/resume
$ # 在恢复后的终端内核对数据是否仍在
root@16c43184e37d:/kaiwudb/bin# ./kwbase sql --insecure --host=127.0.0.1 \
    -e 'SELECT count(*) AS points FROM db_monitor.t_monitor_point;'
points
10000
Time: 1.705714ms

暂停后容器状态为 Paused;恢复后终端自动重连,此前导入的 10,000 行数据仍在——环境状态被完整保存,读者可分次完成复测。

证据图05-容器暂停与恢复.png

复测证据图 5 证明实验进度可保存与恢复,适配分次复测:上图为暂停态(右上角「容器 · 已暂停」+「恢复容器」按钮),下图为恢复态(「容器 · 运行中」,终端重连后 count = 10000)。

复测锚点(可照抄):课程页点击「暂停容器」→ 确认右上角显示「已暂停」→ 点击「恢复容器」→ 在终端执行

./kwbase sql --insecure --host=127.0.0.1 -e 'SELECT count(*) AS points FROM db_monitor.t_monitor_point;'

期望返回 10000(脚本包 05_resume.sh 已封装该检查)。

6.3 什么情况下不适用

结论先行:容器资源受限,Playground 得不出可外推的性能数字,因此不适合压测与容量评估。这条边界由运行形态决定,不是软件缺陷。

对比两种路径的成本结构,可以看出取舍所在。本地容器把维护成本留在使用者一侧,换来对环境的完全掌控;Playground 把这部分成本转移到平台,代价是对平台可用性的依赖,以及在资源调度上引入的不确定性。

判断是否需要它,可以看读者构成:若多数读者只想复现一个结论,隔离环境的收益大于代价;若多数读者打算在此基础上二次开发,本地容器更合适。这里的取舍与 2.4 节一致——两者服务的目标不同,不是相互替代关系。

7 复测取证单

本章是给复测者的对照表。每张证据图对应一条复测命令、一个文件名与一个正文插入位置。复测时应逐条执行、逐条落图;若某项判据不成立,停下并记录,不要继续。

7.1 截图位总表(表 A)

截图位 证据图 内容 复测命令 文件名 正文位置
1 图 1 Playground 安装完成 curl -fsSL https://kwdb.tech/playground.sh | bash figures/shots/shot-01-install.png 3.2 节实测输出下方
2 图 2 启动与课程列表 kwdb-playground start figures/shots/shot-02-start.png 3.3 节实测输出下方
3 图 3 跨模查询实测输出 SQL 终端执行场景一至四 figures/shots/shot-03-sql-query.png 4.3 节实测输出下方
4 图 4 Code 终端出图 Code 终端运行 plot_crossmode.py figures/shots/shot-04-code-plot.png 5.2 节实测输出下方
5 图 5 多会话暂停/恢复 课程页暂停后重新进入 figures/shots/shot-05-multisession.png 6.2 节实测输出下方

7.2 成功判据

阶段 判据 本次实测值
00 终端出现 kwdb-playground 安装路径与版本号 /usr/local/bin/kwdb-playground,v1.4.3
01 浏览器可打开 http://localhost:3006 且课程卡片可点击进入 HTTP 200,12 门课程
02 4 张关系表与 1 张时序表均导入成功,SELECT count(*) 返回非零 时序 10,000 行;关系436/41/26/1500
03 四组查询均返回结果,含 timebucket 与聚合列 52 / 16 / 100 / 6 行
04 终端打印 已保存: crossmode_avg.png,页面可预览该图 返回 52 行并出图
05 暂停后容器为 Paused,恢复后数据仍在 count = 10000

8 排障

下表每一行均为本次实测真实遇到的问题,附定位方法与处理方式。

现象 可能原因 处理
课程容器创建失败(接口返回 500),docker ps 无新容器 默认源 Docker Hub(registry-1.docker.io)不可达 启动课程时显式指定可达镜像源(阿里云 ACR 或 ghcr.io);见 3.3 节实测
建表报 at or near "activetime": syntax error (42601) KWDB 3.2.2 已用 RETENTIONS 取代 ACTIVETIME 把官方 create_load.sql 中的 ACTIVETIME 3h 替换为 RETENTIONS 3h;脚本包已自动处理
启动「SQL 终端快速体验 KWDB」课程失败,提示端口被占用 该课程映射 26257,与主机上已有 KWDB 冲突 改用端口为 0(不映射)的课程,如 multi-mode 或 python-kwdb
pip3 install 报 externally-managed-environment(PEP 668) 镜像 Python 受系统包管理保护 追加 --break-system-packages,或在虚拟环境中安装
课程页停在「环境准备中」 Docker 未运行或镜像未就绪 启动 Docker,点「环境检查」诊断,或切换镜像源
端口 3006 被占用 本机已有进程占用 按 Playground 提示改端口,或释放该端口
导入行数翻倍 重复执行导入 重新进入干净容器,或先清空同名表再导入
Code 终端连不上数据库 客户端协议或参数不符 核对连接参数;改用「SQL 导出 CSV -> Code 读文件」路径
绘图中文显示为方块 容器缺中文字体 安装中文字体,或图内标签改用英文(本文即采用后者)
场景四返回空集 官方固定过滤值在该数据集下无交集 属数据集分布差异,非语句错误;换用该数据集内共现的取值,见 4.3 节

注意:报错信息指向查询语句时,未必是查询的问题。跨模查询依赖两库与多张表,先确认建库建表与导入已成功,再排查 SQL。

附录 A 配套脚本包结构

kwdb_playground_crossmode/
├── env.sh # 唯一配置点:端口、容器名、KWDB 连接参数
├── 00_install.sh # 安装 Playground
├── 01_start.sh # 启动服务并检查课程页
├── 02_load.sh # 建库建表与导入(检测优先,默认不 DROP 存量数据)
├── 03_query.sh # 执行四组跨模查询(含实例校验变体 scenario4b)
├── 04_plot.py # 本地等价出图脚本(读 evidence/ 的回显文件绘图)
├── 05_code_term.sh # 为 Code 终端课程准备跨模数据(跨容器搬运)
├── plot_crossmode.py # 在 Playground Code 终端内运行的取数+绘图脚本
├── 05_resume.sh # 多会话暂停/恢复检查
├── sql/ # 官方四组查询 SQL(原样)+ 实例校验变体 scenario4b.sql
├── run_all.sh # 总控:支持 --list / 区间执行 / 失败即停
└── README.md

其中 plot_crossmode.py 与正文 5.2 节所列源码逐字一致;05_code_term.sh 封装了「跨容器搬数据 + 检测优先导入 + 装依赖」三步,是复现第 5 章的前置步骤。

运行方式:

bash run_all.sh # 从头到尾
bash run_all.sh --list # 列出阶段
bash run_all.sh 02 03 # 只跑 02–03

所有回显落盘至 evidence/,供回填正文与取证。

-- 坚持原创,转载请注明出处 --

评论(0)

Me