前言

如果你曾經處理過 IoT 感測器資料、伺服器監控指標,或是金融交易數據,你大概會發現傳統的關聯式資料庫在面對「大量、時間排序、持續寫入」這類資料時,效能會快速劣化。這正是時序資料庫(Time-Series Database, TSDB)存在的意義。

我在工作中接觸過不少監控系統,從早期用 MySQL 硬撐,到後來導入專門的時序資料庫,效能差距真的是天壤之別。今天這篇文章,我想帶你認識時序資料的特性,並且比較兩個主流方案:TimescaleDB 和 InfluxDB,幫助你在專案中做出合適的技術選型。


時序資料的特性

在深入工具之前,先理解什麼是「時序資料」:

核心特徵

  1. 時間戳是主鍵:每筆資料都有一個時間戳,查詢幾乎都圍繞時間範圍
  2. 寫入密集、甚少更新:資料進來就不太會改,像是溫度紀錄不會回去修改
  3. 最近的資料最熱門:查詢通常集中在最近幾小時或幾天
  4. 需要聚合運算:平均值、最大值、百分位數是家常便飯
  5. 資料量龐大:一台感測器每秒一筆,一千台一天就是 8,640 萬筆

常見應用場景

  • 基礎設施監控:CPU、記憶體、磁碟使用率(Prometheus / Grafana 那一套)
  • IoT 感測器:溫度、濕度、震動、電壓
  • 金融市場:股票報價、交易量
  • 應用程式指標:API 回應時間、錯誤率
  • 能源管理:用電量、發電量

TimescaleDB — 站在 PostgreSQL 的肩膀上

TimescaleDB 是 PostgreSQL 的一個擴充(Extension),這代表你可以用熟悉的 SQL 來操作時序資料,同時保有 PostgreSQL 所有的生態系優勢。

安裝

用 Docker 最簡單:

# docker-compose.yml
version: '3.8'
services:
  timescaledb:
    image: timescale/timescaledb:latest-pg16
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: tsadmin
      POSTGRES_PASSWORD: secret123
      POSTGRES_DB: sensor_data
    volumes:
      - ts_data:/var/lib/postgresql/data

volumes: ts_data:

docker compose up -d
docker exec -it <container_id> psql -U tsadmin -d sensor_data

建立超級表(Hypertable)

TimescaleDB 的核心概念是 Hypertable——它把一張普通的 PostgreSQL 表自動按時間分區(chunk),讓你不用自己管理分區邏輯。

-- 啟用擴充
CREATE EXTENSION IF NOT EXISTS timescaledb;

-- 建立普通表 CREATE TABLE sensor_readings ( time TIMESTAMPTZ NOT NULL, sensor_id INTEGER NOT NULL, temperature DOUBLE PRECISION, humidity DOUBLE PRECISION, location TEXT );

-- 轉換為超級表,以 time 欄位分區 SELECT create_hypertable('sensor_readings', 'time');

-- 建立複合索引 CREATE INDEX idx_sensor_time ON sensor_readings (sensor_id, time DESC);

寫入與查詢

-- 寫入資料
INSERT INTO sensor_readings (time, sensor_id, temperature, humidity, location)
VALUES
    (NOW(), 1, 25.3, 60.1, 'warehouse-A'),
    (NOW(), 2, 22.8, 55.7, 'warehouse-B'),
    (NOW() - INTERVAL '1 hour', 1, 24.9, 61.3, 'warehouse-A');

-- 查詢最近 24 小時某感測器的資料 SELECT time, temperature, humidity FROM sensor_readings WHERE sensor_id = 1 AND time > NOW() - INTERVAL '24 hours' ORDER BY time DESC;

-- 每 15 分鐘的平均溫度(TimescaleDB 專屬函式) SELECT time_bucket('15 minutes', time) AS bucket, sensor_id, AVG(temperature) AS avg_temp, MAX(temperature) AS max_temp, MIN(temperature) AS min_temp FROM sensor_readings WHERE time > NOW() - INTERVAL '7 days' GROUP BY bucket, sensor_id ORDER BY bucket DESC;

