読了時間:12分
概要
ブロックチェーンと連携するアプリケーションを開発している方なら、どのRPCプロバイダーが最高のパフォーマンスを発揮するのか、一度は考えたことがあるでしょう。このガイドでは、RPCプロバイダーのテスト方法を紹介するとともに、RPCリクエストをより深く理解するために知っておくべき基本概念についても解説します。このガイドを読み終える頃には、さまざまなRPCプロバイダーで実験を行い、どれが最高のパフォーマンスを発揮するかを確認できるようになり、プロバイダー間の比較方法をより深く理解できるようになるでしょう。
学習内容
- ブロックの高さだけを比較すると、なぜ誤解を招く可能性があるのか
- RPCプロバイダを評価するためのテストスイートの構築方法
- データの鮮度とは何か、そしてなぜそれが単なる指標よりも重要なのか
- データの鮮度評価システムの実装方法
- RPCの利用を最適化するためのテクニック
必要なもの
- ブロックチェーンRPCプロバイダーに関する基礎知識
- お使いのマシンにNode.jsがインストールされている
- 比較対象となるRPCプロバイダーのURLが少なくとも2つある
ブロック高の分析
アプリケーション用のRPCプロバイダーを比較しているところだ。あるプロバイダーは、競合他社よりも常に3~4ブロック先までのブロック高を返している。別のプロバイダーは50ミリ秒で応答するが、「より優れた」とされるプロバイダーは400ミリ秒かかる。どちらを選ぶべきだろうか?
その時点でより大きなブロック番号を返しているプロバイダーを選んだとしたら、それは間違いです。一見、最も新しいデータを提供しているように見えるプロバイダーでも、実際には、あなたのアプリケーションにとってあまり役に立たない情報を提供している可能性があります。
これは一見して分かりにくいことです。RPCリクエストの処理中に実際に何が起きているのか、そしてそのタイミングが受信するデータにどのような影響を与えるのかを理解する必要があります。なぜこのケースでは、「処理が遅い」プロバイダーの方が、より有用なデータを提供できたのか、その理由を調べてみましょう。
RPCプロバイダーの指標の比較
RPCプロバイダを評価する際、パフォーマンスの比較を行う上で役立つ指標が2つあります:
- ブロック高- どのプロバイダーが最も高いブロック番号を返すか? 数値が高いほど良いですよね? ブロックチェーンの最新の状態を確認したいのですから
- 応答時間- データはどのくらいの速さで取得できますか?レイテンシが低いほど、アプリケーションの反応が速くなります
どちらの指標も重要です。問題は、両者が相反する場合です。
RPCプロバイダの分析
1時間にわたってテストを行った2つのArbitrum RPCプロバイダーの実測データを見てみましょう。Arbitrumが約14,400ブロックを生成した期間中、各プロバイダーに対して約60件の同時リクエストを送信しました。
Arbitrumはおよそ250ミリ秒ごとに、つまり1秒あたり約4回、新しいブロックを生成します。この数値を覚えておいてください。
ブロック高の結果

テスト全体を通じて、グリーンは一貫してイエローより2~4ブロック先行するブロック数を記録しました。ここで分析を終わらせれば、グリーンが明らかな勝者のように見えます。
応答時間の結果
それでは、各プロバイダーの応答時間を確認してみましょう。

「緑」のプロバイダーは最新のブロックデータを提供しましたが、応答時間は遅く、安定していませんでした(時折速いこともありました)。場合によっては最大6秒かかることもありました。「黄色」のプロバイダーはデータがそれほど最新ではありませんでしたが、応答ははるかに速く、通常は200~400ミリ秒程度でした。
アプリの要件に応じて、選択は異なるかもしれません。最新のデータを取得することが最も重要であれば、やはりYellowプロバイダーを選ぶほうがよいかもしれません。
これらの結果は p95 の応答時間を示しており、つまり、リクエストのうち最も遅い上位 5% だけがそれほど遅かったことを意味します。次に、典型的な速度を確認するために、中央値を見てみましょう。

