Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Network & Protocol Guides12 min read

Base RPC Latency: Measuring, OP-Stack Factors, and Optimizing Query Speed

Learn how to measure Base RPC latency, understand OP-Stack factors like block time and L1 settlement, and optimize query speed with practical techniques.

TL;DR

Base RPC latency is dominated by geographic distance, endpoint saturation, and OP-Stack data path. This guide explains how to measure it with a reproducible script and optimize query speed using WebSocket subscriptions, batching, caching, and pagination.

What Determines Base RPC Latency?

Base RPC latency is the time between sending a JSON-RPC request and receiving a response. It is not a single number; it varies by method, endpoint, region, and network conditions. The dominant factors are geographic distance to the endpoint, shared public endpoint saturation, and the OP-Stack data path (op-node and op-reth). Base's ~2-second block time also sets a realistic cadence for polling or subscriptions.

For most applications, the latency you experience is a combination of network round-trip time (RTT) and server processing time. A public endpoint in another continent can add 100-200 ms of RTT, while a saturated endpoint can add seconds of queueing. Understanding these factors helps you choose the right endpoint and optimize your queries.

Geographic distance is often the most visible component. When your client is in Europe and the RPC server is in North America, each request incurs at least one transatlantic round trip. Under the TCP protocol, the handshake alone adds one RTT, and TLS adds another. For a typical HTTPS JSON-RPC call, you can expect at least two RTTs before the request body is even sent. This is why geo-distributed endpoints or edge caching can dramatically reduce perceived latency.

Endpoint saturation is a second major factor. Public endpoints like those offered by free providers are shared among thousands of users. When demand spikes, requests queue in the server's connection pool, and the effective latency can balloon from tens of milliseconds to several seconds. Rate limiting (HTTP 429) is a common symptom. Dedicated endpoints, such as those provided by OnFinality's API service, allocate resources exclusively to your workload, providing consistent performance.

The OP-Stack data path is the third pillar. Base runs op-node (consensus) and op-reth (execution). When you send a JSON-RPC request, it is routed to the appropriate component. For state reads like eth_call or eth_getBalance, op-reth queries its local database. The database is optimized for fast reads, but heavy queries like eth_getLogs over a wide block range can still take hundreds of milliseconds or more. The op-node also syncs with L1 (Ethereum) for data availability and finality, which affects how fresh the data is.

Method weight is a fourth factor that is often underestimated. Light methods like eth_blockNumber or eth_chainId are served from memory and return in under 10 ms on a healthy node. Heavy methods like eth_getLogs or eth_getProof can take orders of magnitude longer because they scan large amounts of data. The Base JSON-RPC methods documentation lists the available methods and their parameters, but it does not specify performance characteristics. You must measure them yourself.

  • Geographic distance: The physical distance between your client and the RPC endpoint directly affects network RTT. Use geo-distributed endpoints to minimize this.
  • Endpoint saturation: Public endpoints are shared; heavy usage by others can cause queuing and rate limiting. Dedicated endpoints provide consistent performance.
  • OP-Stack data path: Base uses op-node (consensus) and op-reth (execution). Requests are processed by op-reth, which reads from its local database. L1 settlement affects finality and freshness of data.
  • Method weight: Light methods like eth_blockNumber are fast; heavy methods like eth_getLogs over large ranges are slow. Choose methods wisely.

OP-Stack Architecture and Its Impact on Latency

Base is an OP-Stack L2 with a ~2-second block time. The stack separates consensus (op-node) and execution (op-reth). When you send a JSON-RPC request, it goes to the op-node (or directly to op-reth if using the execution endpoint), which processes it against the local state. The op-node also syncs with L1 (Ethereum) for data availability and finality.

L1 settlement affects how fresh the data is. Base blocks are considered 'safe' after a short delay, but 'finalized' only after L1 confirmation. If your application requires finalized data, you may need to wait longer, which increases effective latency. For real-time applications, you can use 'safe' or 'latest' blocks, but be aware of reorg risk.

The block time of ~2 seconds means that new blocks are produced frequently. Polling eth_blockNumber every 2 seconds is reasonable, but WebSocket subscriptions (eth_subscribe) are more efficient for real-time updates, as they push events without polling overhead.

The op-node maintains a view of the L2 chain and communicates with L1 to fetch deposit information and batch data. When you request a block by number, the op-node may need to verify that the block is canonical. This verification is usually fast, but under L1 congestion, it can add latency. For most use cases, the execution layer is the bottleneck, not the consensus layer.

The execution layer, op-reth, uses a custom database layout optimized for Ethereum-style workloads. It stores state as a Merkle Patricia Trie, but with optimizations for fast reads. However, certain operations, such as iterating over logs, require scanning blocks and decoding receipts. The cost scales with the number of blocks and the number of logs. For example, an eth_getLogs query over 10,000 blocks with a popular contract can return megabytes of data and take several seconds.

