TimescaleDB 时序数据库实战:给个人站做监控指标存储,超表、压缩、连续聚合与保留策略全流程

什么时候你需要的不是 MySQL 而是时序数据库

个人站长在搭建监控、记录站点访问指标、采集服务器 CPU/内存/带宽数据时,往往会习惯性地"用 MySQL 存一下"。一开始数据量小,看不出问题;但当采集频率上来(比如每 10 秒一条,10 个指标),一年就是几百万行,你会发现:查"最近 24 小时每分钟的平均值"越来越慢,磁盘占用疯涨,写入也开始拖累主库。

根源在于数据模型不匹配。MySQL 是通用关系型数据库,为事务(OLTP)优化;而监控指标这类数据是典型的时序数据(Time Series):只追加、不更新、按时间顺序产生、查询几乎总是带时间范围聚合。用关系库硬扛时序数据,就像用轿车拉货——能拉,但不是它的活。

TimescaleDB 是一个巧妙的折中:它是 PostgreSQL 的扩展(不是独立数据库),在保留完整 SQL 能力和生态的同时,给时序场景做了专门优化。对于已经熟悉 PostgreSQL 的站长来说,学习成本几乎为零。

TimescaleDB 的核心概念:Hypertable

普通 PostgreSQL 表叫 table,TimescaleDB 里对应的是 hypertable(超表)。你按普通表的方式建表、写 SQL,但底层 TimescaleDB 会按照时间维度自动把数据切分成多个 chunk(分块),每个 chunk 对应一个时间区间。

这样做的好处是:

  • 查询裁剪:查最近 1 小时,只需扫描对应的一两个 chunk,不用全表扫;
  • 写入分散:新数据只写最新的 chunk,索引永远保持小体积;
  • 自动治理:老 chunk 可以整块压缩、整块删除(drop),秒级完成,不像 DELETE 那样产生大量死元组。

关键在于:你用的还是标准 SQL,SELECT、JOIN、索引、外键都照常,应用层几乎不用改。

安装:Docker 一条命令搞定

个人站最省事的安装方式是 Docker 镜像,官方维护的 timescale/timescaledb 镜像开箱即用:

docker run -d --name timescaledb \
  -p 127.0.0.1:5432:5432 \
  -e POSTGRES_PASSWORD=your_strong_password \
  -e POSTGRES_DB=metrics \
  -v /data/timescale:/var/lib/postgresql/data \
  timescale/timescaledb:latest-pg16

注意端口映射写的是 127.0.0.1:5432,只监听本机。数据库绝不能直接暴露公网,需要远程访问时走 SSH 隧道或内网。

如果你是在已有的 PostgreSQL 上安装扩展,也可以:

-- 在目标数据库里启用扩展(需要超级用户)
CREATE EXTENSION IF NOT EXISTS timescaledb;

建一张超表

建超表分两步:先建普通表,再转成 hypertable。

-- 1. 建普通表(注意:时间列不能有空值)
CREATE TABLE server_metrics (
  time        TIMESTAMPTZ       NOT NULL,
  host        TEXT              NOT NULL,
  cpu_usage   DOUBLE PRECISION,
  mem_usage   DOUBLE PRECISION,
  net_rx_kb   BIGINT,
  disk_io_kb  BIGINT
);

-- 2. 转成超表,按 time 列切分,chunk 时间跨度 1 天
SELECT create_hypertable('server_metrics', 'time',
  chunk_time_interval => INTERVAL '1 day');

chunk_time_interval 的选择有讲究:原则是让每个 chunk 能放进内存。个人站数据量不大,1 天一个 chunk 通常合适;如果采集频率很高(秒级、多指标),可以缩到 6 小时或 1 小时。

建索引:按查询模式来

TimescaleDB 的索引建法和 PostgreSQL 一样。你的查询几乎总是"某台主机 + 某时间范围",所以复合索引把 host 放前面,time 放后面(或反过来,看具体查询):

CREATE INDEX idx_metrics_host_time
  ON server_metrics (host, time DESC);

因为超表按时间切 chunk,时间维度的过滤已经由分区裁剪承担,索引主要加速"特定主机"的筛选。别盲目建一堆索引,写入时每个索引都要维护,会拖慢写入速度。

