MySQLやPostgreSQLが数十億件のレコードを前に「限界」を迎えるとき
2年前、私は大手ECサイトのトラッキングプロジェクトで大きな壁にぶつかりました。要件は非常に厳しく、システムは1日あたり5億件のイベントを処理する必要がありました。ビジネス部門からは、ダッシュボードのレポートを1秒未満の遅延で、ほぼリアルタイムに表示したいという要求がありました。
当初、チームは信頼性の高いPostgreSQLを使用していました。データが1億件未満のときはすべて順調でした。しかし、ログテーブルが10億件の大台に乗ると、COUNT(DISTINCT user_id) のようなクエリは悪夢へと変わりました。インデックスを貼り、パーティションを分割しても、ダッシュボードをロードするたびにコーヒーを淹れて戻ってきてもまだ終わらない(30〜40秒待たされる)という状態でした。顧客からは不満が噴出し、上司も落ち着かない様子でした。
なぜ従来のデータベースは遅いのか?
連晩のデバッグの末、MySQLやPostgresのようなOLTPデータベースがビッグデータを処理する際に直面する3つの大きな障壁に気づきました。
- 行指向ストレージ(Row-based): 1つのカラムの合計売上を計算するために、ディスクから他のすべてのカラムも読み込む必要があります。これは凄まじいI/Oの無駄を引き起こします。
- インデックスのオーバーロード: 数十億行にもなると、インデックスのサイズがRAM容量を超えてしまいます。インデックスを常に更新し続ける必要があるため、書き込み速度(Ingestion)が大幅に低下します。
- シングルスレッド処理: 複雑なクエリが、サーバークラスター内のすべてのCPUコア의パワーを十分に活用できないことがよくあります。
チームが検討した代替案
Druidに決める前に、いくつかのアプローチを試しましたが、それぞれに欠点がありました。
- 事前集計(Pre-aggregation): Cronジョブを使用して1時間ごとにデータを集計する方法です。これは高速ですが、リアルタイム性が失われます。5分前の数値を見たいという要望には応えられませんでした。
- Elasticsearch: テキスト検索は非常に高速です。しかし、大規模なデータセットでの集計(Aggregation)を行うと、メモリを驚くほど消費し、時には100%正確ではない結果を返すことがありました。
- ClickHouse: パフォーマンスは非常に優れています。しかし、当時のClickHouseのKafkaとの直接連携機能は、Druidほどスムーズで安定していませんでした。
Apache Druid – リアルタイム分析のための「武器」
Apache Druidは単なるデータベースではありません。データウェアハウス、時系列データベース、そして検索インデックスを組み合わせたような存在です。Druidは列指向(Columnar)でデータを保存し、データ取り込み時に自動的にインデックスを作成します。Kafkaから毎秒数百万件のイベントを「飲み込む」能力は、最大の魅力です。
Druid의賢いメカニズムは、データを時間ごとにセグメント(Segments)として分割することにあります。今日のデータをクエリする場合、関連するセグメントのみをスキャンします。古いデータは完全にスキップされるため、サブセコンド(1秒未満)の速度を容易に達成できます。
クイックスタートガイド(Micro-Quickstart)
個人のPCや4GB程度のRAMを搭載したVPSで試すなら、Micro-Quickstart版が最適です。
ステップ1:環境の準備
DruidはJava 11で最適に動作します。以下のコマンドで環境を確認してください:
java -version
ステップ2:ダウンロードと解凍
Apacheのミラーサイトから最新の配布版(例:28.0.1)をダウンロードします:
wget https://dlcdn.apache.org/druid/28.0.1/apache-druid-28.0.1-bin.tar.gz
tar -xzf apache-druid-28.0.1-bin.tar.gz
cd apache-druid-28.0.1
ステップ3:起動
ルートディレクトリで、サービスを起動するためのスクリプトを実行します:
./bin/start-micro-quickstart
CoordinatorやBrokerなどのコンポーネントが起動するまで1分ほど待ちます。その後、UI(http://localhost:8888)にアクセスします。
データの取り込みと実際のクエリ
Druidでは通常の INSERT INTO は使わず、JSON形式の「Ingestion Spec」を使用します。心配はいりません、コンソール画面が手厚くサポートしてくれます。
サンプルデータのロード
シンプルな構造の events.json ファイルがあると仮定します:
{"time": "2023-10-27T10:00:00Z", "user_id": "u1", "event_type": "click", "value": 10}
Druidコンソール上での操作:
- Load data -> Local disk に進み、ファイルのパスを入力します。
- Parse data ステップで、
timeカラムを Primary Timestamp として選択します。これはDruidがデータを効率的に断片化するために不可欠な設定です。 - Submit を押します。タスクが
SUCCESSと表示されれば、データの準備は完了です。
SQLによるクエリ
Druidは標準SQLをサポートしているため、すぐに慣れることができます。このコマンドを試してみてください:
SELECT
event_type,
COUNT(*) as total_events
FROM "your_datasource"
WHERE __time >= CURRENT_TIMESTAMP - INTERVAL '1' DAY
GROUP BY 1
各カラムのビットマップインデックス技術により、結果は瞬時に返ってきます。
運用における「血の滲むような」教訓
Druidを本番環境で運用した結果、3つの重要な注意点にたどり着きました:
- ロールアップ(Rollup)は救世主: ユーザーごとの詳細を追跡する必要がない場合は、ロールアップを有効にしてください。タイムスタンプとディメンションが一致する行を自動的に集計します。私のプロジェクトでは、ロールアップによりストレージ容量が100GBから15GBに削減されました。
- 高カーディナリティ(High Cardinality)を避ける:
session_idのようにユニークな値が多すぎるカラムをディメンションに含めないでください。ロールアップの効果が失われ、クエリが著しく遅くなります。 - RAMの設定: Druidは
Direct Memoryを非常に多く消費します。-XX:MaxDirectMemorySizeパラメータを慎重に設定してください。そうしないと、重いクエリが走ったときにシステムがクラッシュします。
最終的な結果はどうなったでしょうか?チームのダッシュボードは40秒から500ms未満に短縮されました。上司は満足し、DevOpsチームもインデックスの最適化のために夜更かしする必要がなくなり、胸をなでおろしました。リアルタイム性が求められる膨大なデータの問題に直面しているなら、Apache Druidは投資する価値のあるツールです。