Yellowの応答時間は中央値で40~50ミリ秒です。Greenは200~400ミリ秒かかります。これは速度において5~10倍の差があります。
さて、ここでは相反するシグナルが見られます:
- 緑はブロック番号が大きい(新しいデータ)ことを示します
- 黄色は応答速度を向上させます(アクセスが速くなります)
では、実際にどのプロバイダーがより新しいデータを提供してくれるのでしょうか? まもなく明らかになるでしょう。
リクエストのライフサイクルの理解
アプリケーションが RPC リクエストを送信すると、データを受け取るまでにいくつかの手順が実行されます:
- DNS解決:プロバイダのサーバーの特定
- TCP接続:ネットワーク接続の確立
- TLSハンドシェイク:安全な通信の確立
- リクエストの処理:プロバイダーは自社のノードからデータを取得する
- ファーストバイトまでの時間(TTFB):最初の応答データの受信が始まる
- ダウンロード完了:完全な応答を受信しました
ここで重要な点は、ブロックチェーンの状態は、TLSハンドシェイクが完了した後、最初のバイトがユーザーに返送される前のステップ4において、プロバイダーによって取得されるということです。ユーザーが受け取る状態は、リクエストを送信した時点やレスポンスのダウンロードが完了した時点ではなく、その特定の瞬間のブロックチェーンの状態を反映したものです。
応答時間の内訳
それでは、両プロバイダーのリクエストライフサイクルの各ステップに費やされた時間の内訳を見てみましょう。左側が緑のプロバイダー、右側が黄色のプロバイダーです:

リクエストのライフサイクルは、下から上へと進行することに注意してください:DNS → TCP → TLS → ファーストバイト → ダウンロード時間。これらすべてが合計されて、総応答時間が算出されます。
状況を見てください。緑のプロバイダーは、現在のブロック高の検索を始める前に、TLSハンドシェイクを完了させるだけで約200msかかっています。一方、黄色のプロバイダーは、リクエスト全体を100ms未満で完了させています。
これは、次のような意味です:
- イエローがあなたに返信を送る頃には、グリーンはまだ最新のブロック高を「確認」し始めてすらない
- Greenがついにブロックチェーンの状態を取得した時点で、Arbitrumはすでに次のブロックを生成している可能性が高い(ブロックは約250msごとに生成されることを思い出してほしい)
- Yellowは、Greenが1つのリクエストを完了するのにかかる時間内に、3つのリクエストを処理し、3つの異なる連続したブロック高を返すこともできたはずだ
これをより鮮明に見るために、拡大してみましょう:

ここからあるパラドックスが浮かび上がります。Greenはより大きなブロック番号を返すものの、それはリクエストを行った時点でのブロックチェーンの状態を正確に表しているわけではありません。データがあなたに届く頃には、リクエストした時点に比べてすでに情報が古くなっているのです。
このことから、Greenのアプローチには3つの問題点が明らかになります。第一に、この遅延により、アプリケーションがブロックNの情報を必要としているにもかかわらず、GreenがN-1からN+2へとスキップしてしまうと、そのデータが失われてしまいます。第二に、ブロック番号は大きくなっても、データを受け取る頃にはすでに古くなっている可能性があります。第三に、アプリケーションはデータが「現在」の状態を表していると想定して動作していますが、ネットワークはすでにその先へ進んでしまっているのです。
Yellowは、データが要求された時点に比べてより迅速にデータを提供することで、リアルタイムアプリケーションにとってより有用なデータを提供します。このデータは、要求したその瞬間のネットワーク状態をより正確に反映しています。
データの鮮度とは何ですか?
ここで、「データの鮮度」という概念が登場します。これは、RPCプロバイダーを評価する際に本当に重要な要素を捉える指標です。
データの鮮度とは、RPCプロバイダーが新しいブロックチェーンの状態をどのくらいの速さで提供できるかを指します。これは単に最新のブロック番号を取得することでも、応答時間が速いことだけでもありません。それは、以下の要素間の関係に関するものです:
- リクエストを実行してからデータを受け取るまでの所要時間
- RPCプロバイダーによって取得された時点で、ブロックの状態がどれほど最新のものだったか
- ブロックチェーンのブロック生成レートに対するRPCプロバイダーのパフォーマンス
- プロバイダーが、ブロックチェーンの最新の状態をどのくらいの速さで取得・提供できるか
これは、Arbitrum、Solana、Baseといった、ブロックが非常に高速に生成される高スループット型ブロックチェーンにとって極めて重要です。ブロック生成時間が250ミリ秒であるのに対し、応答時間が500ミリ秒では、根本的にブロックチェーンにリアルタイムで追従することができません。1回の応答を得る間に2つのブロックが生成されてしまうため、常に遅れをとってしまい、データを見逃してしまうことになります。
スコアを用いたデータの新鮮度の測定
データの鮮度を定量化するために、応答時間をブロックチェーンの平均ブロック生成時間と比較する「レイテンシ鮮度スコア」を作成することができます:
Freshness Score = 1 - (latency / avgBlockTime)
この計算式により、1.0(瞬時の反応、例えば理論上の最大値)から負の値(ブロックの生成よりも遅い)までの正規化されたスコアが得られます。スコアが0の場合、反応時間が平均ブロック生成時間と完全に一致することを意味します。
これは実際にはどういう意味でしょうか?スコアが0より上であれば、ブロックが生成される速度よりも速く取得できており、次のブロックが到着する前に各ブロックを処理する余裕があります。スコアが高いほど、余裕も大きくなります。スコアがちょうど0の場合は、かろうじて追いついている状態で、余裕はまったくありません。0未満の場合は、根本的に遅れをとっており、ブロックチェーンがブロックを生成する速度が、あなたが取得できる速度を上回っています。
それでは、この「鮮度スコア」を、レイテンシの中央値を用いた「緑」と「黄色」の比較に適用してみましょう:

