How to Compare Dedicated vs Shared Node Access for Solana RPC

A practical method for measuring whether dedicated or shared Solana RPC access fits your workload, using a runnable Node.js health-check script and honest measurement instead of vendor claims.

The short answer: you cannot compare dedicated and shared Solana RPC access from a spec sheet. You compare them by running the same read-only workload against each endpoint you are considering and recording latency, error rate, and rate-limit behavior under your own traffic. This guide gives you a concrete developer task: build a small Node.js 18+ probe that exercises the RPC methods your app actually uses, then interpret the results. It also draws a line that trips up many teams: RPC access is not indexing, and a fast RPC endpoint does not replace a query layer.

What you are actually comparing

Dedicated and shared node access differ in who else is using the underlying node. With shared access, your requests share capacity with other users, so your observed latency and error rate depend partly on their traffic. With dedicated access, the node is provisioned for your workload, which usually means more predictable behavior and higher cost. Neither arrangement is universally faster; the right choice depends on your request mix, concurrency, and tolerance for variance.

Because these properties are workload-dependent, any comparison must be measured on your side. Do not accept a benchmark you did not run. Instead, define the methods your application calls (for example getSlot, getBalance, getAccountInfo, sendTransaction, or getSignaturesForAddress), then measure them against each candidate endpoint with realistic concurrency.

Keep the scope honest: this task measures RPC request behavior only. It does not measure indexing or historical query performance, which is a separate concern covered later in this guide.

  • Shared access = capacity shared with other users; dedicated access = capacity provisioned for you.
  • Compare using your own method mix and concurrency, not a generic benchmark.
  • Measure latency distribution and error rate, not a single average.
  • Treat any vendor number as unverified until you reproduce it.

Build a read-only probe in Node.js 18+

The script below sends JSON-RPC requests to a Solana endpoint you supply via an environment variable. It never hardcodes an endpoint, so you can point it at any candidate. It records per-request latency, counts failures, and prints a summary. Run it against each endpoint you are evaluating, ideally at the same time of day and with the same concurrency.

Node.js 18 ships with a global fetch, so no dependencies are required. Save the file as probe.mjs and run it with your endpoint and a concurrency level.

  • Use environment variables for the endpoint so nothing is hardcoded.
  • Run the same script and concurrency against every candidate.
  • Repeat runs at different times to see variance, which is the key signal for shared vs dedicated.
JavaScript · Node.js
// probe.mjs — Node.js 18+
// Usage: SOLANA_RPC_URL=https://your-endpoint CONCURRENCY=5 REQUESTS=50 node probe.mjs

const url = process.env.SOLANA_RPC_URL;
const concurrency = Number(process.env.CONCURRENCY ?? 5);
const total = Number(process.env.REQUESTS ?? 50);

if (!url) {
  console.error('Missing SOLANA_RPC_URL environment variable.');
  process.exit(1);
}

// Methods your app actually uses. Adjust to match your workload.
const calls = [
  { method: 'getSlot', params: [] },
  { method: 'getHealth', params: [] },
  { method: 'getBalance', params: ['11111111111111111111111111111111'] },
];

async function rpc(body, timeoutMs = 10000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  const started = performance.now();
  try {
    const res = await fetch(url, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ jsonrpc: '2.0', id: 1, ...body }),
      signal: controller.signal,
    });
    const ms = performance.now() - started;
    if (res.status === 429) {
      return { ok: false, ms, reason: 'rate_limited' };
    }
    if (!res.ok) {
      return { ok: false, ms, reason: `http_${res.status}` };
    }
    const json = await res.json();
    if (json.error) {
      return { ok: false, ms, reason: `rpc_${json.error.code ?? 'error'}` };
    }
    return { ok: true, ms };
  } catch (err) {
    const ms = performance.now() - started;
    const reason = err.name === 'AbortError' ? 'timeout' : 'network';
    return { ok: false, ms, reason };
  } finally {
    clearTimeout(timer);
  }
}

async function worker(queue, results) {
  while (queue.length > 0) {
    const body = queue.shift();
    results.push(await rpc(body));
  }
}

