QuestDB:毎秒400万レコードを処理し、深夜のデータベースダウンの恐怖に終止符を打つ

Database tutorial - IT technology blog
Database tutorial - IT technology blog

午前2時の救世主:PostgreSQLとMySQLがIoTデータに「降参」した時

午前2時に鳴り響く電話のバイブレーションは、すべての運用エンジニアにとって悪夢です。Grafanaのダッシュボードは真っ白になり、サーバーのCPUは100%に達し、IOPSは限界を突破. 監視システムは完全に麻痺してしまいます。

私はかつて、工場のセンサー監視プロジェクトでこの苦い経験をしました。当初、チームは使い慣れているという理由でPostgreSQLを選びました。しかし、規模が5,000センサーに拡大し、毎秒10万件以上のレコードが送られてくるようになると、PostgreSQLは息切れし始めました。インデックスは数百GBに膨れ上がり、INSERT文はボトルネックとなり、期間指定(time-range)のクエリはレスポンスに1分以上かかるようになりました。

実際、MySQLやMongoDBは一般的な業務には素晴らしいツールです。しかし、毎秒数百万行の書き込み速度が求められるタイムシリーズ(時系列)データに関しては、QuestDBの敵ではありません。QuestDBは極限まで最適化されています。カラムナ(列指向)ストレージ、CPUのSIMD命令セットの活用、そして純粋なSQLのサポートがその特徴です。

クイックスタート:5分でQuestDBを起動する

煩雑な設定手順に時間を費やす必要はありません。最も速い導入方法はDockerを使用することです。QuestDBは非常に軽量で、大量の依存ライブラリを引き連れることもありません。

docker run -p 9000:9000 -p 8812:8812 -p 9009:9009 -p 9003:9003 \
  questdb/questdb

注意すべきポートは以下の通りです:

  • 9000: Web Console。localhost:9000 にアクセスして、直感的なインターフェースでデータベースを管理できます。
  • 8812: Postgres Wire Protocol。このポートを使用すると、PostgreSQLのライブラリをそのまま使用して接続できます。
  • 9009: InfluxDB Line Protocol。これはデータを最大速度で投入するための「高速道路」です。

なぜ QuestDB は理不尽なほど速いのか?

違いはストレージの仕組みにあります。MySQLのような従来のデータベースは行ベース(row-based)でデータを保存します。10億件のレコードから平均気温を算出する場合、Postgresはsensor_idlocationといった無関係な列まで全てスキャンしなければなりません。これはリソースの甚大な浪費です。

QuestDBは異なるアプローチを取ります。それはカラムナ(列指向)保存です。気温を計算する必要があるとき、システムはディスク上のその特定の列だけを読み取ります。時間によるパーティショニング(Time-partitioning)メカニズムと組み合わせることで、QuestDBは不要なデータ領域を完全に排除します。

1億行のデータを用いた実地テストでは、QuestDBでのAVG計算はわずか数百ミリ秒で完了しました。これは、複雑なチューニングなしでPostgreSQLよりも15倍高速な数値です。

データの投入:InfluxDB Line Protocol (ILP) を優先する

QuestDBはSQLのINSERTをサポートしていますが、それはIoTにとって最適な方法ではありません。最高のパフォーマンスを得るには、InfluxDB Line Protocol (ILP)を使用すべきです。このプロトコルは、定期的にディスクにフラッシュする前に、データを直接RAMに投入します。

以下のPythonコードは、ソケットを介してセンサーデータを送信するシミュレーションです:

import socket
import time

HOST, PORT = 'localhost', 9009

def send_sensor_data():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        while True:
            # 構造: table_name,tags fields timestamp
            data = f"sensors,sensor_id=S001 temperature=25.5,humidity=60.2 {time.time_ns()}\n"
            s.sendall(data.encode())
            time.sleep(0.01) # 毎秒100件のレコードを送信

send_sensor_data()

大きなメリットは、QuestDBがスキーマレス(Schema-less)をサポートしている点です。最初にデータを受信した瞬間に自動的にテーブルを作成し、新しい列を追加するため、DB設計の時間を節約できます。

拡張SQLによるインテリジェントなクエリ

QuestDBはFluxのような新しい言語を学ぶ必要はありません。標準的なSQLを使用しながら、タイムシリーズ(時系列)データに「真価を発揮する」キーワードを追加しています。

1. SAMPLE BY:超高速な時間グルーピング

GROUP BYでの複雑な日時処理関数は忘れてください。QuestDBなら、たった1行のコマンドで済みます。

SELECT timestamp, avg(temperature)
FROM sensors
SAMPLE BY 1h; -- 1時間ごとの平均を自動的にグルーピング

2. LATEST BY:最新の状態を即座に取得

全センサーの現在の状態を知るために、従来のSQLでは非常に重いJOINMAX(timestamp)が必要でした。QuestDBはこれを一瞬で処理します。

SELECT * FROM sensors LATEST BY sensor_id;

3. ASOF JOIN:ズレのあるデータの結合

これは金融やIoTにおいて非常に強力な機能です。pricesテーブルとtradesテーブルがミリ秒単位で正確に一致しない場合でも、ASOF JOINは自動的に直前の最も近い値を取得してマッチングさせます。

実地運用の教訓

強力なQuestDBですが、システムハングを避けるために守るべき独自のルールがあります:

  • パーティショニングの使用は必須: テーブル作成時には必ず PARTITION BY DAY または MONTH を宣言してください。これにより、古いデータの削除(保持期間管理)がテーブルロックを発生させずに即座に行えます。
  • 順序外(Out-of-order)データの管理: QuestDBはデータが時系列順に送られてくる時に最高のパフォーマンスを発揮します。データの遅延(ラグ)が大きい場合は、バッファを最適化するために cairo.max.uncommitted.rows パラメータを調整してください。
  • ハードウェア: SSDまたはNVMeへの投資を惜しまないでください。QuestDBはシーケンシャルな書き込みを多用するため、ディスク速度がスループットを決定する鍵となります。
  • SELECT * の制限: 数十億行のテーブルに対して、Web Consoleで LIMIT を付けずに SELECT * を実行すると、表示データの過負荷によりブラウザがフリーズします。

まとめ

QuestDBは、厳格なACID特性が必要な金融取引などの用途でPostgreSQLを完全に置き換えるために作られたわけではありません。しかし、インフラ監視や数百万台のIoTデバイスのトラッキングに苦労しているなら、これが救世主となります。 インデックスの最適化に夜を徹する代わりに、その重荷をQuestDBに任せて、ぐっすり眠りましょう。

Share: