2024年4月にBaselimeがCloudflareの傘下に加わったとき、当社のアーキテクチャには数百のAWS Lambda関数、数十のデータベース、そして大量のキューが溢れていました。複雑さが足かせとなり、クラウドのコストは急速に増大していました。現在、当社はCloudflare上にBaselimeとWorkers Observabilityを構築しており、クラウドコンピューティング料金を80%以上節約できる見込みです。Cloudflareの想定コストは、スタンドアロンサービスであるBaselimeに対するもので、Workers有料プランに基づいて算出されています。大幅なコスト削減を実現しただけでなく、アーキテクチャを簡素化し、全体的な遅延、スケーラビリティ、信頼性を向上させました。
コスト(日あたり) | 移行前(AWS) | 移行後(Cloudflare) |
コンピューティング | $650 - AWS Lambda | $25 - Cloudflare Workers |
CDN | $140 - Cloudfront | $0 - 無料 |
データストリームと分析データベース | $1,150 - Kinesis Data Stream + EC2 | $300 - Workers Analytics Engine |
合計(日あたり) | $1,940 | $325 |
全体(年あたり) | $708,100 | $118,625(83%のコスト削減) |
表1:AWSとWorkersのコスト比較(米ドル)
Cloudflareの傘下となった際、すぐに利用量が急増し、発表後1週間のうちに、毎日10億件以上のイベントを処理することとなり、アクティブユーザー数は3倍に増えました。
プラットフォームが拡大するにつれて、スケーラビリティ、信頼性、コストに関する新たな考慮事項とともに、リアルタイムでの可観測性を管理するという課題も深刻化の一途をたどりました。これを受け、Cloudflare開発者プラットフォーム上でBaselimeを再構築した結果、運用上のオーバーヘッドを削減しながら、迅速にイノベーションを実現することができました。
当社のアーキテクチャは当初、すべてAmazon Web Services(AWS)上に構築されていました。ここでは、毎日数百億件のイベントの取り込み、処理、保存に対応するデータパイプラインに焦点を当てて説明します。
このパイプラインは、AWS Lambda、Cloudfront、Kinesis、EC2、DynamoDB、ECS、ElastiCache上に構築されていました。
図1:初期データパイプラインアーキテクチャ
主な要素は次の通りです。
データレセプター:OpenTelemetry、Cloudflare Logpush、CloudWatch、Vercelなどの複数のソースからのテレメトリデータを受信します。データの検証、認証、そして各ソースからのデータの共通内部フォーマットへの変換についても対応します。データレセプターは、データソースに応じて、AWS Lambda(関数URLとCloudfrontを使用)またはECS Fargateのいずれかにデプロイされました。
Kinesis Data Stream:データレセプターからのデータを、次のステップであるデータ処理に向けて転送します。
プロセッサー:ストレージ用データの拡充と変換に対応する単一のAWS Lambda関数。この関数は、リアルタイムのエラー追跡とログのパターン検出も実行していました。
ClickHouseクラスター:すべてのテレメトリデータは最終的にインデックス化され、EC2のセルフホスト型ClickHouseクラスタに保存されました。
これらの主な要素に加え、エラー処理、再試行、メタデータの保存に対応するため、既存スタックではFirehose、S3バケット、SQS、DynamoDB、RDSとのオーケストレーションも必要でした。
このアーキテクチャは、はじめのうちはうまく機能していましたが、より多くのお客様に対応するためにソリューションを拡張するにつれ、大きな問題が生じるようになりました。
データレセプターとKinesis Data Stream間のインターフェースでの再試行処理は複雑で、Firehose、S3バケット、SQS、別のLambda関数の導入とオーケストレーションを必要としました。
セルフホスト型のClickHouseでも、コストをコントロールしながらも増大するユーザーベースに対応するために、継続的に容量を計画し、設定を更新しなければならないため、重大な課題が大規模で生じることとなりました。
また、ワークロードの増大に伴い、特にAWS Lambda、Kinesis、EC2において、予測不可能な勢いでコストが増大し始めました。Cloudfront(Lambda関数URLの前に付加するカスタムドメインのために必要)やDynamoDBなどでも、AWS Lambda、Kinesis、EC2と比較するとそれほど明白ではないものの、コストが増大していました。具体的には、AWS LambdaのI/O操作に費やす時間が特に高コストでした。データレセプターからClickHouseクラスターまで、すべてのステップでデータを次の段階に移すには、ネットワークリクエストが完了するのを待つ必要があり、Lambda関数のウォールタイムの70%以上を占めていました。
一言で言えば、私たちは常にアラートに悩まされ、イノベーションのペースは遅く、コストは制御不能に陥っていました。
さらに、このソリューション全体は単一のAWSリージョン(eu-west-1)に展開されていました。その結果、ヨーロッパ大陸以外の開発者がBaselimeにログやトレースを送信する際には長い遅延が発生しました。
最新アーキテクチャ — Cloudflareへの移行
Cloudflare開発者プラットフォームへの移行により、コスト、簡素さ、俊敏性に妥協することなく、極めて高速かつグローバルに分散され、高いスケーラビリティを備えたアーキテクチャを実現するにはどうすればよいかを改めて考えることができました。この新しいアーキテクチャは、Cloudflareのプリミティブ上に構築されています。
図2:最新データパイプラインアーキテクチャ
Cloudflare Workers:Baselimeの中核
Cloudflare Workersは今や、私たちが行うすべての中核となっています。すべてのデータレセプターとプロセッサはWorkers内で実行されます。Workersはコールドスタート時間を最小限に抑え、デフォルトでグローバルにデプロイされます。そのため、開発者がBaselimeにイベントを送信する際も、遅延は常に短いものです。
さらに、パイプラインのステップ間のデータ転送にはJavaScriptネイティブのRPCを多用しています。低遅延で軽量であり、コンポーネント間の通信を簡素化します。これにより、別々のコンポーネントが完全に別々のアプリケーションとしてではなく、同じプロセス内の関数のように機能するため、アーキテクチャがさらに簡素化されます。
export default {
async fetch(request: Request, env: Bindings, ctx: ExecutionContext): Promise<Response> {
try {
const { err, apiKey } = auth(request);
if (err) return err;
const data = {
workspaceId: apiKey.workspaceId,
environmentId: apiKey.environmentId,
events: request.body
};
await env.PROCESSOR.ingest(data);
return success({ message: "Request Accepted" }, 202);
} catch (error) {
return failure({ message: "Internal Error" });
}
},
};
コードブロック1:JavaScriptネイティブRPCを使ってプロセッサを実行する簡素化されたデータレセプター。
Workersは、Rate Limitingバインディングも公開しているため、サービスにレート制限を自動的に追加できるようになりました。これまでは、DynamoDBとElastiCacheを組み合わせて自分たちで構成する必要がありました。
さらに、Worker呼び出し内でctx.waitUntilを多用して、リクエスト/レスポンスパスの外にデータ変換をオフロードしています。これにより、開発者がデータレセプターに送信する呼び出しの遅延がさらに短縮されます。
Durable Objects:ステートフルなデータ処理
Durable Objectsは、サーバーレス環境でステートフルなアプリケーションを構築できるようにする、Cloudflare開発者プラットフォーム内にある独自のサービスです。当社では、リアルタイムのエラー追跡とログのパターン検出の両方のために、データパイプラインでDurable Objectsを使用しています。
例えば、リアルタイムでエラーを追跡するために、私たちは新しいタイプのエラーごとにDurable Objectを作成します。そしてこのDurable Objectが、エラーの頻度、お客様に通知するタイミング、およびエラーの通知チャネルを追跡する役割を担います。このような単一ビルディングブロックでの実装により、ElastiCache、Kinesis、複数のLambda関数がRDSデータベースを高頻度のエラーによる過負荷から保護するためにオーケストレーションを行う必要がなくなります。
図3:リアルタイムエラー検出アーキテクチャの比較
Durable Objectsを使用すると、データパイプライン内の状態管理の一貫性と同時実行性を正確に制御することができます。
データパイプラインに加えて、当社ではDurable Objectsをアラートに使用しています。以前のアーキテクチャでは、EventBridge Scheduler、SQS、DynamoDB、複数のAWS Lambda関数をオーケストレーションする必要がありましたが、Durable Objectsでは、すべてalarmハンドラ内で処理されます。
Workers Analytics Engine:大規模な高カーディナリティ分析
当社独自のClickHouseクラスタの管理は技術的に興味深いものの困難であり、開発者は最高の可観測性をもって作業にあたることはできませんでした。今回の移行により、サーバーインスタンスの管理に時間を費やす必要がなくなり、私たちは製品をよりよいものにするために多くの時間を割けるようになりました。
Workers Analytics Engineにより、スケーラブルな高カーディナリティ分析データベースに同期的にイベントを書き込むことができます。データベースは、Workers Analytics Engineを支える技術と同じ技術の上に構築しました。また高カーディナリティに加え、高次元性をネイティブに有効にするために、Workers Analytics Engineに内部変更を加えました。
さらに、Workers Analytics Engineと当社のソリューションはCloudflareのABR分析を活用しています。ABRはAdaptive Bit Rateの略で、テレメトリデータを異なる解像度(100%~0.0001%)で複数のテーブルに保存することができます。0.0001%のデータを含むテーブルに対してクエリを実行する場合、すべてのデータを持つテーブルよりもはるかに高速になります。ただし、精度がトレードオフとなります。そのため、システムにクエリが送信されると、Workers Analytics Engineはクエリを実行するのに最も適切なテーブルを動的に選択し、クエリ時間と精度の両方を最適化します。ユーザーは、データセットのサイズやクエリの所要時間に関係なく、最適なクエリ時間で最も正確な結果を得られます。常にすべてのデータセットに対してクエリを実行していた旧システムと比較して、新しいシステムは全ユーザーベースとユースケースで高速なクエリを提供できるようになりました。
これらのコアサービス(Workers、Durable Objects、Workers Analytics Engine)に加えて、新しいアーキテクチャはCloudflare開発者プラットフォームのその他のビルディングブロックを活用しています。具体的には、非同期メッセージ、サービスの分離、イベント駆動型アーキテクチャを可能にするQueues、トランザクションデータ(クエリ、アラート、ダッシュボード、構成など)のメインデータベースであるD1、迅速な分散型ストレージを実現するWorkers KV、そして当社のすべてのAPI用のHonoなどです。
Baselimeはイベント駆動型アーキテクチャに基づいており、すべてのユーザーアクションがイベントをトリガーします。つまり、ユーザーの作成、ダッシュボードの編集、その他のアクションの実行など、すべてのユーザーアクションがイベントとして記録され、システムの残りの部分に出力されるという原則に基づいて動作します。そのため、Cloudflareへの移行には、アップタイムとデータの一貫性を損なうことなく、イベント駆動型アーキテクチャを移行することが含まれました。以前はAWS EventBridgeとSQSで稼働していましたが、Cloudflare Queuesに完全に移行しました。
その際、ストラングラーフィグパターンに従い、ソリューションをAWSからCloudflareに段階的に移行していきました。システムの中断を最小限に抑えながら、システムの特定の部分を新しいサービスに徐々に置き換えていく方式です。プロセスの初期段階で、移行中のすべてのトランザクションイベント処理のバックボーンとして機能する中央のCloudflare Queueを作成しました。新規ユーザーの登録であろうとダッシュボードの編集であろうと、すべてのイベントがこのQueueに集約されました。そこから、イベントは動的にルーティングされ、各イベントはアプリケーションの関連する部分に動的にルーティングされます。ユーザーアクションはD1とKVに同期され、移行中もAWSとCloudflareの両方ですべてのユーザーアクションがミラーリングされます。
この同期メカニズムにより、一貫性を維持し、ユーザーがBaselimeを操作する中でデータが失われることは一切ありませんでした。
以下は、イベントがどのように処理されるかを示した例です。
export default {
async queue(batch, env) {
for (const message of batch.messages) {
try {
const event = message.body;
switch (event.type) {
case "WORKSPACE_CREATED":
await workspaceHandler.create(env, event.data);
break;
case "QUERY_CREATED":
await queryHandler.create(env, event.data);
break;
case "QUERY_DELETED":
await queryHandler.remove(env, event.data);
break;
case "DASHBOARD_CREATED":
await dashboardHandler.create(env, event.data);
break;
//
// Many more events...
//
default:
logger.info("Matched no events", { type: event.type });
}
message.ack();
} catch (e) {
if (message.attempts < 3) {
message.retry({ delaySeconds: Math.ceil(30 ** message.attempts / 10), });
} else {
logger.error("Failed handling event - No more retrys", { event: message.body, attempts: message.attempts }, e);
}
}
}
},
} satisfies ExportedHandler<Env, InternalEvent>;
コードブロック2:簡素化された、移行中の内部イベント処理
当社は、データパイプラインをAWSからCloudflareに、アウトサイドイン方式で移行しました。データレセプターから始め、段階的にデータプロセッサとClickHouseクラスタを新しいアーキテクチャに移行しました。テレメトリデータ(ログ、メトリクス、トレース、ワイドイベントなど)は、ClickHouse(AWS内)とWorkers Analytics Engineの両方に、保持期間は30日間で同時に書き込みました。
最後のステップは、これまでAWS LambdaとECSコンテナでホストされていたすべてのエンドポイントを、Cloudflare Workersに書き換えることでした。これらのWorkersの準備ができたら、既存のLambda関数ではなくWorkersを参照するようにDNSレコードを切り替えるだけです。
複雑な作業にもかかわらず、データパイプラインからすべてのAPIエンドポイントの書き換えまで、移行プロセス全体は、3人のエンジニアからなるチームが3か月足らずで完了できました。
2024年6月上旬にデータレセプターをAWSからCloudflareに切り替えた後、AWS Lambdaのコストが85%以上削減されました。これらのコストは主に、データレセプターが同じリージョン内にあるKinesis Data Streamにデータを送信するのに費やしたI/O時間によって発生していました。
図4:Baselimeの1日あたりのAWS Lambdaコスト[注:データのギャップは、クラウドアカウントの親組織が変更された際にAWS Cost Explorerでデータ損失が発生したことによります。]
さらに、Cloudfrontを使用して、データレセプターを参照するカスタムドメインを有効にしていました。データレセプターをCloudflareに移行した後、Cloudfrontは必要なくなりました。それに応じて、Cloudfrontの費用は0ドルまで削減されました。
図5:Baselimeの1日あたりのCloudfrontコスト[注:データのギャップは、クラウドアカウントの親組織が変更された際にAWS Cost Explorerでデータ損失が発生したことによります。]
私たちがCloudflareを利用していた場合、Cloudflare Workersの費用は、1日あたり25ドルほどになると推定されます。これはAWSでの1日あたり790ドルと比較して、95%以上のコスト削減となります。このような削減は主に、Workersの価格設定モデルによってもたらされます。WorkersはCPU時間に対して課金し、レセプターは主にデータを移動するだけで、コスト増大の主な原因はI/Oバウンドであったためです。
セルフホスト型のClickHouseからWorkers Analytics Engineへの切り替えによるコストへの影響を評価するには、EC2インスタンスだけでなく、ディスクスペース、ネットワーキング、Kinesis Data Streamのコストも考慮する必要があります。
当社は8月下旬にこの切り替えを完了し、KiKinesis Data StreamとすべてのEC2関連コストの両方で95%以上のコスト削減を実現しました。
図6:Baselimeの1日あたりのKinesis Data Streamコスト[注:データのギャップは、クラウドアカウントの親組織が変更された際にAWS Cost Explorerでデータ損失が発生したことによります。]
図7:Baselimeの1日あたりのEC2コスト[注:データのギャップは、クラウドアカウントの親組織が変更された際にAWS Cost Explorerでデータ損失が発生したことによります。]
私たちがCloudflareを利用していた場合、Workers Analytics Engineの費用は1日あたり300ドルほどになると推定されます。これはAWSでの1日あたり1150ドルと比較して、70%以上のコスト削減となります。
Cloudflareに移行することで、大幅なコスト削減を実現できただけでなく、パフォーマンスも全体的に改善しました。Cloudflareのネットワーク全体でユーザーにより近い場所でリアルタイムのイベント取り込みが行われるようになり、ユーザーへの応答はより迅速になりました。ユーザーがデータをクエリする際の応答も、ClickHouseを大規模に運用するCloudflareの深い専門知識のおかげで、はるかに迅速になっています。
最も重要なのは、スループットやスケールの制限に縛られなくなったことです。当社は2024年9月26日にWorkers Logsをリリースしました。現在、当社のシステムは、速度や信頼性を犠牲にすることなく、以前よりもはるかに多くのイベントを処理できるようになりました。
実現されたコスト削減はそれだけで素晴らしいものであり、それらのシステムの総所有コストは含まれません。プラットフォームがより多くのことを管理してくれるようになったため、システムとコードベースを大幅に簡素化できました。アラートが減り、インフラストラクチャの監視に費やす時間が減り、製品改善に集中できるようになりました。
CloudflareへのBaselimeの移行は、当社がプラットフォームを構築してスケーリングする方法を変革しました。Workers、Durable Objects、Workers Analytics Engine、その他のサービスを活用することで、当社は完全にサーバーレスでグローバルに分散したシステムを稼働することができ、さらにはコスト効率と俊敏性を向上させることができました。運用オーバーヘッドが大幅に削減され、イテレーションが迅速になり、ユーザーにより優れた可観測性ツールを提供できるようになりました。
Workers Logsがあれば、今すぐにでもCloudflare Workersの監視に着手できます。今後については、リアルタイムのエラー追跡、アラート通知、高カーディナリティや次元性イベントに対するクエリビルダーなど、Cloudflareダッシュボードで直接提供する予定の機能について胸躍らせています。2025年初頭までにすべてリリースできる予定です。