写入数据

写入就是普通 INSERT,也可以用 PostgreSQL 的批量写入能力:

INSERT INTO server_metrics (time, host, cpu_usage, mem_usage, net_rx_kb, disk_io_kb)
VALUES
  (NOW(), 'web01', 12.5, 63.2, 1024, 512),
  (NOW(), 'web02', 8.1,  55.7, 2048, 256);

-- 应用侧高频写入建议用 COPY(比逐条 INSERT 快一个数量级)
-- COPY server_metrics FROM STDIN WITH (FORMAT csv);

实际采集脚本(比如用 shell 或 Python 定时抓 /proc 和 ss 的输出)建议攒批写入,每 10~30 秒提交一批,减少事务开销。

用连续聚合(Continuous Aggregates)加速长期查询

这是 TimescaleDB 最有价值的功能之一。问题场景:你想看"最近 30 天每小时的 CPU 均值",如果每次都从原始秒级数据算,扫描量巨大。连续聚合的做法是预先物化好聚合结果,并且随新数据自动增量更新:

CREATE MATERIALIZED VIEW cpu_hourly
WITH (timescaledb.continuous) AS
SELECT
  time_bucket('1 hour', time) AS bucket,
  host,
  AVG(cpu_usage) AS avg_cpu,
  MAX(cpu_usage) AS max_cpu
FROM server_metrics
GROUP BY bucket, host;

-- 配置自动刷新策略:1 小时前到 1 个月前的数据每小时刷新一次
SELECT add_continuous_aggregate_policy('cpu_hourly',
  start_offset => INTERVAL '1 month',
  end_offset   => INTERVAL '1 hour',
  schedule_interval => INTERVAL '1 hour');

time_bucket 是 TimescaleDB 提供的分桶函数,把时间戳归到指定粒度的桶里。end_offset 设为 1 小时是为了避免刷新那些还在写入、可能变化的最新桶。

之后查趋势就查这个视图,速度极快:

SELECT bucket, host, avg_cpu
FROM cpu_hourly
WHERE host = 'web01' AND bucket > NOW() - INTERVAL '30 days'
ORDER BY bucket;

压缩老数据:省磁盘的关键手段

时序数据写入后基本不再变动,非常适合列式压缩。TimescaleDB 的压缩对时序数据通常能达到 10 倍以上的压缩比:

-- 开启表的压缩,按 host 分段、按 time 排序
ALTER TABLE server_metrics SET (
  timescaledb.compress,
  timescaledb.compress_segmentby = 'host',
  timescaledb.compress_orderby   = 'time DESC'
);

-- 自动压缩 7 天前的 chunk
SELECT add_compression_policy('server_metrics', INTERVAL '7 days');

segmentby 决定按哪一列分组压缩(查询里经常过滤的列),orderby 决定块内排序。压缩后的 chunk 只能读不能写,所以只压缩足够老、不会再变动的数据。

把采集脚本和 TimescaleDB 连起来

前面讲了建表和查询,但时序库的价值要靠"持续写入"体现。下面是一个极简的 Linux 采集脚本思路,用 shell 抓取系统指标后批量写入:

#!/bin/bash
# 每 30 秒采集一次,攒批写入 TimescaleDB
HOST=$(hostname)
while true; do
  # 从 /proc 读取 CPU 使用率(简化示意)
  CPU=$(awk '/^cpu / {idle=$5; total=0; for(i=2;i<=8;i++) total+=$i; print (1-idle/total)*100}' /proc/stat)
  # 读取内存使用率
  MEM=$(free | awk '/Mem:/ {print $3/$2*100}')
  psql "host=127.0.0.1 dbname=metrics user=postgres" -c \
    "INSERT INTO server_metrics (time, host, cpu_usage, mem_usage)
     VALUES (NOW(), '$HOST', $CPU, $MEM);"
  sleep 30
done

这个例子每次单独 INSERT,适合验证;生产环境更推荐攒一批数据后用 COPY 批量写入。把脚本交给 systemd timer 或 crontab 托管,做好日志轮转,采集就稳了。写入端还有一个细节:一定要给连接加上合理的 statement_timeout,避免数据库抖动时采集脚本被长时间阻塞堆积。