const queue = Array.from({ length: total }, (_, i) => calls[i % calls.length]);
const results = [];
await Promise.all(Array.from({ length: concurrency }, () => worker(queue, results)));

const latencies = results.filter(r => r.ok).map(r => r.ms).sort((a, b) => a - b);
const failures = results.filter(r => !r.ok);
const pct = p => latencies[Math.min(latencies.length - 1, Math.floor(p / 100 * latencies.length))];

console.log(JSON.stringify({
  endpoint: url,
  requests: results.length,
  ok: latencies.length,
  failed: failures.length,
  failureReasons: failures.reduce((acc, f) => {
    acc[f.reason] = (acc[f.reason] ?? 0) + 1;
    return acc;
  }, {}),
  latencyMs: latencies.length ? {
    p50: Math.round(pct(50)),
    p95: Math.round(pct(95)),
    max: Math.round(latencies[latencies.length - 1]),
  } : null,
}, null, 2));

The script handles the failures that matter in practice: HTTP 429 rate limiting, non-2xx responses, JSON-RPC error objects, timeouts via AbortController, and network errors. Each is counted by reason so you can tell a rate-limit problem from a latency problem.

Expected result: a JSON summary with request counts, failure reasons, and p50/p95/max latency. On a healthy endpoint you should see zero failures and a tight latency spread. A shared endpoint under contention often shows more 429s or a wider p95 relative to p50.

Reading the results without overclaiming

Run the probe several times against each candidate, at different times of day, and compare the distributions. The signal you are looking for is variance: if p95 is far above p50 and 429s appear under your normal concurrency, the endpoint is likely sharing capacity with other traffic. If latency is stable and failures are near zero, the access arrangement is holding up for your workload.

Do not convert these numbers into a general verdict about a provider. Your results describe your method mix, your region, and your concurrency at that moment. Another team with different traffic may see different results. Report your own measurements and re-run them when your workload changes.

If you need a starting point for Solana network details and endpoint configuration, see the Solana network page. For background on how RPC fits into a Web3 stack, the OnFinality Learn library covers the fundamentals.

  • Compare p95 to p50 across repeated runs; high variance suggests shared contention.
  • Count 429s at your real concurrency, not an artificially low one.
  • Re-measure after any change in traffic pattern or method mix.
  • Never generalize one run into a provider-wide claim.

RPC access is not indexing or querying

A common mistake is to evaluate an RPC endpoint by how well it answers historical or aggregate questions. Solana RPC methods like getSignaturesForAddress and getAccountInfo return live chain state and recent history, but they are not a query engine. If you need to filter, aggregate, or join historical data, you need an indexing layer that ingests blocks and serves structured queries.

This distinction changes how you compare dedicated vs shared access. RPC comparison is about request latency, error rate, and rate-limit behavior for the methods your app calls. Indexing comparison is about data freshness, query flexibility, and how the indexer handles reorgs and backfills. Measure them separately, with different tools.

If your workload is mostly reads of live state, an RPC probe like the one above is the right instrument. If your workload is analytical or historical, an RPC probe will mislead you.

  • RPC serves live state and recent history; indexing serves structured historical queries.
  • Do not judge an RPC endpoint by aggregate or historical query performance.
  • Measure RPC with a request probe; measure indexing with query-level tests.
  • Pick the tool that matches your actual workload.

Your next step

Run the probe against the endpoints you are considering, at your real concurrency, and keep the JSON output for each. That gives you a defensible, reproducible comparison of dedicated vs shared access for your own workload. If you want help interpreting results or choosing an access model, the RPC Assistant is a reasonable place to continue, and the Solana network page has the network-level context you need before you configure anything.

Remember the boundary: this guide measures RPC request behavior only. It does not benchmark indexing, and it does not assert performance or support guarantees for any provider. Your own repeated measurements are the only comparison that counts.

  • Keep per-endpoint JSON outputs for a reproducible record.
  • Re-run the probe whenever your traffic or method mix changes.
  • Use RPC measurement for RPC decisions and separate tools for indexing decisions.