概要
時系列データベース(Time Series Database、TSDB)とは、時刻とセットになったデータ(時系列データ)の保存・検索・分析に特化したデータベースシステムです。センサーが一定間隔で記録する温度・圧力・振動などの測定値、株価や為替のティックデータ、サーバーのCPU/メモリ使用率など、「いつ・何の値だったか」を大量かつ高頻度に記録するワークロードに最適化されています。
一般的なRDB(リレーショナルデータベース)でもタイムスタンプ付きのデータは扱えますが、以下の点で時系列DBが圧倒的に有利です。
- 書き込み性能: 1秒あたり数百万件の時系列データを効率的に追記
- 圧縮率: 時系列データの特性(連続値、差分が小さい)を活かした高圧縮
- 時系列クエリ: 時間範囲指定、ダウンサンプリング、移動平均などを簡潔に記述
- 自動データ保持管理: データ保持期間(Retention Policy)の設定で古いデータを自動削除
IoTシステムでは、数千台のセンサーが毎秒データを送信するため、1日で数十億件のレコードが生成されることも珍しくありません。時系列DBはこの規模のデータを効率的に管理し、任意の時間範囲でのグラフ表示や異常検知クエリを高速に処理します。
歴史・背景
時系列データの保存は古くから必要とされており、1990年代には株式市場向けの「RRDtool」がグラフ生成と組み合わせた時系列データ保存ツールとして登場しました。しかしIoT時代以前は、一般的なRDBかRRDtoolで事足りるデータ量でした。
2010年代に入りIoTとクラウドモニタリングの普及で時系列データの量が爆発的に増大。2013年にOpenTSDB(HBaseベース)、2014年にInfluxDB(GoおよびRustで実装、スタンドアロンTSDB)が登場し、専用時系列DBの市場が生まれました。2019年には既存のPostgreSQLをTSDB拡張する「TimescaleDB」が普及し、RDBとTSDBの融合という方向性も示されました。
クラウドベンダーも時系列DBをマネージドサービスとして提供するようになり、2018年にAWS Timestream(プレビュー)、2020年にGAとなりました。Azure Time Series Insightsも同時期に強化されています。
産業IoT分野では、OPC-UAとの親和性が高い「Apache IoTDB」が中国を中心に普及しています。
技術仕様
主要な時系列DBの比較
| DB | 実装言語 | ライセンス | 特徴 |
|---|---|---|---|
| InfluxDB | Go/Rust | BSL(OSSコア) | 最もポピュラー、InfluxQL/Flux対応 |
| TimescaleDB | C(PostgreSQL拡張) | Apache 2.0 | SQLが使える、PostgreSQL互換 |
| Prometheus | Go | Apache 2.0 | Pull型収集、Kubernetes向け |
| Victoria Metrics | Go | Apache 2.0 | 高パフォーマンス、PromQL互換 |
| Amazon Timestream | マネージド | AWS独自 | AWSネイティブ統合 |
| Azure Time Series Insights | マネージド | Azure独自 | PaaS、Azureネイティブ |
| QuestDB | Java/C++ | Apache 2.0 | 超高速(PostgreSQLプロトコル互換) |
| Apache IoTDB | Java | Apache 2.0 | 産業IoT、OPC-UA連携 |
| OpenTSDB | Java | LGPL | HBase上に構築、大規模向け |
InfluxDB のデータモデル
Measurement(テーブルに相当)
└── Tag Set(インデックス付きメタデータ)
└── Field Set(実際の測定値、インデックスなし)
└── Timestamp(ナノ秒精度)
例:
measurement: "factory_sensor"
tags: device_id="cnc-001", location="tokyo", line="A"
fields: temperature=25.3, vibration=0.42, rpm=3000
timestamp: 1700000000000000000 (Unix nanoseconds)
InfluxDB Line Protocol(書き込みフォーマット):
# 書式: measurement,tag_key=tag_value field_key=field_value timestamp
factory_sensor,device_id=cnc-001,location=tokyo temperature=25.3,vibration=0.42,rpm=3000i 1700000000000000000
factory_sensor,device_id=cnc-002,location=osaka temperature=24.1,vibration=0.31,rpm=2800i 1700000000000000000
データ圧縮の仕組み
時系列DBが高い圧縮率を実現する主な手法:
| 圧縮手法 | 説明 | 適用例 |
|---|---|---|
| Delta of Delta | 差分の差分を符号化 | タイムスタンプ(規則的な増加) |
| Gorilla encoding | IEEE 754浮動小数点のXOR + 可変長ビット | センサー値(緩やかな変化) |
| Zigzag encoding | 符号付き整数を符号なしに変換 | 正負混在の差分値 |
| Run Length Encoding | 同じ値の連続を圧縮 | 固定値(設定変更がない状態) |
| 辞書圧縮 | 繰り返し文字列を辞書化 | タグ値(device_idなど) |
典型的なIoTセンサーデータでは、これらの組み合わせにより非圧縮の10〜50分の1のサイズに圧縮できます。
動作原理
書き込みパスとLSM-Tree
InfluxDBなど多くのTSDBは、LSM-Tree(Log-Structured Merge-Tree)アーキテクチャを採用しています。
書き込みパス:
データ受信
↓
WAL(Write-Ahead Log)に追記
↓
メモリキャッシュ(MemTable)に保持
↓ [定期的フラッシュ]
TSMファイル(Time-Structured Merge Tree)
→ ディスク上で圧縮・マージ(Compaction)
→ 古いデータほど大きなファイルに統合
読み取りパス:
クエリ受信
↓
メモリキャッシュ + TSMファイルを並列読み取り
↓
マージ・フィルタリング
↓
結果返却
Flux クエリ言語の例(InfluxDB 2.x)
# InfluxDBのFlux言語: 過去1時間の各デバイスの平均温度
from(bucket: "factory_data")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "factory_sensor" and r._field == "temperature")
|> group(columns: ["device_id"])
|> mean()
|> yield(name: "average_temperature")
# 1分ごとのダウンサンプリング(大量データのグラフ表示向け)
from(bucket: "factory_data")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "factory_sensor" and r._field == "vibration")
|> aggregateWindow(every: 1m, fn: mean, createEmpty: false)
|> yield(name: "downsampled_vibration")
# 異常検知: 移動平均から3σを超えるデータを検出
data = from(bucket: "factory_data")
|> range(start: -6h)
|> filter(fn: (r) => r._measurement == "factory_sensor" and r._field == "vibration")
mean = data |> mean()
stddev = data |> stddev()
data
|> map(fn: (r) => ({r with zscore: (r._value - mean._value) / stddev._value}))
|> filter(fn: (r) => math.abs(x: r.zscore) > 3.0)
|> yield(name: "anomalies")
IoTデバイスからInfluxDBへのデータ送信
# MQTTからInfluxDBへのブリッジ(Python)
import paho.mqtt.client as mqtt
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
import json
import time
INFLUXDB_URL = "http://localhost:8086"
INFLUXDB_TOKEN = "my-auth-token"
INFLUXDB_ORG = "my-org"
INFLUXDB_BUCKET = "factory_data"
influx_client = InfluxDBClient(url=INFLUXDB_URL, token=INFLUXDB_TOKEN, org=INFLUXDB_ORG)
write_api = influx_client.write_api(write_options=SYNCHRONOUS)
def on_mqtt_message(client, userdata, message):
"""MQTTメッセージを受信してInfluxDBに書き込む"""
try:
payload = json.loads(message.payload)
# トピックからdevice_idを抽出 (例: factory/cnc-001/telemetry)
parts = message.topic.split('/')
device_id = parts[1] if len(parts) > 1 else "unknown"
# InfluxDB Pointを構築
point = (Point("factory_sensor")
.tag("device_id", device_id)
.tag("location", payload.get("location", "unknown"))
.field("temperature", float(payload.get("temperature", 0)))
.field("vibration", float(payload.get("vibration", 0)))
.field("rpm", int(payload.get("rpm", 0)))
.time(payload.get("timestamp", int(time.time_ns()))))
write_api.write(bucket=INFLUXDB_BUCKET, record=point)
except Exception as e:
print(f"InfluxDB書き込みエラー: {e}")
mqtt_client = mqtt.Client()
mqtt_client.on_message = on_mqtt_message
mqtt_client.connect("mqtt-broker", 1883)
mqtt_client.subscribe("factory/+/telemetry")
mqtt_client.loop_forever()
用途・ユースケース
IoTセンサーデータの保存と可視化
工場の数百台のセンサーから毎秒データを収集し、InfluxDB + Grafanaのスタックでリアルタイムに可視化します。異常値の検出アラートもGrafanaのAlertingで設定可能です。
インフラモニタリング(DevOps)
サーバー・コンテナのCPU/メモリ/ネットワーク使用率を時系列DBで管理。Prometheus + Grafanaが標準的なスタックとして普及しています。
エネルギー管理
スマートメーターや太陽光パネルの発電量データを時系列DBで長期保存。月次・年次の消費トレンド分析や、天気との相関分析に活用します。
金融・トレーディング
株価・FXのティックデータを時系列DBで管理。ミリ秒以下の精度での時刻管理とバックテストが重要です。QuestDBのような超高性能TSDBが使われます。
-- TimescaleDB(PostgreSQL拡張)を使ったIoTデータ管理
-- PostgreSQLの構文が使えるため、既存のSQLスキルが活用できる
-- 時系列テーブルの作成
CREATE TABLE sensor_data (
time TIMESTAMPTZ NOT NULL,
device_id TEXT NOT NULL,
temperature DOUBLE PRECISION,
vibration DOUBLE PRECISION,
rpm INTEGER
);
-- TimescaleDBのハイパーテーブルに変換(時系列最適化)
SELECT create_hypertable('sensor_data', 'time');
-- データ保持ポリシー(30日以上前のデータを自動削除)
SELECT add_retention_policy('sensor_data', INTERVAL '30 days');
-- 連続集計(バックグラウンドで1分ごとの集計を維持)
CREATE MATERIALIZED VIEW sensor_1min
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 minute', time) AS bucket,
device_id,
avg(temperature) AS avg_temp,
max(vibration) AS max_vibration
FROM sensor_data
GROUP BY bucket, device_id;
-- 効率的な時間範囲クエリ
SELECT time, device_id, temperature
FROM sensor_data
WHERE time > NOW() - INTERVAL '1 hour'
AND device_id = 'cnc-001'
AND temperature > 80
ORDER BY time DESC;
実装・開発のポイント
データ設計のベストプラクティス
InfluxDBのタグとフィールドの使い分け:
タグ(Tags)← インデックス化される → クエリのWHERE条件に使う
✓ device_id、location、sensor_type
✗ 温度値、振動値(高カーディナリティになりメモリを大量消費)
フィールド(Fields)← インデックスなし → 実際の測定値
✓ temperature, vibration, rpm
✗ device_id(フィールドにするとインデックスなしでクエリが遅い)
データ保持戦略(階層的保存)
生データ(1秒間隔) → 保持期間: 7日
1分ダウンサンプル → 保持期間: 90日
1時間ダウンサンプル → 保持期間: 2年
1日ダウンサンプル → 保持期間: 10年
# InfluxDB: 自動ダウンサンプリングタスク設定
task_script = """
option task = {
name: "downsample_1min",
every: 1m,
}
from(bucket: "raw_data")
|> range(start: -task.every)
|> filter(fn: (r) => r._measurement == "factory_sensor")
|> aggregateWindow(every: 1m, fn: mean)
|> to(bucket: "downsampled_1min", org: "my-org")
"""
他技術との比較
| 項目 | InfluxDB | TimescaleDB | Amazon Timestream | RDB(PostgreSQL) |
|---|---|---|---|---|
| 時系列特化 | 完全特化 | 拡張で対応 | 完全特化 | 汎用 |
| SQLサポート | 独自言語(Flux)、InfluxQL | フルSQL | 独自SQL | フルSQL |
| スケーラビリティ | 高 | 高 | 非常に高(マネージド) | 中 |
| 圧縮率 | 非常に高 | 高 | 高 | 低 |
| 運用負担 | 中 | 中(PG管理) | 低(マネージド) | 高 |
| 既存スキル活用 | 要学習 | SQL使える | 要学習 | そのまま |
| ライセンス | BSL(制限あり) | Apache 2.0 | 有料 | PostgreSQLライセンス |
時系列データベースはIoTプラットフォームから収集されたセンサーデータの永続化に不可欠であり、ダッシュボードツール(Grafanaなど)のデータソースとして機能します。エッジコンピューティング環境では、軽量な時系列DB(InfluxDB OSS、QuestDB)をエッジノード上で動かすことで、クラウドへの通信量を削減しつつリアルタイム分析を実現します。