开始写这篇文章之前,我一直在想拿什么场景来讲 KWDB 的监控接入。纯讲 API、讲配置也能写,但读者看完大概只会记住几个路径,最终我还是选定了园区物业的能耗管理场景,原因很简单:有真实的业务数据(电表、区域、功率、能耗),也有真实的"看不出来"的时刻——数据都在库里,可一旦大屏上的曲线不对劲,光靠数据库自身的指标根本说不清问题出在哪。
想象下某个场景,园区物业的能耗大屏上,某天总功率曲线突然抬高。值班同事第一反应是去看 KWDB 节点——CPU、内存、SQL 延迟,全都平稳得不像有事。那就只能确认一件事:电量确实变高了。但到底是哪片区域、哪批电表先出的偏差?变化是出在上报、聚合,还是查询链路?没人说得清。
其实 SampleDB 里这些数据都有。tsdb.meter_data 存着电压、电流、功率和能耗,rdb.meter_info、rdb.area_info 描述电表和区域。缺的不是数据,而是一条把业务数据变成监控指标的链路——数据躺在库里,监控看不见,等于没有。这次接入最后拆成了两条路径:KWDB 自身的指标走官方 /_status/vars,SampleDB 的业务指标则由一个小 Python Exporter 查询聚合后暴露成 /metrics。Prometheus 负责采集和告警,Grafana 把两类指标摆在一起看。说白了,就是让"库里的数据"和"监控的眼睛"接上线。
先把两类监控指标分开
1. 场景和数据量
为了让演示不依赖 SampleDB 自带的 10,100 条时序样例数据,测试环境将同一套表结构扩展到一个月的园区数据:
| 数据对象 | 规模 |
| 电表 | 300 个 |
| 区域 | 100 个 |
| 采样间隔 | 5 分钟 |
| 时间范围 | 2026-07-01 00:00 至 2026-07-31 23:55 |
| 时序记录 | 约 267.8 万条 |
| 监控事件 | 2026-07-15 14:20 功率异常 SampleDB 原始表结构不需要改动。关系表仍然是: |
rdb.meter_info
rdb.user_info
rdb.area_info
rdb.alarm_rules
时序表仍然是:
tsdb.meter_data(
ts,
voltage,
current,
power,
energy,
meter_id
)
2. 扩展一批带日期的数据
SampleDB 仓库提供了 generate_series 的插入示例。这里把时间固定到 2026 年 7 月,方便演示告警时刻和回放查询:
INSERT INTO tsdb.meter_data
(ts, voltage, current, power, energy, meter_id)
SELECT
TIMESTAMPTZ '2026-07-01 00:00:00+00:00'
+ s * INTERVAL '5 minutes',
220.0 + ((m + s) % 10)::FLOAT8,
5.0 + ((m + s) % 15)::FLOAT8 * 0.1,
1000.0 + ((m + s) % 20)::FLOAT8 * 50,
5000.0 + s::FLOAT8 * 10,
'M' || m::TEXT
FROM generate_series(0, 8927) AS s
CROSS JOIN generate_series(1, 300) AS m;
写入后先确认行数和时间范围:
SELECT COUNT(*) AS rows,
MIN(ts) AS first_ts,
MAX(ts) AS last_ts
FROM tsdb.meter_data
WHERE ts >= TIMESTAMPTZ '2026-07-01 08:00:00+00:00'
AND ts < TIMESTAMPTZ '2026-08-01 09:00:00+00:00';
3. 两条监控路径
KWDB 集群指标使用官方配置:
KWDB node:8080/8081/8082
|
| GET /_status/vars
v
Prometheus job=kaiwudb
|
v
官方 Grafana Dashboard
SampleDB 业务指标单独走 Exporter:
tsdb.meter_data + rdb.meter_info + rdb.area_info
|
| SQL 聚合
v
Python Exporter :9105/metrics
|
v
Prometheus job=sampledb-exporter
|
v
业务能耗 Dashboard
这一步不能省略。/_status/vars 暴露的是 KWDB 节点指标,不会自动把 meter_data.power 变成 Prometheus 指标。
先写一个只导出聚合数据的 Exporter
1. 指标不要按电表无限展开
如果把 meter_id 作为 Prometheus 标签,每个电表都会产生一组新的时间序列。电表数量一大,Prometheus 的基数和存储压力会跟着上升。
这次只暴露区域级指标:
sampledb_area_power_watts{region,area}
sampledb_meter_data_lag_seconds{region,area}
sampledb_meter_status_total{status}
sampledb_exporter_scrape_duration_seconds
需要查看某个电表的原始点位时,仍然回到 KWDB 查询,不把明细全部搬进 Prometheus。
2. 安装依赖
Psycopg2 按 KWDB 的 Python 连接示例安装,Exporter 指标使用 Prometheus 官方 Python 客户端:
python3.10 -m venv .venv
source .venv/bin/activate
pip install psycopg2-binary prometheus-client
连接密码放在环境变量里:
export KWDB_HOST=127.0.0.1
export KWDB_PORT=26257
export KWDB_USER=monitor_reader
export KWDB_PASSWORD='change-me'
export KWDB_DATABASE=defaultdb
export EXPORTER_PORT=9105
3. Exporter 代码
下面的代码使用 5 分钟窗口聚合区域功率,并计算每个区域最后一条数据的延迟。MONITOR_NOW 可用于回放 2026 年 7 月 15 日的场景;不设置时使用当前时间。
import os
import time
from datetime import datetime, timezone
import psycopg2
from prometheus_client import Gauge, Histogram, start_http_server
AREA_SQL = """
WITH power_5m AS (
SELECT
mi.area_id,
SUM(md.power)::FLOAT8 AS total_power
FROM tsdb.meter_data AS md
JOIN rdb.meter_info AS mi
ON md.meter_id = mi.meter_id
WHERE md.ts >= %s::TIMESTAMPTZ - INTERVAL '5 minutes'
GROUP BY mi.area_id
),
last_seen AS (
SELECT
mi.area_id,
MAX(md.ts) AS last_ts
FROM tsdb.meter_data AS md
JOIN rdb.meter_info AS mi
ON md.meter_id = mi.meter_id
WHERE md.ts >= %s::TIMESTAMPTZ - INTERVAL '24 hours'
GROUP BY mi.area_id
)
SELECT
a.region,
a.area_name,
COALESCE(p.total_power, 0),
s.last_ts
FROM rdb.area_info AS a
LEFT JOIN power_5m AS p
ON p.area_id = a.area_id
LEFT JOIN last_seen AS s
ON s.area_id = a.area_id
"""
STATUS_SQL = """
SELECT status, COUNT(*)
FROM rdb.meter_info
GROUP BY status
"""
area_power = Gauge(
"sampledb_area_power_watts",
"Aggregated power of an area",
["region", "area"],
)
data_lag = Gauge(
"sampledb_meter_data_lag_seconds",
"Seconds since the latest meter sample in an area",
["region", "area"],
)
meter_status = Gauge(
"sampledb_meter_status_total",
"Number of meters by status",
["status"],
)
scrape_duration = Histogram(
"sampledb_exporter_scrape_duration_seconds",
"Time spent querying KWDB",
)
scrape_failures = Gauge(
"sampledb_exporter_last_scrape_success",
"Whether the last SampleDB scrape succeeded",
)
def monitor_now():
value = os.getenv("MONITOR_NOW")
if value:
return datetime.fromisoformat(value).astimezone(timezone.utc)
return datetime.now(timezone.utc)
def collect():
started = time.perf_counter()
conn = None
try:
conn = psycopg2.connect(
host=os.getenv("KWDB_HOST", "127.0.0.1"),
port=int(os.getenv("KWDB_PORT", "26257")),
user=os.getenv("KWDB_USER", "monitor_reader"),
password=os.environ["KWDB_PASSWORD"],
dbname=os.getenv("KWDB_DATABASE", "defaultdb"),
connect_timeout=3,
options="-c statement_timeout=4000",
)
at = monitor_now()
with conn.cursor() as cur:
cur.execute(AREA_SQL, (at, at))
for region, area, power, last_ts in cur.fetchall():
area_power.labels(region, area).set(float(power or 0))
if last_ts is None:
lag = 86400.0
else:
if last_ts.tzinfo is None:
last_ts = last_ts.replace(tzinfo=timezone.utc)
lag = max(0.0, (at - last_ts).total_seconds())
data_lag.labels(region, area).set(lag)
cur.execute(STATUS_SQL)
for status, count in cur.fetchall():
meter_status.labels(status).set(int(count))
scrape_failures.set(1)
except Exception:
scrape_failures.set(0)
raise
finally:
if conn is not None:
conn.close()
scrape_duration.observe(time.perf_counter() - started)
if __name__ == "__main__":
start_http_server(int(os.getenv("EXPORTER_PORT", "9105")))
while True:
try:
collect()
except Exception as exc:
print(f"sampledb scrape failed: {exc}", flush=True)
time.sleep(30)
代码里有几个刻意的限制:查询窗口只有 5 分钟,SQL 设置了 4 秒超时,Prometheus 标签只保留区域维度。Exporter 自己也输出成功状态和查询耗时,出了问题不会只剩下一块空白的 Grafana 面板。
启动并检查:
export MONITOR_NOW='2026-07-15T14:25:00+00:00'
python exporter.py
curl http://127.0.0.1:9105/metrics | grep sampledb_
把 KWDB 原生指标和业务指标写进 Prometheus
本文按 KWDB 3.2.0 的部署方式组织,Prometheus 和 Grafana 版本沿用文档示例中的 Prometheus 2.53.0 与 Grafana 11.1.0。
1. Prometheus 配置
KWDB 官方示例使用 /_status/vars,不是常见的 /metrics。自定义 Exporter 才使用 /metrics:
global:
scrape_interval: 10s
evaluation_interval: 10s
rule_files:
- "rules/alerts.rules.yml"
- "rules/aggregation.rules.yml"
- "rules/sampledb.rules.yml"
scrape_configs:
- job_name: "kaiwudb"
metrics_path: "/_status/vars"
scheme: "http"
static_configs:
- targets:
- "127.0.0.1:8080"
- "127.0.0.1:8081"
- "127.0.0.1:8082"
labels:
cluster: "my-kaiwudb-cluster"
- job_name: "sampledb-exporter"
metrics_path: "/metrics"
static_configs:
- targets:
- "127.0.0.1:9105"
labels:
source: "sampledb"
启动 Prometheus:
./prometheus --config.file=prometheus.yml
页面默认在 http://127.0.0.1:9090。第一次配置时先检查两个 Job 都是 UP,再去看 Grafana。否则后面的面板问题很难区分是 PromQL 写错还是根本没有采集到数据。
2. 增加业务告警规则
KWDB 自带的 alerts.rules.yml 继续保留,用来检测节点不可达、磁盘空间、Range、Raft 和证书等问题。SampleDB 业务规则单独放在 sampledb.rules.yml:
groups:
- name: sampledb.business
rules:
- alert: SampleDBAreaPowerHigh
expr: sampledb_area_power_watts > 7000
for: 5m
labels:
severity: warning
annotations:
summary: "区域功率超过阈值"
description: "{{ $labels.region }}/{{ $labels.area }} 当前功率 {{ $value }} W"
- alert: SampleDBMeterDataLag
expr: sampledb_meter_data_lag_seconds > 180
for: 3m
labels:
severity: critical
annotations:
summary: "电表数据延迟超过 3 分钟"
description: "{{ $labels.region }}/{{ $labels.area }} 没有及时上报数据"
- alert: SampleDBExporterDown
expr: up{job="sampledb-exporter"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "SampleDB Exporter 不可用"
SampleDBAreaPowerHigh 是业务告警,InstanceDead 是数据库节点告警。两者同时出现时,排查顺序不同:先确认 Exporter 是否还能查询,再看 KWDB 节点本身是否异常。
3. 通过 API 验证采集结果
curl 'http://127.0.0.1:9090/api/v1/query?query=up%7Bjob%3D%22sampledb-exporter%22%7D'
也可以直接查询原生 SQL 延迟指标:
curl --get 'http://127.0.0.1:9090/api/v1/query' \
--data-urlencode 'query=histogram_quantile(0.99, rate(sql_service_latency_bucket{job="kaiwudb"}[5m]))'
在 Grafana 里合并两套监控视图
1. 添加 Prometheus 数据源
Grafana 默认地址是 http://127.0.0.1:3000。在 Connections > Data sources 中添加 Prometheus,URL 填:
http://127.0.0.1:9090
点击 Save & test,确认数据源可用。
生产环境不要继续使用默认的 admin/admin,也不要直接照搬 Prometheus 配置里的 insecure_skip_verify: true。启用 HTTPS 时应配置受信任的 CA,而不是跳过证书校验。
2. 导入 KWDB 官方面板
先导入官方面板,再新建一个业务 Dashboard。比较有用的三个面板是:
1.KaiwuDB_Console_Overview.json
4.KaiwuDB_Console_SQL.json
9.KaiwuDB_Console_Slow_Query.json
官方 SQL 面板已经使用了以下指标:
sql_conns
sql_failure_count
sql_service_latency_bucket
exec_latency_bucket
sql_distsql_queries_active
慢查询面板关注:
requests_slow_raft
requests_slow_distsender
requests_slow_lease
requests_slow_latch
3. 增加 SampleDB 业务面板
园区总功率:
sum(sampledb_area_power_watts)
按区域排行:
topk(10, sampledb_area_power_watts)
数据延迟:
max(sampledb_meter_data_lag_seconds)
Exporter 查询耗时 P95:
histogram_quantile(
0.95,
rate(sampledb_exporter_scrape_duration_seconds_bucket[5m])
)
KWDB SQL 服务延迟 P99:
histogram_quantile(
0.99,
rate(sql_service_latency_bucket{job="kaiwudb"}[5m])
)
这几块面板放在同一行时,能看出一个很实用的关系:如果业务功率图异常,但 KWDB SQL 延迟正常,问题更可能在数据本身或采集链路;如果两者一起升高,再去检查 SQL、连接数和节点资源。
把异常完整回放一遍
1. 先制造业务侧功率升高
为了模拟 14:20 的尖峰,在几块电表上追加一小段高功率数据:
INSERT INTO tsdb.meter_data
(ts, voltage, current, power, energy, meter_id)
SELECT
TIMESTAMPTZ '2026-07-15 14:20:00+00:00'
+ s * INTERVAL '5 minutes',
228.0,
8.2,
3200.0,
9000.0 + s * 20,
'M' || (s + 1)::TEXT
FROM generate_series(0, 5) AS s;
Exporter 下一轮采集后,Prometheus 中这些电表所属区域的 sampledb_area_power_watts 会升高。持续 5 分钟后,SampleDBAreaPowerHigh 进入 firing 状态。
2. 再模拟数据上报中断
暂时停止写入某个区域的电表数据,或者把 MONITOR_NOW 调到数据集之后:
export MONITOR_NOW='2026-08-01T00:10:00+00:00'
Exporter 仍然能访问 KWDB,但 sampledb_meter_data_lag_seconds 会持续增加。这个状态和数据库节点掉线不同:
up{job="kaiwudb"}
仍然是 1,而业务延迟指标已经超过 180 秒。监控系统因此能区分“数据库活着,但业务数据没有继续到达”。
3. 最后模拟 KWDB 节点不可达
停止一个节点或临时阻断其 HTTP 监控端口,官方规则中的:
up{job="kaiwudb"} == 0
会触发 InstanceDead。Grafana 的 Overview、SQL 和 Slow Query 面板也会少一个实例的数据,这时不应该继续把业务延迟全部归因于电表写入。
这套监控链路留下的几个结论
这次接入后,Prometheus 中保存的是两类不同的指标:
| 指标 | 代表什么 | 入口 |
|---|---|---|
| sql_conns、sql_service_latency_bucket | KWDB SQL 服务状态 | /_status/vars |
| requests_slow_raft、requests_slow_latch | 节点内部慢请求 | /_status/vars |
| sampledb_area_power_watts | 区域业务功率 | Exporter /metrics |
| sampledb_meter_data_lag_seconds | 电表数据延迟 | Exporter /metrics 几条规则最终固定下来: |
- 原始电表数据留在 KWDB,Prometheus 只接收低基数的聚合指标。
- KWDB 的 /_status/vars 和 Exporter 的 /metrics 分开配置、分开排障。
- 所有业务指标都要有对应的 Exporter 健康指标,不能只监控业务值。
- Grafana 先导入 KWDB 官方面板,再增加 SampleDB 业务面板,不修改官方面板中的指标语义。
- 告警规则按数据库层和业务层分组,避免收到一条告警后不知道应该查节点还是查数据采集。
- 这样串起来之后,监控的作用不再只是“看见曲线”。7 月 15 日的功率异常可以继续追到具体区域,数据延迟可以和数据库健康状态分开判断,SQL 延迟升高时也能在同一块 Grafana 页面上看到。SampleDB 负责保存明细,Prometheus 负责时间序列指标和告警,Grafana 负责把两者放到一个排查入口里。

评论(0)