概要
Cloudflareは1秒あたり10億件以上のイベントを処理しています。ネットワークは120以上の国の330+都市にまたがります。各HTTPリクエスト、各Workerの呼び出し、各R2の読み取り操作の裏には大量のデータがあります。しかし何年もの間、そのデータにアクセスするのは簡単ではありませんでした。データは何十もの本番データベース、ClickHouseクラスタ、Kafkaストリーム、Google Cloudバケット、BigQueryデータセット、そして長い尾を持つパイプライン群に分散していました。
たとえば「今日サインアップしたドメインのうち、トラフィックでTop100に入っているのは何件か?」という単純な問いに対しても、Cloudflareのアナリストはどのシステムに聞けばよいか、どの資格情報を使うか、どのクエリ言語を書くか、そして対象データがサンプリングされているのか新鮮なのか7日分遅延しているのかを知る必要がありました。その結果、データから十分に情報に基づいた洞察を得るのは難しかったのです。
この問題を解決するために、社内で2つのツールを構築しました:Town Lake(Cloudflareの統一データ分析プラットフォーム)と、Town Lakeの上で動くAIデータエージェントSkipperです。Town LakeはCloudflareが知るすべてに対する単一のSQLインターフェースであり、SkipperはCloudflare内の誰もが自然言語で質問して、秒単位で正確かつ監査可能な回答を得られる仕組みです。以下は、それらをどのように作ったかの物語です。
問題の形
ハイパーグロースを経験した企業で働いたことがあれば、データのスプロール(散在)がどのようなものかご存知だと思います。Cloudflareの事例は次のような具体的な症状を伴っていました。
- 多すぎる分散システム
- プロダクトエンジニアが顧客問題を調査する際、アカウントメタデータのためにPostgres、解析イベントのためにClickHouse、使用量の集計のためにBigQuery、rawログのためにR2、リアルタイム信号のためにKafkaトピックをクエリする必要があることがありました。各システムは固有の資格情報、固有の言語、固有の保持ポリシーを持っていました。
- サンプリングされたデータ
- ダッシュボードには問題ありませんが、請求のような領域では使えません。解析パイプラインは700M+ events per secondを扱うためにダウンサンプリングしています。解析ダッシュボードを高速に読み込ませるためには正しい振る舞いですが、請求発行に必要な使用量を計算するときには全く逆です。
- 内部データの外部依存
- 以前の内部レポーティングスタックの一部は外部ベンダーに依存していました。コストに加えて、重要なデータの一部について別のクラウドへの強い外部依存がありました。
- 誰もデータを見つけられない
- 全ての適切な資格情報があったとしても、"Billable Workers requests by account" に該当する正しいテーブルが特定のClickHouseクラスタの特定スキーマにあり、それが特定のPostgresディメンションテーブルと結合され、さらに結合にあいまいな顧客ID変換が必要だと知っていなければなりませんでした。部族的知識が多すぎました。
文化的な課題もありました:データインフラは長らくビジネスのための裏方機能として扱われ、独立した重要なインフラとしては認識されていませんでした。
私たちの目標
社内の適切な権限を持ち、知る必要がある誰もがCloudflareに関する質問に答えを得られる単一の場所を作りたかったです。例:
- 「前四半期の収益で上位100顧客を表示して」
- 「直近48時間でスコア>0.9のBot Management MLスコアリングイベントを特定のASNから来たものに限って列挙して」
- 「$100以上使った顧客からのTop100の請求サポートチケットを見つけて」
その場所は、請求やセキュリティ調査のように生データが必要なクエリに対しては新鮮で正確な非サンプリングデータを提供し、ダッシュボードや探索のように高速なダウンサンプリング済みデータで十分なクエリには高速に応答する必要がありました。
さらに、セキュリティとガバナンスを組み込み、PII(個人を特定できる情報)を自動検出し、センシティブなテーブルはデフォルトでロックされるようにしたいと考えました。全てのアクセスは監査可能であるべきで、時間限定のパーミッション付与により、ユーザーは必要な作業中のみデータにアクセスできるようにします。
基盤はCloudflare自身のプロダクト上に構築したいと考えました:R2をストレージに、Workersをコンピュートに、Cloudflare Accessを認証に、Workflowsをオーケストレーションに。データ基盤に大きな投資をするなら、私たちが顧客に販売する同じ製品群上に構築するべきだと考えたからです。
最後に、最終的にはSQLを知らなくても使えるインターフェースを実現したいと考えました。これがSkipperになりました。
Town Lake(プラットフォーム)
コアとして、私たちのデータプラットフォームのアーキテクチャはデータレイクハウスです:オブジェクトストレージを読み取るクエリエンジンと、ストレージをデータベースのように振る舞わせるメタデータレイヤーを組み合わせたもの。オースティンの地名にちなみTown Lakeと名付けました。主要コンポーネントは以下の通りです。
- クエリエンジン
- Apache Trinoを採用しました:単一のSQLクエリでPostgresテーブル、ClickHouseテーブル、R2上のIcebergテーブルを中間結果を別システムにマテリアライズすることなく結合できます。例えば「今週のWorkersリクエストで上位100の支払顧客は誰か」というクエリは、フィルタをClickHouseにプッシュし、アカウント次元をPostgresと結合し、R2の請求ロールアップでランク付けするといった処理計画にコンパイルされ、一度に実行されます。
- R2 Data Catalog(管理されたApache Icebergサービス)
- コールドおよびウォームデータはこちらに格納されます。Icebergはスキーマ進化、タイムトラベル、パーティション進化、データの老化に伴うコンパクションを提供します。先週の分は分単位→時間単位、前四半期分は時間単位→日単位というようにマテリアライズの解像度を下げていきます。新しさが失われるほどストレージコストは下がりますが、データはクエリ可能なままです。ParquetファイルをR2に置くコストは、同じデータをOLAPデータベースに置き続けるよりずっと安価です。
- DataHub(メタデータカタログ)
- すべてのテーブル、カラム、オーナー、ラインエッジ、用語集がここにあります。ユーザーが "what's in townlake.dim.accounts" を尋ねると、DataHubはテーブル説明、カラム説明、オーナーチーム、それにデータを供給する上流テーブルや消費する下流テーブルを返します。
- Lifeguard(アクセス制御サービス)
- アクセスルールをD1に保存し、内部のアクセス管理システムからユーザーおよびグループメンバーシップを動的に引き取り、TrinoがHTTP経由で読み取る結合JSONポリシーをレンダリングします。LifeguardはSkipperやGatewayにも基本的なアクセス情報を供給し、ユーザーがクエリ時にブロックされるのではなくフロントドアで弾かれるようにします。
- Skimmer(PII検出スキャナ)
- 継続的に実行され、すべてのテーブルの全カラムから行をサンプリングし、Workers AIを使って各カラムがPIIを含むか分類します。これには2段階あります:まず高速なカラム単位の分類、次に何かがフラグされた場合はエージェント的な2次パスでフルテーブルコンテキストを取得し、Trinoに直接クエリして検証します。所見はDataHubとLifeguardのallowlistに流れ、人間によるレビューのワークフローに入ります。
- Transformer(ELTエンジン、Workflows上に構築)
- ユーザーはYAMLフロントマターでSQL変換のDAG(ターゲットテーブル、マテリアライズモード、依存関係、スケジュール)を定義します。TransformerはグラフをコンパイルしてTrino上で実行し、状態はDurable Objectsで管理、定義はR2に格納、実行履歴はD1に保存します。
- Ingestion(オペレーショナルシステムからレイクへの橋渡し)
- オーケストレータは長期稼働するKubernetesデプロイとして実行され、パイプライン設定を読み、短命のワーカージョブを起動してPostgresやClickHouseから抽出、Parquetに変換し、IcebergテーブルとしてR2にロードします。各パイプラインはfull-replaceまたはincremental-appendで動作します。
デフォルト閉鎖: 設計によるガバナンス
統一データプラットフォームを作る際の真の懸念は、センシティブなデータの大きな表面を作ってしまうことです。従来の答えは「デフォルトで開く、例外として制限する」でした。つまりまずは全てにアクセスを許し、誰かが見つけたら監査してセンシティブなテーブルをロックダウンするというやり方です。Town Lakeはこれと逆のアプローチを取ります。テーブルはレビューされるまでクエリ可能になりません。
- 新しいデータベースがTrinoに接続されるか新しいテーブルが作成されると、Skimmerがスキャンしてカラムを分類し、中央のallowlistに保留として登録します。レビュー担当者がテーブルと特定のカラムを承認するまではユーザーはそのテーブルをクエリできません。
- これは面倒に聞こえますが、2つの理由で実用的です。
- 自動化されています。Skimmerの分類器はかなり良く、明らかなPII(メール、IP、名前、電話番号)や、特定のプレフィックスにマッチするAPIトークン、ユーザーに紐づく不透明なIDのような非明白なセンシティブデータの長い尾を検出します。レビュアーは検出内容を見て承認・上書き・否認を行えます。ほとんどのレビューは数秒で終わります。
- ワークフローはセルフサービスです。アクセス権がないテーブルをクエリすると、エラーメッセージは "permission denied" ではありません。"このテーブルはレビューが必要です。レビューをリクエストするにはここをクリックしてください。" と表示されます。Skipperは適切なRBACグループを提案し、リンクを案内します。
- スキーマの発見とデータアクセスを分離します。ユーザーはどのテーブルが存在するかを見ることができますが、未レビューのカラムはDESCRIBEやSHOW COLUMNS、SELECT * から隠されます。この微妙な区別は重要です:未レビューの新カラムが既承認テーブルの既存ダッシュボードを壊すことを防ぎます。
- PIIはセッションごとにオプトインです。デフォルトではTrinoはセンシティブなカラムを画面に現れる前にマスキングします。正当な理由(例:不正調査)で生のPIIが必要な場合、セッションでフラグを立てると権限がチェックされ、マスキングが解除されます。その操作とすべてのクエリはログに残ります。
Skipper:AIデータエージェント
現代では単なるクエリエンジンだけでは不十分です。SQLは依然として障壁であり、数万のテーブルの中からどれをクエリすべきか(正規のスキーマを知ること)を知るのも障壁です。Skipperは自然言語の質問から検証済みの回答へ到達する会話型AIエージェントの私たちの解です。Town Lakeの上に、そして我々の開発プラットフォーム上(Workers、Workers AI、Durable Objects、D1、R2、Workflows、KV)に構築しました。
- インターフェースはチャットボックスです。質問例:
- 「Show me the top 10 customers by R2 storage cost in the last 30 days, and the change versus the previous 30 days.」
- Skipperは適切なテーブルを見つけ(DataHub検索)、スキーマとラインエージを取得し、SQLを書き、Trinoに投げ、結果をポーリングしてテーブルやチャートを表示します。
- フォローアップ質問も可能です。例:「Now break it down by region, and ignore internal Cloudflare accounts.」文脈を保持し、クエリを洗練して再実行します。
- 何かがおかしいとき(例:結合でゼロ行になった、フィルタが期待したものを除外した等)は、Skipperが調査して調整し、再試行するという閉ループ推論を行います。
- 難しかったのは”正しいコンテキスト”を持たせることです。Skipperはチャートをダッシュボードにパッケージ化して社内共有したり、他の内部アプリケーションに埋め込んだりできます。またTransformerでの変換グラフ構築ツールやLifeguardを通じたアクセスと権限のチェックツールも持っています。
- Skipperはユーザーがいる場所で使えるようになっています。これらのツールはWorkersで提供され、Workers AIで駆動される組み込みのエージェントハーネスをバックエンドに持ちます。一方で多くの内部ユーザーはローカルのエージェントフロー経由で作業しており、SkipperのツールはMCPサーバ経由でも利用可能です。
コンテキストの層
LLMにSQLプロンプトとテーブル名のリストを与えるだけでは、結合を誤って想像したり、カラムを誤用したり、自信満々にまったく間違った数値を出してしまうことがあります。これは初期実験で痛い目を見て学びました。解決策は、モデルが検索時に引き出せる複数層の実証的なコンテキストを用意することです。
- レイヤー1:スキーマと使用状況メタデータ
- DataHubは全テーブルのすべてのカラム、型、主キー、外部キーを知っています。また、どのテーブルが comm...