その違いは次のとおりです:
- Greenプロバイダー:スコアは0を超えることがなく、つまりArbitrumのブロック生成速度よりも一貫して応答が遅かったことを意味する。スコアは-1を下回ることもあったが、これは新しいブロックの生成速度に比べて2倍も遅かったことを示している。
- 黄色プロバイダー:0.8前後という一貫して高いスコアを維持しており、これは次のブロックが生成されるまでに十分な余裕を持ってデータを送信できたことを意味します。ブロックチェーンが次の段階に進む前に、アプリケーションが実際にデータを処理し、判断を下すことができます。
p95レイテンシ(処理速度が遅い外れ値のリクエストを考慮したもの)を用いてフレッシュネススコアを算出すると、状況はさらに明確になります:

両プロバイダーともp95ではパフォーマンスが低下していますが、Greenのマイナスのスコアははるかに顕著です。Yellowでさえ、最も遅いリクエストでは-1を下回っており、ネットワークの混雑時や高負荷時には、高速なプロバイダーであっても処理に苦労することがあることがわかります。
結論:Yellowがより低いブロック番号を返したのは、まさにデータ提供がより迅速であり、リクエストのライフサイクルの早い段階でブロックチェーンの状態を最新の状態として取得していたためである。 一方、Greenがより高いブロック番号を返したのは、接続の確立(TLSハンドシェイクなど)に時間がかかりすぎたため、ようやくブロックチェーンの状態をキャプチャした時点では、すでにさらに数ブロックが生成されていたからです。ブロック番号は高いものの、リアルタイムのデータを必要とするアプリケーションにとっては、客観的に見てYellowの方が優れたプロバイダーです。Yellowは、リクエストした時点のブロックチェーンの状態を正確に反映したデータを配信し、その速度も十分速いため、アプリケーションがブロックの生成ペースに追いつくことができます。
独自のテストスイートの構築
それでは、RPCプロバイダーを自分でテスト・比較するためのツールを作成してみましょう。応答時間やブロック高を測定し、フレッシュネススコアを算出するスクリプトを作成します。
プロジェクトの設定
まず、新しい Node.js プロジェクトを作成し、必要な依存関係をインストールします:
mkdir rpc-comparison
cd rpc-comparison
npm init -y
npm install ethers
テストスクリプトの作成
「」という名前の新しいファイルを作成します。 compare-rpc.js:
const { ethers } = require('ethers');
// Configuration
const PROVIDERS = {
provider1: 'YOUR_FIRST_RPC_URL',
provider2: 'YOUR_SECOND_RPC_URL'
};
const CHAIN_BLOCK_TIME = 250; // milliseconds (adjust for your chain)
const TEST_DURATION = 60; // minutes
const REQUEST_INTERVAL = 60; // seconds between requests
// Store results
const results = {
provider1: { blockHeights: [], latencies: [], timestamps: [] },
provider2: { blockHeights: [], latencies: [], timestamps: [] }
};
// Measure request with detailed timing
async function measureRequest(providerUrl) {
const startTime = Date.now();
try {
const provider = new ethers.JsonRpcProvider(providerUrl);
const blockNumber = await provider.getBlockNumber();
const endTime = Date.now();
return {
blockNumber,
latency: endTime - startTime,
timestamp: startTime
};
} catch (error) {
console.error('Request failed:', error.message);
return null;
}
}
// Run comparison test
async function runTest() {
console.log('Starting RPC provider comparison...\n');
const testStart = Date.now();
const testEnd = testStart + (TEST_DURATION * 60 * 1000);
while (Date.now() < testEnd) {
// Make concurrent requests to both providers
const [result1, result2] = await Promise.all([
measureRequest(PROVIDERS.provider1),
measureRequest(PROVIDERS.provider2)
]);
// Store results
if (result1) {
results.provider1.blockHeights.push(result1.blockNumber);
results.provider1.latencies.push(result1.latency);
results.provider1.timestamps.push(result1.timestamp);
}
if (result2) {
results.provider2.blockHeights.push(result2.blockNumber);
results.provider2.latencies.push(result2.latency);
results.provider2.timestamps.push(result2.timestamp);
}
console.log(`Provider 1: Block ${result1?.blockNumber}, Latency ${result1?.latency}ms`);
console.log(`Provider 2: Block ${result2?.blockNumber}, Latency ${result2?.latency}ms\n`);
// Wait before next request
await new Promise(resolve => setTimeout(resolve, REQUEST_INTERVAL * 1000));
}
analyzeResults();
}
// Calculate statistics
function calculateStats(values) {
const sorted = [...values].sort((a, b) => a - b);
const median = sorted[Math.floor(sorted.length / 2)];
const p95 = sorted[Math.floor(sorted.length * 0.95)];
const avg = values.reduce((a, b) => a + b, 0) / values.length;
return { median, p95, avg };
}
// Calculate freshness score
function calculateFreshnessScore(latency) {
return 1 - (latency / CHAIN_BLOCK_TIME);
}
// Analyze and display results
function analyzeResults() {
console.log('\n=== Analysis Results ===\n');
Object.keys(results).forEach(providerName => {
const data = results[providerName];
const latencyStats = calculateStats(data.latencies);
console.log(`${providerName.toUpperCase()}:`);
console.log(` Total Requests: ${data.latencies.length}`);
console.log(` Latency (median): ${latencyStats.median.toFixed(2)}ms`);
console.log(` Latency (p95): ${latencyStats.p95.toFixed(2)}ms`);
console.log(` Latency (avg): ${latencyStats.avg.toFixed(2)}ms`);
const medianFreshness = calculateFreshnessScore(latencyStats.median);
const p95Freshness = calculateFreshnessScore(latencyStats.p95);
console.log(` Freshness Score (median): ${medianFreshness.toFixed(3)}`);
console.log(` Freshness Score (p95): ${p95Freshness.toFixed(3)}`);
console.log();
});
}
// Run the test
runTest().catch(console.error);
スクリプト内の設定変数を更新してください:
- 置換
YOUR_FIRST_RPC_URLそしてYOUR_SECOND_RPC_URL実際のRPCエンドポイントを使用して - 調整する
CHAIN_BLOCK_TIMEご利用のブロックチェーンの場合(Arbitrum:250ms、Ethereum:約12000ms、Solana:約400ms) - 変更
TEST_DURATIONそしてREQUEST_INTERVAL必要に応じて
次に、以下を実行してください:
node compare-rpc.js
結果の分析
このスクリプトは、リアルタイムでの比較結果を表示し、最後に要約分析を提供します。
Provider 1: Block 386487336, Latency 164ms
Provider 2: Block 386487339, Latency 261ms
Provider 1: Block 386487358, Latency 204ms
Provider 2: Block 386487360, Latency 301ms
Provider 1: Block 386487378, Latency 263ms
Provider 2: Block 386487381, Latency 262ms
Provider 1: Block 386487400, Latency 200ms
Provider 2: Block 386487402, Latency 312ms
Provider 1: Block 386487420, Latency 254ms
Provider 2: Block 386487424, Latency 323ms
Provider 1: Block 386487443, Latency 169ms
Provider 2: Block 386487445, Latency 298ms
Provider 1: Block 386487463, Latency 241ms
Provider 2: Block 386487466, Latency 269ms
=== Analysis Results ===
PROVIDER1:
Total Requests: 27
Latency (median): 227.00ms
Latency (p95): 368.00ms
Latency (avg): 243.19ms
Freshness Score (median): 0.092
Freshness Score (p95): -0.472
PROVIDER2:
Total Requests: 27
Latency (median): 316.00ms
Latency (p95): 589.00ms
Latency (avg): 356.07ms
Freshness Score (median): -0.264
Freshness Score (p95): -1.356
上記のデータを見ると、プロバイダー2は一貫してより大きなブロック番号(2~4ブロック先)を返しており、一見するとこちらの方が優れており、より「新しい」データを持っているように見えます。
しかし、プロバイダー1の方が著しく高速であり(中央値で約113ms、p95で約221ms速い)、これが重要な知見です。プロバイダー2がより高いブロック番号を返すのは、応答時間が遅いため、リクエストのライフサイクルにおいてより遅い時点のブロックチェーンの状態をキャプチャしているからです。 一方、プロバイダー1はより早い段階で状態を取得(TLS/接続が高速)していますが、それをより迅速にユーザーに提供しています。ブロック番号は低くなりますが、そのデータはリクエストを行った時点のチェーンの状態をより正確に反映しています。
RPCの利用を最適化する
データの鮮度に関する要件に基づき、以下の戦略をご提案します:
最新のデータが必要な用途については:
- 鮮度スコアが 0.5 を超えるプロバイダーを優先する
- 応答時間の中央値を監視する
- リアルタイムの更新には、WebSocketのサブスクリプションの利用をご検討ください
- アプリケーションから地理的に近いプロバイダーを選択してください
注:このガイドで解説するレイテンシ・フレッシュネス・フレームワークは、HTTPベースのRPCリクエストに適用されます。WebSocketのサブスクリプションは、新しいブロックが生成されるたびにデータをクライアントにプッシュするという異なる仕組みで動作するため、リクエスト/レスポンスのサイクルやそれに伴うレイテンシの問題が解消されます。
ハイスループット用途向け:
- 一貫して低遅延なプロバイダーを利用する
- 接続プールの実装
- 必要に応じてレスポンスをキャッシュする
- 複数のプロバイダーにわたる負荷分散を検討してください
一般的なベストプラクティス:
- プロバイダーを定期的に評価する
- 経時的な劣化を監視する
- バックアッププロバイダーを準備しておく
- ヘルスチェックと自動フェイルオーバーを利用する
よくある質問
ブロックチェーンRPCのパフォーマンスにおける「データの鮮度」とは何ですか?
データの鮮度は、ブロックが生成された時点に対して、RPCプロバイダーがブロックチェーンの状態をどれほど迅速に配信するかを示す指標です。これは、返されるブロック番号だけでなく、応答時間と、データが取得された時点での最新度も考慮に入れています。
RPCプロバイダーを比較する際、ブロック番号が大きいと誤解を招くことがあるのはなぜでしょうか?
より高いブロック番号を返すプロバイダーは、単に応答時間が遅いだけであり、つまりリクエストのライフサイクルの後半でブロックチェーンの状態をキャプチャしていることを意味します。ブロック番号が低く、応答が速いプロバイダーは、多くの場合、リクエストを行った時点のチェーンの状態をより正確に反映したデータを提供します。
レイテンシ・フレッシュネス・スコアはどのように算出されるのですか?
鮮度スコアの計算式は、1 - (レイテンシ / 平均ブロック生成時間) です。スコアが 0 より大きい場合は、ブロックの生成ペースよりも速くブロックを取得できていることを意味し、スコアが 0 の場合は、生成ペースに追いついていることを意味し、負のスコアの場合は、ブロックチェーンの生成ペースに遅れをとっていることを示します。
RPCプロバイダのパフォーマンスを比較する際の主要な指標にはどのようなものがありますか?
主な指標は、ブロック高さ(どのブロック番号が返されるか)と応答時間(レイテンシ)の2つです。ただし、これらを個別に評価するのではなく、データの鮮度という観点から総合的に評価する必要があります。
まとめ
データの鮮度を理解することで、RPCプロバイダーを選択する際に、より適切な判断を下すことができます。ブロック番号が大きいプロバイダーが必ずしも優れているとは限りません。重要なのは、ブロックが生成された時点から、どれほど迅速かつ確実にブロックチェーンデータにアクセスできるかということです。 従来の指標に加えて、レイテンシや鮮度スコアを測定することで、プロバイダーのパフォーマンスを包括的に把握し、アプリケーションの具体的なニーズに最適な選択肢を選定することができます。
ご質問がある場合は、ぜひDiscordにご参加いただくか、Twitterからご連絡ください。
皆様からのフィードバックを心よりお待ちしております!❤️
ご意見や新しいトピックに関するご要望などがありましたら、ぜひお知らせください。皆様からのご連絡を心よりお待ちしております。
