How to Benchmark Solana RPC Providers: Latency, Errors, and Slot Freshness
Compare Solana RPC endpoints with a repeatable test plan for method latency, error rate, success rate, and slot freshness. Learn what to measure, how to avoid misleading results, and how to use findings in production.
A useful Solana RPC benchmark compares endpoints under the same conditions and records more than one response-time number. Measure representative RPC methods, successful and failed responses, and slot freshness from the client region your application actually uses. The result is a decision aid for capacity planning and safe failover—not a universal claim that one endpoint will always be fastest.
What to measure
Use the same method, parameters, commitment level, client region, and sample count for every endpoint in one test run.
| Metric | What it tells you | Why it matters |
|---|---|---|
| End-to-end latency | Time from your client sending a request to receiving a response | Shows the experience your application sees, including network and provider response time |
| P50, P95, and P99 latency | Typical, slower, and tail response-time distribution | A low average can hide occasional slow calls that affect production workflows |
| Success rate | Share of requests that return a valid result in the test window | Shows whether fast responses are also consistently usable |
| Error and timeout rate | Failed HTTP or JSON-RPC calls, plus client-side timeouts | Separates performance issues from reliability or request-shape issues |
| Slot freshness | How far an endpoint's observed slot is from a chosen reference at the same measurement time | Helps identify whether low latency is paired with current chain data |
Do not compare a cached response from one endpoint with an uncached response from another. Do not change commitment levels, retry behavior, payload size, or the client region halfway through a comparison.
Choose representative RPC methods
Start with read-only calls that resemble your application's real workload. A small baseline can include:
getLatestBlockhashfor a lightweight chain-state read;getSlotto record the endpoint's observed slot;getBalancefor a simple account read; and- one application-relevant read method with a realistic payload size.
Keep transaction submission out of the first benchmark. A transaction test has different risks: it can create on-chain side effects, result in duplicate submission if retries are careless, and mix transaction-landed behavior with ordinary RPC-read latency. Treat it as a separate, controlled test only after you have a clear test-wallet and confirmation plan.
Run a repeatable baseline
The following TypeScript example measures a small, sequential read-only sample. It is a baseline for comparing endpoints—not a load test. Use endpoints and credentials from environment variables; never paste production keys into a benchmark script or CI logs.
const endpoints = [
process.env.RPC_A_URL!,
process.env.RPC_B_URL!,
];
const calls = [
{ name: "getLatestBlockhash", params: [{ commitment: "confirmed" }] },
{ name: "getSlot", params: [{ commitment: "processed" }] },
];
type Sample = {
endpoint: string;
method: string;
ok: boolean;
ms: number;
slot?: number;
error?: string;
};
async function rpcCall(endpoint: string, method: string, params: unknown[]) {
const started = performance.now();
try {
const response = await fetch(endpoint, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
});
const payload = await response.json();
const ms = performance.now() - started;
if (!response.ok || payload.error) {
return { endpoint, method, ok: false, ms, error: JSON.stringify(payload.error ?? response.status) };
}
return {
endpoint,
method,
ok: true,
ms,
slot: method === "getSlot" ? payload.result : undefined,
};
} catch (error) {
return {
endpoint,
method,
ok: false,
ms: performance.now() - started,
error: error instanceof Error ? error.message : String(error),
};
}
}
const samples: Sample[] = [];
for (const endpoint of endpoints) {
for (let i = 0; i < 50; i++) {
for (const call of calls) {
samples.push(await rpcCall(endpoint, call.name, call.params));
}
}
}
console.table(samples);
For an initial comparison, run the same test from the same runner more than once at different times. Record the endpoint name privately, the client region, the exact methods and commitments, sample count, timeout policy, and whether any retries occurred.
Calculate percentiles and success rate
For each endpoint and method, calculate percentiles from successful samples and report failed samples separately. Do not silently drop errors from a latency calculation.
function percentile(values: number[], p: number) {
const sorted = [...values].sort((a, b) => a - b);
if (sorted.length === 0) return null;
const index = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);
return sorted[index];
}
function summarize(samples: Sample[]) {
const successful = samples.filter((sample) => sample.ok);
const latencies = successful.map((sample) => sample.ms);
return {
requests: samples.length,
successRate: samples.length ? successful.length / samples.length : 0,
p50Ms: percentile(latencies, 50),
p95Ms: percentile(latencies, 95),
p99Ms: percentile(latencies, 99),
failures: samples.filter((sample) => !sample.ok),
};
}
A threshold such as a maximum P95 is an internal operating decision, not a universal Solana RPC standard. Choose thresholds based on the latency budget and failure tolerance of your application, then revisit them when your workload or deployment region changes.
Measure slot freshness carefully
getSlot can help you observe whether endpoints are reporting similar chain progress at the same time. Record each endpoint's slot alongside the timestamp and compare it with a reference sampled immediately before or after the call.
A reference is not ground truth; it can be delayed too. Use the measurement to identify a pattern that needs investigation, not to make a permanent freshness claim from a single test.
For a useful comparison:
- sample the reference and candidate endpoints as close together as practical;
- record commitment level for every call;
- repeat across normal and busy periods; and
- investigate persistent differences before changing application routing.
Separate baseline tests from capacity tests
A sequential baseline tells you how an endpoint behaves for a small, controlled workload. It does not prove how the endpoint behaves at your production concurrency or request volume.
Before increasing concurrency:
- read the documented plan limits for every endpoint you test;
- use a dedicated test key and a controlled runner;
- start below documented limits and increase gradually;
- record 429s, timeouts, and error payloads rather than retrying in a hot loop; and
- stop a test when it risks affecting production traffic or breaching a provider's documented policy.
For Carbium-specific rate-limit behavior and safe 429 handling, read Solana RPC Rate Limits Explained.
Use results safely in production
Do not automatically route production traffic based on a single fastest sample. A safe production design needs separate health checks, a defined failover policy, and transaction-aware retry behavior.
- Use benchmark results to choose candidates for deeper evaluation.
- Use health checks to detect current failures or degradation.
- Check transaction status before retrying a submission; do not resend only because a previous request timed out.
- Keep a clear record of which endpoint handled a request so failures can be investigated.
- Re-run the same benchmark after meaningful application, network, region, or endpoint changes.
See the Safe RPC Failover Checklist and RPC Errors Reference before implementing a production routing change.
Common benchmark mistakes
| Mistake | Better approach |
|---|---|
| Comparing one response from each endpoint | Use a repeated sample and report the distribution |
| Reporting an average only | Include P50, P95, P99, success rate, and failures |
| Treating a client-side timeout as proof of provider failure | Record the full request conditions and inspect the response/error path |
| Benchmarking from an unrelated region | Test from the region and network path your users or services use |
| Using automatic retries without recording them | Record retry policy and evaluate first-attempt and final outcomes separately |
| Retrying a transaction submission after a timeout | Check transaction status before deciding whether another submission is appropriate |
| Treating a short test as a permanent provider ranking | Repeat across relevant times and workloads; results are workload- and location-specific |
Frequently asked questions
Which metric identifies the fastest Solana RPC provider?
No single metric does. Compare the latency distribution for the methods your application uses, then consider success rate, error behavior, slot freshness, and the client region. A result from one method or one location is not a universal provider ranking.
Can I compare endpoints from different regions?
Yes, but include the client region in the result. A comparison from one runner measures that runner's network path as well as endpoint behavior. Test from each production-relevant region before making a routing decision.
Should an initial benchmark include sendTransaction?
No. Start with read-only methods. Transaction submission needs a separate, controlled plan because it can create on-chain side effects and a careless retry can produce duplicate sends.
How often should I rerun a benchmark?
Re-run after meaningful changes to application workload, deployment region, endpoint configuration, or routing policy. For recurring checks, keep the method set, sample size, runner, and timeout policy stable enough to compare results over time.