另一个常见需求是"表已建好,之后才想加新指标列"。TimescaleDB 支持普通 ALTER TABLE ... ADD COLUMN,新列对历史 chunk 会补 NULL,对时序场景完全够用,不必重建超表。

数据保留策略:自动清理老数据

监控数据不需要永久保留。用保留策略自动 drop 老 chunk:

-- 只保留 90 天,更老的 chunk 自动删除
SELECT add_retention_policy('server_metrics', INTERVAL '90 days');

因为删除是整块 drop,几乎瞬间完成,不会像 DELETE FROM ... WHERE time < ... 那样产生海量死元组、需要 VACUUM 慢慢回收。还可以把保留策略和压缩策略配合:7 天前的数据先压缩(省磁盘),90 天后再删除,让"热数据不压缩、温数据压缩、冷数据清理"形成一套自动的分层治理。

实战查询示例

几个监控场景的常用 SQL:

-- 最近 24 小时每 5 分钟的平均 CPU,按主机分组
SELECT time_bucket('5 minutes', time) AS bucket,
       host,
       ROUND(AVG(cpu_usage)::numeric, 2) AS avg_cpu
FROM server_metrics
WHERE time > NOW() - INTERVAL '24 hours'
GROUP BY bucket, host
ORDER BY bucket DESC;

-- 找出最近 7 天 CPU 曾超过 90% 的主机
SELECT DISTINCT host
FROM server_metrics
WHERE time > NOW() - INTERVAL '7 days'
  AND cpu_usage > 90;

和 MySQL / InfluxDB 的选型对比

  • vs MySQL:MySQL 无自动分区裁剪和连续聚合,大数据量下聚合查询会越来越慢。但 MySQL 生态成熟、运维资料多。如果你已经在用 PostgreSQL,直接加 TimescaleDB 扩展是最平滑的路径。
  • vs InfluxDB:InfluxDB 是专用时序库,写入性能强,但查询语言(Flux/InfluxQL)自成一套,SQL 生态割裂,且社区版集群能力受限。TimescaleDB 的优势是"标准 SQL + 完整 PostgreSQL 生态",能直接和现有 BI 工具、ORM 打通。
  • 选择建议:已经用 PostgreSQL 的站,无脑选 TimescaleDB;纯新项目、追求极致写入吞吐、且能接受学习新查询语言,可以考虑 InfluxDB;数据量小、只是想存点统计,MySQL 加好索引也能凑合。

几个容易踩的坑

1. 时间列必须有值且不能更新。 hypertable 的分区键(时间列)不允许更新,插入时不能为 NULL。如果你的业务有时间会修正的场景,要额外设计。

2. 唯一约束必须包含时间列。 在超表上建 UNIQUE 或 PRIMARY KEY 时,必须把分区时间列包含进去,否则报错。这是分区表的通病。

3. chunk 太小会适得其反。 chunk_time_interval 设得太小(比如 1 分钟),会生成海量 chunk,元数据开销反而拖慢查询。个人站从 1 天起步,按数据量调整。

4. 压缩后不可写。 压缩 chunk 之前确认该时间范围的数据不再变动。往压缩过的 chunk 里插入数据会报错。

5. 备份用 pg_dump 要注意扩展。 TimescaleDB 的备份建议用 pg_dump 加 --schema-only 处理扩展定义,或直接用物理备份(pg_basebackup)。恢复时先建扩展再导数据。

小结

TimescaleDB 用"PostgreSQL 扩展"的方式,把时序数据的分区、压缩、连续聚合、保留策略这些能力,包装成了几条 SQL 就能配好的功能,同时不牺牲标准 SQL 能力。对个人站长来说,它是从 MySQL 迁移监控数据时风险最低的选择:你学的是 PostgreSQL 的 SQL,用的是时序优化的引擎。搭一座服务器监控系统,从建一张 hypertable、配好压缩和保留策略开始,就已经比"往 MySQL 里堆表"专业得多了。

Last modification:October 11th, 2026 at 10:30 pm

Leave a Comment