自動パイプライン処理でv0.devのロード時間を50%短縮
Vercel KVはWebプロジェクトにとって非常に有用なツールですが、多用するとHTTPリクエスト数が急増し、パフォーマンスに悪影響を及ぼす可能性があります。Redisのパイプラインを使えばコマンドをまとめてリクエスト数を削減できますが、実装は決して簡単ではありません。パイプラインの効率性と、基本的なRedisコマンドの手軽さを両立できる解決策はあるのでしょうか?そこで登場するのが自動パイプライン(auto-pipelining)です。コードの複雑化を避けながら、シームレスにパフォーマンスを向上させることができます。
課題
Redisをデータソースとして大量に利用するWebページを構築したと想像してください。複数のコンポーネントがそれぞれ独自にRedisへの呼び出しを行い、中には異なるコンテンツで何度もレンダリングされるものもあります。
このような構成では、Webページを開くたびにRedisへのリクエストが大量に発生します。膨大なリクエスト数は大きなオーバーヘッドをもたらし、パフォーマンスを低下させます。さらに、Cloudflare Workersのように同時HTTPリクエスト数が厳しく制限されている環境では、これは致命的な問題になりかねません。
従来のパイプライン
こうした状況での従来の解決策が、Redisパイプラインの活用です。リクエストを1つずつ送信する代わりに、複数のコマンドを収集してまとめて実行できます。これにより、1つのHTTPリクエストに複数のRedisコマンドが含まれるため、HTTPリクエスト数が大幅に削減され、パフォーマンスが向上します。
import { kv } from '@vercel/kv';
const pipeline = kv.pipeline();
pipeline.set("foo", "bar");
pipeline.get("foo");
const res = await pipeline.exec();
console.log(res); // ["OK", "bar"]
パイプラインのデメリット
しかし、パイプラインの利用は開発者の観点から見ると大きな負担になります。パイプラインAPIは標準的なRedis APIとは異なるため、有効化・無効化の切り替えが容易ではありません。
また、1つのパイプライン内で異なるコンポーネント向けのデータを取得する必要がある場合、データを使用する場所とは別の場所にフェッチロジックを記述しなければなりません。この分離は、コードベースの断片化や保守性の低下を招きます。
自動パイプライン(Auto-Pipelining)
自動パイプラインは、これらの問題をシームレスに解決します。既存のコードを一切変更することなく、プロジェクトでパイプライン機能を有効化できるのです。これまでどおりRedisを通常の方法で使いながら、Redisクライアントが可能な範囲でコマンドを自動的にバッチ化し、手間をかけずにパフォーマンスを引き上げます。
具体的なシナリオとして、複数のキーから値を取得するケースで、自動パイプラインがどう最適化するのかを見てみましょう。
const keys = ["key1", "key2", "key3"];
const values = await Promise.all(keys.map(key => kv.get(key)));
パイプラインを使用しない場合、このコードはRedisに3つのHTTPリクエストを送信します。一方、自動パイプラインを有効にすると、これらのリクエストは1つのHTTPリクエストにまとめられます。
慣用的なReactサーバーコンポーネントとの相性
Reactサーバーコンポーネント(RSC)は、自身でデータを取得できます。典型例として、ツイートを表示するコンポーネントは次のように実装されることがあります。
async function Tweet({id}) {
const tweet = kv.get(`tweets:${id}`)
return <div>{tweet.text}</div>
}
このコンポーネントを次のようにループ内で呼び出すと、
{tweetIds.map(id => <Tweet id={id} />)}
先ほどの例と同じく、N個のバックエンドリクエストが発生します。ここでも自動パイプラインを有効にすれば、慣用的なReactコードをそのまま維持しながら、N個のコマンドが1つのパイプラインにバッチ化されます。
仕組み
自動パイプラインは、バックグラウンドで「アクティブなパイプライン」を維持することによって動作します。各コマンドはパイプラインに自分自身を追加し、deferExecutionを呼び出します。
private async deferExecution() {
await Promise.resolve()
return await Promise.resolve()
}
deferExecutionを呼び出すと、そのコマンドはNode.jsのメインスレッドの制御を譲ります。すると、次のGETコマンドがスレッドの制御を取得して実行を進め、最初のGETとまったく同じ動作を行います。つまり、アクティブなパイプラインに自分自身を追加し、再びスレッドの制御を譲るのです。
以下は、自動パイプラインのロジックを擬似コードで表したものです。
let activePipeline: Pipeline;
let pipelinePromises: new WeakMap<Pipeline, Promise<Array<unknown>>>();
let commandIndex: number;
const executeCommand = (command) => {
activePipeline = activePipeline || createNewPipeline();
activePipeline.addCommand(command);
commandIndex++;
const pipelinePromise = deferExecution().then(() => {
if (!pipelinePromises.has(activePipeline) {
const pipelinePromise = pipeline.exec();
pipelinePromises.set(pipeline, pipelinePromise);
activePipeline = null;
commandIndex = 0
};
return pipelinePromises.get(activePipeline)!;
});
const result = await pipelinePromise;
return result[commandIndex];
};
3番目のGETも、アクティブなパイプラインに自分自身を追加した後、deferExecutionを呼び出します。この時点で、制御を譲るべき他のコマンドが存在しないため、保留されていたGETコマンドのいずれかが制御を取り戻し、パイプラインを実行します。
この例では、パイプラインは3つの結果を返し、それぞれが最初のコマンドに対応しています。各コマンドは引数のインデックスを追跡しているため、バッチレスポンスから正しい結果を受け取ることができます。
自動パイプラインのロジックを支える実際のコードは、公開リポジトリで確認できます。
v0.devにおける自動パイプラインの効果
自動パイプラインがv0.devのパフォーマンスにもたらした影響は、ランディングページの改善から明らかです。ランディングページには過去のクエリや生成結果の事例が表示されます。まず表示対象のアイテム一覧を取得し、その後、各アイテムごとに個別のフェッチを行うため、大量のRedisリクエストが発生していました。

自動パイプラインを有効にすると、このプロセスはスイッチ一つで最適化されます。以前は個別のフェッチが何度も行われていた後半部分が、1つのパイプライン操作へと統合されたのです。
導入前は、これらのRedisリクエストが大量の個別HTTPリクエストとなり、ページの読み込み時間を遅延させていました。しかし、自動パイプラインを有効にすると、リクエストは効率的にパイプラインへバッチ化され、必要なHTTPリクエスト数が大幅に削減されます。その結果、ページの読み込み時間は約450msから約200msへと、およそ半減しました。
v0チームは当初、自動パイプラインを自チーム向けのハックとして実装していましたが、現在ではUpstashのRedisクライアントv1.31.3以降、およびVercel KV v2.0で誰でも利用できるようになっています。
-
RedisでリアルタイムA/Bテストツールを自作する方法【完全ガイド】
A/Bテストは、今日のデジタル経済において競争力を維持したいマーケターにとって欠かせない手法となっています。既存のアイデアを検証し、新しいアイデアを実験し、何が効果的で何がそうでないかを明確にすることができます。 この情報がなければ、Webページの最適化は試行錯誤の連続となり、時間と労力を大きく浪費してしまいます。しかし残念ながら、市販のA/Bテストソフトウェアは高価であり、その恩恵を受けられない人も少なくありません。 そこで登場したのがRedisです。プログラマーのThiago Camargo氏は、Redisを活用することでこうした障壁を取り払い、リアルタイムで動作するオープンなA/Bテ
-
C#でAzure Redis Cacheをマスターする実践ガイド
はじめに Azure Redis Cacheは、オープンソースのインメモリ型Redisキャッシュを基盤とするサービスです。バックエンドのデータソースからデータをキャッシュに読み込み、キャッシュからWebページを配信することで、アプリケーションのパフォーマンスを大幅に向上させることができます。本記事では、ステップバイステップでC#のWebアプリケーションにAzure Redis Cacheを導入する方法を解説します。 Azure Redis Cacheとは? 現代のアプリケーションの多くは大量のデータを扱います。データベースからデータを取得する際、通常は対象のテーブルを検索し、その結果をユーザー