time_bucket() 是 TimescaleDB 的殺手級功能,比 PostgreSQL 原生的 date_trunc() 更靈活,可以指定任意時間間隔。

連續聚合(Continuous Aggregates)

當你的查詢總是在算「每小時平均值」之類的聚合,可以用連續聚合來預先計算:

-- 建立連續聚合視圖
CREATE MATERIALIZED VIEW hourly_avg
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('1 hour', time) AS hour,
    sensor_id,
    AVG(temperature) AS avg_temp,
    AVG(humidity) AS avg_humidity,
    COUNT(*) AS readings_count
FROM sensor_readings
GROUP BY hour, sensor_id;

-- 設定自動更新策略 SELECT add_continuous_aggregate_policy('hourly_avg', start_offset => INTERVAL '3 hours', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '1 hour' );

資料保留策略

時序資料通常不需要永久保存原始精度,可以設定自動清理:

-- 超過 30 天的原始資料自動刪除
SELECT add_retention_policy('sensor_readings', INTERVAL '30 days');

-- 壓縮超過 7 天的資料 ALTER TABLE sensor_readings SET ( timescaledb.compress, timescaledb.compress_segmentby = 'sensor_id' );

SELECT add_compression_policy('sensor_readings', INTERVAL '7 days');


InfluxDB — 專為時序而生

InfluxDB 是一個從頭設計的時序資料庫,不依附於任何現有的資料庫引擎。它有自己的查詢語言和儲存引擎,在時序場景下做了極致的最佳化。

安裝(InfluxDB 2.x)

# docker-compose.yml
version: '3.8'
services:
  influxdb:
    image: influxdb:2.7
    ports:
      - "8086:8086"
    environment:
      DOCKER_INFLUXDB_INIT_MODE: setup
      DOCKER_INFLUXDB_INIT_USERNAME: admin
      DOCKER_INFLUXDB_INIT_PASSWORD: secret123456
      DOCKER_INFLUXDB_INIT_ORG: myorg
      DOCKER_INFLUXDB_INIT_BUCKET: sensor_data
      DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: my-super-secret-token
    volumes:
      - influx_data:/var/lib/influxdb2

volumes: influx_data:

資料模型