To understand the full data path, consider a typical eth_getBalance call. The request arrives at the RPC endpoint, which forwards it to the op-node. The op-node identifies the block number (e.g., 'latest') and passes the request to op-reth. op-reth looks up the account state in its database, which may involve reading multiple nodes of the Merkle trie. The result is then serialized and sent back. Each step adds microseconds to milliseconds, but the network RTT dominates for remote clients.

  • L1 settlement: 'safe' blocks are available quickly, but 'finalized' blocks require L1 confirmation, adding latency.
  • Block time: ~2 seconds means new blocks are frequent; use WebSocket subscriptions to avoid polling overhead.
  • Execution layer: op-reth handles state reads; heavy queries like eth_getLogs can be slow.
  • Consensus layer: op-node syncs with L1; under L1 congestion, verification may add latency.

How to Measure Base RPC Latency: A Reproducible Script

To measure latency accurately, you need a script that tests multiple methods and measures both sequential and concurrent performance. The following Node.js script uses the built-in fetch API (Node 18+) to send JSON-RPC requests to a given endpoint. It measures latency for eth_chainId, eth_blockNumber, eth_getBalance, eth_getLogs, and eth_call. Run it with node measure-latency.js <RPC_URL>.

This script is measurement guidance, not a vendor benchmark. Results vary by region, endpoint, and network conditions. Fill in the results table below to compare endpoints.

The script uses process.hrtime.bigint() for high-resolution timing. It sends a single request for each method sequentially, then sends 5 parallel requests for each method to measure concurrency. The output includes the HTTP status code and the first 100 characters of the response body, which helps diagnose errors.

For a more robust measurement, you should run the script multiple times at different times of day and from different regions. You can also modify the script to test WebSocket connections or to use a specific block number for eth_getLogs. The key is to be consistent in your methodology so that comparisons are meaningful.

When interpreting results, note that the first request to a new endpoint may be slower due to DNS resolution and TLS handshake. It is advisable to warm up the connection by sending a few requests before measuring. The script does not do this automatically, but you can add a warm-up loop if needed.

// measure-latency.js
const url = process.argv[2];
if (!url) { console.error('Usage: node measure-latency.js <RPC_URL>'); process.exit(1); }

const methods = [
  { name: 'eth_chainId', params: [] },
  { name: 'eth_blockNumber', params: [] },
  { name: 'eth_getBalance', params: ['0x0000000000000000000000000000000000000000', 'latest'] },
  { name: 'eth_getLogs', params: [{ fromBlock: '0x0', toBlock: '0x10', address: '0x0000000000000000000000000000000000000000' }] },
  { name: 'eth_call', params: [{ to: '0x0000000000000000000000000000000000000000', data: '0x' }, 'latest'] }
];

async function measure(method, params) {
  const start = process.hrtime.bigint();
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
  });
  const end = process.hrtime.bigint();
  const ms = Number(end - start) / 1e6;
  const text = await res.text();
  return { ms, status: res.status, body: text.slice(0, 100) };
}

async function sequential() {
  console.log('Sequential measurements:');
  for (const m of methods) {
    const r = await measure(m.name, m.params);
    console.log(`${m.name}: ${r.ms.toFixed(2)} ms (HTTP ${r.status})`);
  }
}

async function concurrent() {
  console.log('\nConcurrent measurements (5 parallel requests per method):');
  for (const m of methods) {
    const times = await Promise.all(Array(5).fill().map(() => measure(m.name, m.params)));
    const avg = times.reduce((a, b) => a + b.ms, 0) / times.length;
    console.log(`${m.name}: avg ${avg.toFixed(2)} ms`);
  }
}

sequential().then(concurrent).catch(err => { console.error(err); process.exit(1); });

// Expected output shape:
// Sequential measurements:
// eth_chainId: 12.34 ms (HTTP 200)
// eth_blockNumber: 15.67 ms (HTTP 200)
// ...
// Concurrent measurements (5 parallel requests per method):
// eth_chainId: avg 13.45 ms
// ...

Results Table: Fill in Your Measurements

Use the table below to record your measurements for different endpoints and regions. This helps you compare providers and choose the best one for your workload. Remember that results vary by time of day and network conditions.

When filling the table, be sure to note the exact time and date, as well as the network conditions (e.g., during a major NFT mint, latency may spike). Also record the client's geographic location and the endpoint's location if known. This context is crucial for interpreting the numbers.

For a more comprehensive comparison, you can also test WebSocket connections and measure the time to receive a subscription notification. This is especially relevant for real-time applications. The script above does not cover WebSocket, but you can extend it using the ws library.

  • Endpoint: The RPC URL you tested.
  • Region: Your location or the endpoint's location.
  • Method: The JSON-RPC method tested.
  • Sequential latency: Average of 5 sequential requests.
  • Concurrent latency: Average of 5 parallel requests.

Common Failures and Fixes

When measuring or using Base RPC, you may encounter timeouts, rate limits, or inconsistent results. Here are common issues and how to fix them.

If you get HTTP 429 (Too Many Requests), you are hitting rate limits. Use a dedicated endpoint or reduce request frequency. If you get timeouts, increase your client timeout or use a closer endpoint. For heavy methods like eth_getLogs, paginate by block range to avoid timeouts.

