このBlog記事はガオ株式会社による Langfuse GmbH "From Zero to Scale: Langfuse's Infrastructure Evolution" の日本語訳 後半となります。原文はこちらをご確認ください。
前半記事はこちら。
ビルディングブロック2: キャッシュと分離型のインフラストラクチャ
Clickhouseへのデータ取り込み問題を解決する一方で、プロンプト管理機能を利用するユーザーの状況も改善する必要がありました。Observerbility データ分析の結果、プロンプトAPIにはいくつかの問題があることが判明しました。具体的には、それらが大量の取り込みトラフィックがデータベースのIOPSやコンテナのCPUを枯渇させる可能性がありました。
最初に、プロンプトを取得するためのPostgresデータベースへの問い合わせを回避しようと試みました。ここで、Redisをキャッシュとして利用することの強力さと、それがもたらす影響を実感しました。プロンプトが更新されるタイミングは把握しているため、古い結果を返すリスクなく効率的にキャッシュを無効化できます。
次に、プロンプトや認証など、レイテンシーと可用性に敏感なインフラを専用のデプロイメントに分離しました。AWS ECSの特定のエンドポイントに対して、LoadBalancerルールと専用のTargetGroupを使用することで、取り込みトラフィックをアプリケーションの他の部分から隔離できます。さらに、専用ルートによって可観測性が向上するという追加のメリットも得られました。この構成はまだHelm Chartには含まれていませんが、必要であれば主要なクラウドプロバイダーやKubernetes全体でエミュレートできます。
これらの2つの変更により、プロンプトAPIのp99(99パーセンタイル)のレスポンスタイムを7秒から100ミリ秒に短縮することに成功しました。
3つ目に、SDKを更新して、ゼロレイテンシのプロンプトフェッチングを使用するようにしました。以前にAPIからプロンプトのバージョンが取得されている場合は、そのバージョンを次のLLM呼び出しに使用し、LangfuseのAPIへの呼び出しはバックグラウンドで実行します。これにより、プロンプトがユーザーのアプリケーションのクリティカルパスから取り除かれました。
ビルディングブロック3:OLAPデータベース+クエリ最適化
Langfuse V3への移行における最後の課題は、読み取りクエリを最適化し、すべてのユーザーに対して一貫して低いレイテンシーを実現することでした。この取り組みを開始した時点で、すでにPostgresと並行してClickhouseにデータを書き込んでいました。これにより、Clickhouseからのデータの保存方法と取得方法を実験することができました。何度も「これでうまくいった」と思ったものの、そのたびにさらに多くのデータが到着しました。
当初、過去1週間以内のデータに対するすべての呼び出し(単一レコードのルックアップを含む)はp99で1秒以内に完了し、1週間以上のデータについては4秒以内であれば許容できると考えていました。Datadogでの通常のトレーシングに加えて、PostgresとClickHouseの読み取りを同時に実行し、それらのレイテンシーの違いを測定するためのフレームワークを構築しました。一貫して改善された結果を確認することで、正しい方向に進んでいるという確信を得ました。
まず最初に正しく行う必要があったのは、テーブルスキーマ、パーティショニング、およびデータの並び順キーでした。これらを変更するには、テーブル全体の書き換えが必要となり、つまり、すべての更新に数日かかる可能性がありました。他のClickhouseユーザーとの会話やClickhouseのドキュメントを参照し、早い段階でprojectIdと日付を並び順キーの最初の2つの列にすることにしました。さらに、フィルタリングによく使用される列にスキップインデックスを設定して、アイテムIDも追加しました。Postgresとは異なり、Clickhouseでは効率的な行ルックアップのためのB-Treeインデックスを保持できません。常にディスクレイアウトに基づいてデータを検索する必要があります
次に最適化する必要があったのは結合処理 (Join) でした。Oberverbility データへの結合を行う際に、処理に数秒かかり、Clickhouseノードで使用可能なすべてのメモリを使い果たすことが頻繁に発生していました。Clickhouseは、結合の右側 (The right side of a join) を効果的にフィルタリングするのが苦手だということを学びました。そこで可能な限り結合を避け、Common Table Expressions (CTE) を使用して、できる限りフィルターを手動でプッシュダウンしました。場合によっては、単一のトレースやObserverbility データのルックアップで並び順キーをより有効に活用するために、タイムスタンプなどの追加情報をフロントエンドAPIの呼び出しに追加しました。
これらは私たちが想定していた課題でしたが、ここからはClickhouseの特異な性質について掘り下げていきます。まず、ReplacingMergeTree内でデータを重複排除する方法を見つける必要がありました。Clickhouseは最終的には行を重複排除しますが、挿入後数分から数時間以内に多くの重複が発生し、短期間のメトリクスやダッシュボードがほぼ使い物にならないことがわかりました。FINALキーワードを使用すると改善されましたが、クエリ実行中のリソース消費量が増加したり、スキップインデックスの最適化がまったく利用されなくなったりするという新たな問題が発生しました。書き込みタイムスタンプによる重複排除(order byとlimit by)、FINALキーワード、およびdistinct集計を組み合わせて、信頼性の高いメトリクス、テーブル、およびビューを作成しました。
スキーマ設計に多くの時間を費やしたことが、Clickhouseへの移行において大きな成果をもたらしました。プロセス全体を通してわずかな調整しか必要なく、PostgresからClickhouseへの1回のデータインポートで済みました。残りの最適化については、特定のユースケースで何がうまくいくかを予測できなかったため、迅速な反復が功を奏しました。優れた可観測性と堅牢な実験フレームワークは、反復と新しい仮説の形成に迅速なフィードバックを提供しました。昔から言われているように、「変更が難しいことについては時間をかけて考え、できる限り迅速に動く」ということが重要です。
これらにより、以前は大規模プロジェクトでの外れ値によって悪化していたフロントエンドおよびバックエンドAPI呼び出しにおけるパフォーマンスが向上しました。
V3 Architecture
まとめ:Langfuse v3のアーキテクチャに施した主な変更点は以下の通りです。
Worker コンテナ:イベントを非同期に処理する
S3/Blob store: ラージオブジェクトを保存するため
Clickhouse: Trace, Observations, Scoreを保存する
Redis/Valkey: Eventキューイングとデータキャッシュ
V3構成
V2構成 (参考)
結論 (Conclusion)
Langfuse v3のリリースに非常に満足しています。Langfuse Cloud ユーザーからのポジティブなフィードバック、最初の1週間でのセルフホストユーザーの高い導入率、そしてわずかな問題点リストは、プロジェクト全体を通して多くの正しい決断を下したことを示しています。新しいアイデアを迅速にテストし、明確なデータに基づいた強力な仮説に導かれることで、大きな進歩を遂げることができました。
しかし、プロジェクト中、しばしばスケジュールを見誤っていました。今後の取り組みについては、スコープを最大1か月に制限し、途中でより多くのマイルストーンを導入する予定です。さらに、Langfuseが急速に成長しているため、このプロジェクトの進行中、主要な担当者が継続的なインシデントやバグ修正の対応に手が取られることが多くありました。
私たちはデータ駆動型の文化、本番データのサブセットで実験を行うためのシンプルな方法、そしてスタック全体にわたる優れた可観測性を維持します。これらの要素は、費やした各時間の価値を最大化し、セルフホストとクラウドベースの両方の顧客を満足させ続けるのにとって重要な意味を持つものとなります。
日本語:Langfuse 日本語サイト
本ブログにおけるお問い合わせは こちら まで。
Comments