tracio.aiの30ミリ秒未満パイプラインをどう構築したか
シグナル収集から訪問者IDまで30ミリ秒未満で。Go、ClickHouse、Redis、分散処理を用いた当社アーキテクチャの解説です。
tracio.aiのデバイス識別エンジンを構築するにあたり、当社には譲れない要件が1つありました。暗号化されたシグナルの受信から訪問者IDの返却まで、パイプライン全体が95パーセンタイルで30ミリ秒未満に完了しなければならない、という要件です。本記事では、この目標を達成するために構築したアーキテクチャを詳しく解説します。
パイプラインの概要
識別パイプラインには5つのステージがあります。シグナル復号、シグナル正規化、ハッシュ計算、アイデンティティ解決、応答シリアライズです。各ステージは個別に最適化されており、並列実行が可能なステージは並列で実行されます。全体の予算は30msで、おおよそ次のように配分されています。復号2ms、正規化3ms、ハッシュ化2ms、アイデンティティ解決20ms、シリアライズ1ms。残りの2msはバッファです。
シグナル復号は、クライアント側で暗号化された転送を元に戻します。当社はGoの暗号パッケージをハードウェアアクセラレーションとともに使用しており、一般的な4KBのペイロードの復号を1ms未満で完了します。正規化はシグナルのJSONを解析し、型を検証し、プラットフォーム固有の変換を適用します。たとえば、バージョン固有のノイズを除去するためにユーザーエージェント文字列を正規化します。
分散アイデンティティ解決
アイデンティティ解決、つまりこのデバイスが以前に確認されたことがあるかどうかの判定は、最もレイテンシに敏感なステージです。当社はデバイスプロファイルをRedisに保存し、分散キールーティング層を用いてクラスタ全体にシャーディングしています。このルーティングはハードウェア階層のフィンガープリントに基づいてキーを分配するため、同じデバイスに対するルックアップは常に同じRedisノードに到達します。
当社のシャーディング実装は、均等な分散を保証するために仮想ノード(物理ノードあたり150個)を使用しています。ノードが追加または削除されると、キーの1/Nのみを再マッピングすればよく、ここでNはノード数です。当社はこのルーティング層をGoで実装しており、O(log n)のルックアップ時間かつゼロアロケーションを実現しています。
アイデンティティストアとしてのRedis
当社は他の選択肢(Memcached、ScyllaDB、DynamoDB)ではなくRedisを選びました。その理由は、一貫したサブミリ秒の応答時間と、複雑なデータ構造のサポートにあります。各デバイスプロファイルはRedisハッシュとして保存され、各シグナル階層のハッシュ、訪問者ID、最終確認タイムスタンプ、信頼度メタデータのフィールドを持ちます。
アイデンティティ解決クエリは、単一のHGETALL呼び出しの後、受信したシグナルハッシュを保存済みのハッシュと比較するものです。ハードウェア階層が一致すれば、既存の訪問者IDを高い信頼度で返します。ソフトウェア階層のみが一致する場合は、シグナルレベルのデータの類似度比較を行い、これがブラウザを更新した同じデバイスかどうかを判定します。何も一致しなければ、新しい訪問者IDを生成します。
イベント保存のためのClickHouse
すべての識別イベントは非同期でClickHouseに書き込まれます。当社はインサートをバッチ化するバッファ付きライターを使用しており、100msの間、または1,000イベントが蓄積されるまでのいずれか早い方までイベントを収集します。このバッチ化は極めて重要です。ClickHouseは個々の行のインサートよりも、大きなインサート(一度に数千行)で最も高いパフォーマンスを発揮するためです。
当社のClickHouseスキーマは、最も一般的な2つのクエリパターンに合わせて最適化されています。特定の訪問者IDに関するすべてのイベントのルックアップと、期間ごとのイベントの集計です。当社は(visitor_id, timestamp)を主キーとするMergeTreeエンジンを使用しており、高速なポイントルックアップと効率的なレンジスキャンを実現しています。マテリアライズドビューが日次および時間単位で事前集計されたメトリクスを保持します。
スケール環境で30ミリ秒未満を達成する
レイテンシ目標を達成するうえで、3つのアーキテクチャ上の決定が重要でした。第1に、パイプラインは完全にストリーミング型であり、HTTPリクエストボディ全体を受信し終える前にシグナルの処理を開始します。第2に、Redisのルックアップは永続的接続によるコネクションプーリングを使用し、TCPハンドシェイクのオーバーヘッドを排除しています。第3に、ClickHouseへの書き込みは完全に非同期であり、応答経路を決してブロックしません。
毎秒5万リクエストの負荷テストにおいて、当社のp50レイテンシは12ms、p95は24ms、p99は38msです。p99はRedisクラスタのリバランス中に30msの目標を時折超えることがありますが、p95は一貫して30ms未満を維持します。より厳しいレイテンシ要件を持つお客様には、マルチテナントの競合を排除する専用Redisクラスタを提供しています。