Another common issue is receiving a JSON-RPC error response with code -32005 (limit exceeded) or -32000 (server error). These often indicate that the request is too large or the node is overloaded. For eth_getLogs, you can reduce the block range or filter by address to limit the result set. For eth_call, you can use eth_estimateGas first to ensure the call is valid.

Inconsistent results can also stem from the node's sync status. If the node is still syncing, it may return stale data or errors. Check the eth_syncing method to see if the node is fully synced. If it returns false, the node is in sync; otherwise, it is still catching up.

Finally, be aware of the difference between 'latest', 'safe', and 'finalized' block tags. Using 'latest' may give you the most recent block, but it could be reorged. Using 'safe' or 'finalized' reduces reorg risk but adds latency. Choose the appropriate tag based on your application's requirements.

  • Timeout: Increase client timeout or use a dedicated endpoint with lower latency.
  • Rate limit: Use a dedicated endpoint or batch requests to reduce count.
  • Heavy method: Page eth_getLogs by block window (e.g., 1000 blocks per request).
  • Data freshness: Use 'safe' or 'finalized' tags if you need settled data, but be aware of latency tradeoffs.
  • Sync status: Check eth_syncing to ensure the node is fully synced.

Optimizing Query Speed: Best Practices

To reduce effective latency, consider the following optimizations. First, use WebSocket (eth_subscribe) for real-time newHeads and logs instead of polling. This eliminates polling overhead and pushes data as soon as it's available. Second, batch multiple JSON-RPC requests into a single HTTP request to reduce round trips. Third, cache state reads (e.g., eth_call results) for a short TTL to avoid repeated calls. Finally, page eth_getLogs by block window to avoid heavy responses.

For trading or indexing applications, a geo-distributed or dedicated endpoint is recommended. Public endpoints are convenient but can be saturated. OnFinality's API service offers dedicated endpoints with predictable performance, and RPC pricing is transparent.

Batching is particularly effective when you need to make multiple independent calls. For example, if you need to fetch balances for 100 addresses, you can send a single JSON-RPC batch request with 100 eth_getBalance calls. This reduces the number of HTTP round trips from 100 to 1, cutting latency dramatically. Most RPC providers support batching, but be aware of response size limits.

Caching is another powerful technique. If you are reading the same state frequently (e.g., a token price), you can cache the result for a few seconds. This is especially useful for eth_call, which can be expensive. Use a short TTL (e.g., 5-10 seconds) to balance freshness and performance.

For eth_getLogs, always specify a block range and, if possible, an address or topic filter. This reduces the amount of data scanned and the response size. If you need to scan a large range, break it into smaller windows (e.g., 1000 blocks) and process them sequentially or in parallel. This also helps avoid timeouts and rate limits.

Finally, consider using a load balancer or failover strategy. If you rely on a single RPC endpoint, you risk downtime. Use multiple endpoints and implement health checks to automatically switch to a healthy one. This is especially important for production applications.

  • Use WebSocket subscriptions for real-time data.
  • Batch requests to reduce round trips.
  • Cache state reads with a short TTL.
  • Page eth_getLogs by block window.
  • Choose a dedicated endpoint for production workloads.
  • Implement failover with multiple endpoints and health checks.

Tradeoffs and Limitations

Measuring latency is not a one-time task; it should be part of your monitoring. However, there are limitations. Third-party benchmarks like comparenodes provide independent rankings but use their own methodology and may not reflect your region or workload. Always measure your own endpoints.

Also, latency is not the only metric. Reliability and rate limits matter. See our guides on Base RPC timeouts and retries and Base RPC rate limits and reliability for more.

Another limitation is that latency measurements can be skewed by network jitter and server-side caching. Some providers cache responses to popular methods like eth_blockNumber, which can make them appear faster than they actually are. To get a true picture, test methods that are not cached, such as eth_getBalance with a random address.

Finally, remember that latency is only one part of the equation. Throughput, error rates, and data freshness are equally important. A low-latency endpoint that returns stale data is not useful. Always verify that the data is up-to-date and accurate.

  • Third-party benchmarks may not reflect your region or workload.
  • Latency is not the only metric; consider reliability and rate limits.
  • Caching can skew measurements; test uncached methods.
  • Verify data freshness and accuracy.

Next Steps

Now that you understand Base RPC latency, you can apply these techniques to your application. For more context, explore the Base network guide and the OnFinality Learn hub. If you need a dedicated endpoint, check the Base RPC endpoint settings in RPC Assistant.

Remember to measure your own latency regularly and adjust your strategy as your workload evolves. Set up monitoring to track latency and error rates over time. Use the script provided in this guide as a starting point, and customize it to your specific needs.

For further reading, refer to the official Base documentation for the latest information on RPC methods and network parameters. The OP-Stack documentation also provides insights into the architecture and performance characteristics.

  • Regularly measure latency and monitor trends.
  • Customize the measurement script for your workload.
  • Consult official documentation for updates.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started