限界点:SQLが壁にぶつかる時
昨年、私たちのIoTプラットフォームは、どれだけインデックスを調整しても解決できないパフォーマンスの壁に直面しました。私たちは1,200台の産業用センサーを管理しており、それぞれが温度、湿度、振動のメトリクスを毎秒出力していました。これは毎分72,000回の書き込み、つまり毎日1億行以上の新規データが生成されることを意味します。当初は、16GBのRAMを搭載したクラウドサーバー上の標準的なPostgreSQLインスタンスにすべてを投入していました。最初の1ヶ月は持ちこたえましたが、その後、破綻をきたしました。
これまで瞬時に表示されていたダッシュボードが突然20秒間フリーズするようになりました。過去1時間の平均振動ピークを求めるといった単純な計算でも、データベースのCPU使用率が100%に張り付き、他のユーザーが実質的に締め出される状態になりました。リレーショナルデータにおいてPostgreSQLは素晴らしいツールですが、私たちは「高級セダンで50トンの砂利を運ぼうとしていた」ことに気づいたのです。私たちに必要だったのは、時系列データの取り込み(インジェクション)に特化した、産業用の専門ツールでした。
なぜ標準的なデータベースでは限界があるのか
MySQLやPostgreSQLが「遅い」わけではありません。問題はその設計(DNA)にあります。伝統的なデータベースはインデックスにB-Treeを使用します。これは特定のユーザーIDを検索するには最適ですが、高頻度のIoT書き込みでは深刻なインデックスの断片化を引き起こします。センサーからのデータが届くたびに、データベースはディスク上のインデックスを再ソートして更新しなければならず、結果としてIOPS(秒あたりの入出力操作数)のデッドスパイラルに陥るのです。
データ保持(リテンション)もまた、静かなキラーとなります。IoTの世界では、2年前のデータに対してミリ秒単位の精度が必要になることは稀です。しかし、1億行を超えるSQLテーブルに対してDELETEコマンドを実行することは、実質的に「自爆型のDDoS攻撃」をしているようなものです。テーブルがロックされ、トランザクションログが肥大化し、基盤となるファイルの構造上、実際にはディスク容量が解放されないことも少なくありません。
用途に合った適切なツールの選定
移行に踏み切る前に、3つの異なるアーキテクチャをベンチマークしました:
- TimescaleDB: PostgreSQLの強力な拡張機能です。メトリクスが複雑なリレーショナルテーブルと深く結びついている場合には最適な選択肢です。しかし、純粋な時系列エンジンと比較すると、ストレージ使用量は依然として高めです。
- MongoDB: 柔軟なスキーマには適していますが、時系列データに対してはメモリ消費が激しいのが難点です。微分や移動平均などのネイティブ関数が不足しているため、アプリケーションコード側で重い処理を行う必要があります。
- InfluxDB: これが私たちの選んだ勝者です。Time-Structured Merge Tree (TSM) エンジンを採用しており、高速な書き込みと強力なデータ圧縮のために専用設計されています。
InfluxDBを選んだ理由は、「Telegraf」エージェントがデータ取り込みのパイプラインを標準機能で処理できたこと、そして圧縮機能によって毎月のストレージコストを大幅に削減できる見込みがあったからです。
基盤のセットアップ
Dockerを使えば、本番環境レベルのインスタンスを数分で立ち上げることができます。UI、ストレージエンジン、タスクスケジューラが1つのコンテナにまとめられたInfluxDB 2.7のイメージをデプロイしました。
docker run -d -p 8086:8086 \
--name influxdb_production \
-v /var/lib/influxdb2:/var/lib/influxdb2 \
influxdb:2.7
http://localhost:8086にアクセスした後、Organization(組織)とBucket(バケット)を設定します。InfluxDBにおいてバケットは単なるデータベースではなく、「有効期限が組み込まれたコンテナ」です。データの自動削除(パージ)までの保存期間を、初日に決定します。
Telegraf:安定稼働の秘訣
データをプッシュするために独自のPythonやNode.jsスクリプトを書くことに時間を費やさないでください。私たちは、ネットワーキングの「厄介な」部分を処理してくれる軽量エージェント、Telegrafを使用しました。ポイントをまとめてバッチ処理し、データベースがビジーな場合はリトライを行い、MQTTストリームをネイティブにパースします。
以下は、MQTTブローカーをInfluxDBにブリッジするために使用した設定のスニペットです:
[[outputs.influxdb_v2]]
urls = ["http://influxdb:8086"]
token = "${INFLUX_TOKEN}"
organization = "industrial-iot"
bucket = "raw_sensor_data"
[[inputs.mqtt_consumer]]
servers = ["tcp://broker.internal:1883"]
topics = ["factory/+/metrics"]
data_format = "json"
このアプローチにより、ファームウェアチームからエラー処理の負担がなくなりました。メンテナンスのためにデータベースがダウンしても、Telegrafは接続が復旧するまでデータをメモリにバッファリングしてくれます。
Fluxクエリ言語:パワーと苦労
InfluxDB 2.xでは、関数型クエリ言語であるFluxを使用します。SELECT * FROMに慣れている人にとって、Fluxはコードを逆向きに書くのを学ぶような感覚かもしれません。パイプ(|>)を使用して、フィルターや変換にデータを渡していきます。最初は独特ですが、分析においては驚くほど強力です。
特定のセンサーの5分間の移動平均を計算する場合、コードは以下のようになります:
from(bucket: "raw_sensor_data")
|> range(start: -1h)
|> filter(fn: (r) => r["_measurement"] == "vibration")
|> filter(fn: (r) => r["machine_id"] == "CNC-04")
|> aggregateWindow(every: 5m, fn: mean)
|> yield(name: "smooth_average")
これをSQLで行うには、複雑なウィンドウ関数やサブクエリが必要です。Fluxでは、可読性の高い線形なパイプラインとして記述できます。
ダウンサンプリング:ディスク容量を85%削減した方法
本当の魔法は、データのライフサイクルを自動化したときに起こりました。3ヶ月前の「秒単位」の振動データは必要ありません。必要なのは傾向(トレンド)だけです。そこで、データを異なる層に「ダウンサンプリング」するタスクを設定しました。
- 高解像度バケット: 秒単位のデータ。7日後に自動削除。
- アーカイブバケット: 15分ごとの平均値。2年間保持。
生データは1日あたり約40GBのディスク容量を消費していました。この15分のダウンサンプリングタスクを導入した後、長期保存用データの増加は週にわずか150MBまで抑えられました。これは、ビジネスチームが必要とする過去のインサイトを失うことなく、インフラのオーバーヘッドを劇的に削減できたことを意味します。
6ヶ月後の結論
専用の時系列データベースへの移行は、私たちのシステムの信頼性における転換点となりました。クエリの応答時間は数秒という苦痛なレベルから、100ミリ秒未満へと短縮されました。さらに重要なのは、センサーを追加してもデータベースのクラッシュを心配する必要がなくなったことです。もしあなたのプロジェクトがログ、金融データ、またはIoTメトリクスを扱うなら、それらをリレーショナルモデルに無理やり押し込めるのはやめましょう。負荷を処理するために設計されたツールを使ってください。