InfluxDB 的資料模型和關聯式資料庫很不一樣:

  • Measurement:類似表名(如 sensor_readings
  • Tag:有索引的 metadata(如 sensor_id, location
  • Field:實際的數值(如 temperature, humidity
  • Timestamp:時間戳

用 Python 寫入資料

from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
from datetime import datetime

client = InfluxDBClient( url="http://localhost:8086", token="my-super-secret-token", org="myorg" )

write_api = client.write_api(write_options=SYNCHRONOUS)

# 寫入單筆資料 point = ( Point("sensor_readings") .tag("sensor_id", "1") .tag("location", "warehouse-A") .field("temperature", 25.3) .field("humidity", 60.1) .time(datetime.utcnow()) )

write_api.write(bucket="sensor_data", record=point)

# 批次寫入 points = [] for i in range(100): p = ( Point("sensor_readings") .tag("sensor_id", str(i % 10)) .tag("location", f"zone-{i % 3}") .field("temperature", 20.0 + (i * 0.1)) .field("humidity", 50.0 + (i * 0.05)) ) points.append(p)

write_api.write(bucket="sensor_data", record=points) client.close()

用 Flux 查詢(InfluxDB 2.x)

query_api = client.query_api()

# 查詢最近 1 小時的平均溫度 query = ''' from(bucket: "sensor_data") |> range(start: -1h) |> filter(fn: (r) => r._measurement == "sensor_readings") |> filter(fn: (r) => r._field == "temperature") |> filter(fn: (r) => r.sensor_id == "1") |> aggregateWindow(every: 5m, fn: mean, createEmpty: false) |> yield(name: "mean_temp") '''

tables = query_api.query(query) for table in tables: for record in table.records: print(f"{record.get_time()} -> {record.get_value():.2f}")


TimescaleDB vs InfluxDB — 怎麼選?

| 面向 | TimescaleDB | InfluxDB |
|——|————-|———-|
| 底層引擎 | PostgreSQL 擴充 | 自建儲存引擎 |
| 查詢語言 | SQL | Flux / InfluxQL |
| 學習成本 | 低(會 SQL 就行) | 中(Flux 要另學) |
| JOIN 能力 | 完整支援 | 不支援 |
| 壓縮率 | 好(~90%) | 極佳(~95%) |
| 寫入效能 | 高 | 極高 |
| 生態系 | PostgreSQL 全家桶 | TICK Stack |
| 適合場景 | 需要 JOIN、已有 PG | 純時序、極大寫入量 |

我的建議

  • 團隊已經熟悉 PostgreSQL → 選 TimescaleDB,學習成本最低,且可以和業務資料放在同一個資料庫裡做 JOIN
  • 純 IoT / 監控場景,寫入量極大 → 選 InfluxDB,它的寫入吞吐量確實更強
  • 需要複雜查詢和 JOIN → TimescaleDB 勝出,因為它就是 PostgreSQL
  • 團隊規模小、不想維護太多元件 → TimescaleDB,少一個服務就少一份心力

實戰:用 TimescaleDB 建一個簡易伺服器監控

最後來個完整的小範例,模擬伺服器監控的情境:

-- 建立監控資料表
CREATE TABLE server_metrics (
    time        TIMESTAMPTZ NOT NULL,
    host        TEXT NOT NULL,
    cpu_usage   DOUBLE PRECISION,
    mem_usage   DOUBLE PRECISION,
    disk_io     DOUBLE PRECISION,
    net_in      BIGINT,
    net_out     BIGINT
);

SELECT create_hypertable('server_metrics', 'time');

-- 模擬寫入資料 INSERT INTO server_metrics SELECT generate_series( NOW() - INTERVAL '7 days', NOW(), INTERVAL '10 seconds' ) AS time, 'web-server-' || (random() * 3)::int AS host, random() * 100 AS cpu_usage, 40 + random() * 50 AS mem_usage, random() * 1000 AS disk_io, (random() * 1000000)::bigint AS net_in, (random() * 500000)::bigint AS net_out;

-- 找出 CPU 使用率超過 90% 的時段 SELECT time_bucket('5 minutes', time) AS period, host, AVG(cpu_usage) AS avg_cpu, MAX(cpu_usage) AS peak_cpu FROM server_metrics WHERE time > NOW() - INTERVAL '24 hours' GROUP BY period, host HAVING AVG(cpu_usage) > 90 ORDER BY avg_cpu DESC;

-- 每小時的流量統計 SELECT time_bucket('1 hour', time) AS hour, host, SUM(net_in) / 1024 / 1024 AS total_in_mb, SUM(net_out) / 1024 / 1024 AS total_out_mb FROM server_metrics WHERE time > NOW() - INTERVAL '7 days' GROUP BY hour, host ORDER BY hour DESC;


小結

時序資料庫不是什麼新奇的東西,但它確實解決了傳統資料庫在面對高頻寫入和時間範圍查詢時的痛點。TimescaleDB 和 InfluxDB 各有優勢,選擇的關鍵在於你的團隊背景和實際需求。

如果你是 PostgreSQL 使用者,我強烈建議先試試 TimescaleDB,幾乎零學習成本就能獲得極大的效能提升。如果你的場景是純粹的 IoT 或監控資料,InfluxDB 搭配 Grafana 的組合也是非常成熟的方案。

延伸閱讀