Solana getProgramAccounts Benchmark: Shyft’s Accelerated gPA vs. Standard Node

A closer look at gPA’s two-phase scan architecture, benchmarked against a standard Solana RPC node.

getProgramAccounts is one of the most frequently called — and least understood — bottlenecks in Solana application development. On a standard RPC node, it performs a full scan of every account owned by the target program on every request. For large programs, that means seconds of latency. For the largest ones, it means timeouts.

This post benchmarks Shyft’s Accelerated getProgramAccounts against a standard unmodified Solana node across four production DeFi programs. Numbers are presented as-is, including the cases where the standard node holds its own.

What Is getProgramAccounts and Why Is It Slow?

getProgramAccounts (gPA) is one of the most used — and most expensive — RPC methods on Solana. It lets you fetch all accounts owned by a given program, optionally filtered by account data using dataSize and memcmp filters.

The problem is structural. A standard Solana validator has no index over account data. When your request arrives, the node walks every single account owned by that program, reads its raw byte buffer, applies your filters, and returns the matches — one by one, on every request, regardless of how specific your filter is. For small programs with a few thousand accounts, this is fine. For programs like Pump.fun AMM or Raydium CLMM that own millions of accounts, you’re looking at seconds per call, if the node responds at all.

This isn’t a bug. It’s the architecture. Standard validators are optimized for consensus and block replay, not for read-heavy indexed queries.

What Shyft’s Accelerated gPA Does Differently

Shyft built a separate accounts engine that runs on dedicated hardware, completely decoupled from the RPC layer. The architecture is worth understanding because it explains why the numbers look the way they do:

  • gRPC ingestion streams account updates directly from the validator layer in real time, keeping the index current without polling.
  • RocksDB indexing writes account state into an embedded key-value store keyed by program + offset. Instead of scanning every account on every request — cost that grows linearly with how many accounts a program owns — a keyed lookup goes directly to the result regardless of program size.
  • Stateless JSON-RPC serves reads directly from RocksDB — no external calls, no network hops between components.

When a getProgramAccounts request arrives, the engine checks two things: is this program in the accelerated set, and does the memcmp offset match an indexed key? If yes to both, the result comes from RocksDB.

If either condition isn’t met, the request falls through to the standard scan path automatically — same response format, no error, no behavior change on your end. This is worth stating clearly: there’s no downside to pointing at an accelerated endpoint. Requests for non-indexed programs still work, they just don’t get the speed benefit.

No code changes required either way.

The engine currently covers 12 program + offset combinations including Pump.fun AMM, Raydium CLMM/AMM/CPMM, Meteora DLMM/DAMM, Orca Whirlpool, Drift, PancakeSwap CLMM, Address Lookup Table, and the Solana Stake Program. New indexes can be added on demand without downtime.

The Benchmark Setup

I used hey, a Go-based HTTP load testing tool, which is straightforward for JSON-RPC benchmarking since every Solana RPC call is just an HTTP POST. The setup:

hey -n 10 -c 1 -q 1 -m POST \ 
-H "Content-Type: application/json" \ 
-d '{ ...payload... }' \ 
<endpoint>
  • 10 requests, 1 concurrent, 1 req/s — representative of a real app making sequential gPA calls
  • Endpoints: rpc.ams.shyft.to (Amsterdam) vs a standard unmodified Solana node in Amsterdam. Tested from a baremetal in the AMS region.
  • Programs tested: Raydium CLMM (CAMM), Pump.fun AMM, Meteora DLMM, Address Lookup Table
  • hey keeps the TCP connection open after the first request, which is representative of real application behavior where connections are reused

I’m running these from the same machine to keep network conditions consistent between the two endpoints.

Results

Program 1: Raydium CLMM — CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK

Filter: dataSize: 1544 + memcmp offset 73

Request cURL:

curl -X POST https://rpc.ams.shyft.to/?api_key=YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getProgramAccounts",
  "params": [
    "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK",
    {
      "encoding": "base64",
      "filters": [
        { "dataSize": 1544 },
        { "memcmp": { "offset": 73, "bytes": "AHfNrqoVxeAc3zARfzngPk1QXYWyvKBowPQykhPZk8pP" } }
      ]
    }
  ]
}'

Each Solana account stores its data as a raw byte buffer. Programs define a fixed binary layout for that buffer — the same way a C struct lays out fields at known positions. Offset 73 in a Raydium CLMM pool account is where the token_mint_a field begins. Filtering at that offset means “give me all CLMM pools where the first token mint matches this address.” Shyft’s index is built around that specific byte position, so the lookup is direct rather than a scan.

MetricShyft AMSStandard Node
Avg18.6ms5,203ms
p5016.0ms5,281ms
p7523.0ms5,410ms
p9035.8ms5,567ms
Success10/1010/10
Meteora benchmarks
Meteora benchmarks image

~280× faster on average. The standard node returned all 10 responses — but every single one took 4.9–5.6 seconds. That’s not a fluke or a cold start; it’s consistent. Raydium CLMM owns a very large number of pool accounts, and the node is walking all of them on every call. Shyft’s index at offset 73 (token_mint_a) collapses this to a direct lookup.

Program 2: Pump.fun AMM — pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA

Filter: memcmp offset 43

Request cURL:

curl -X POST https://rpc.ams.shyft.to/?api_key=YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getProgramAccounts",
  "params": [
    "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
    {
      "encoding": "base64",
      "filters": [
        { "memcmp": { "offset": 43, "bytes": "T4T8I/7SrQyFRxwvhoh/IzjSO8qinIinMl3nI/a4le8=", "encoding": "base64" } }
      ]
    }
  ]
}'

Offset 43 in a Pump.fun AMM pool account is where the pool’s creator address is stored. The bytes value in the filter (T4T8I/7SrQyFRxwvhoh/IzjSO8qinIinMl3nI/a4le8=) is a base64-encoded public key — in this case a specific wallet address we’re filtering for. To use this for your own queries, replace that value with the base64-encoded public key you’re looking up. Most protocol SDKs will construct the correct filter for you automatically.

MetricShyft AMSStandard Node
Avg16.1ms16,729ms
p5015.3ms16,586ms
p7516.2ms17,670ms
p9023.2ms18,031ms
Success10/1010/10

~1,000× faster on average. The standard node did return all 10 responses this time — but every one took 15–18 seconds. Pump.fun AMM has too many accounts for a standard node to scan quickly, and the cost shows up directly in wall-clock time: over 16 seconds per call, on average, just to walk the program’s full account set and apply the filter.

Shyft returned all 10 results, averaging 16.1ms, with a p50 of 15.3ms. This is the clearest argument for indexed gPA in this benchmark: a query that resolves in milliseconds on the indexed path takes the better part of 20 seconds on a standard node — well outside what most applications can afford to wait on.

Program 3: Meteora DLMM — LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo

Filters: memcmp offset 88 (SOL, token_x) + memcmp offset 120 (USDC, token_y)

Request cURL:

curl -X POST https://rpc.ams.shyft.to/?api_key=YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getProgramAccounts",
  "params": [
    "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
    {
      "encoding": "base64",
      "filters": [
        { "memcmp": { "offset": 88, "bytes": "So11111111111111111111111111111111111111112" } },
        { "memcmp": { "offset": 120, "bytes": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } }
      ]
    }
  ]
}'

This one’s a two-filter query — pulling all DLMM pools where one side is SOL and the other is USDC. Offset 88 and offset 120 are where token_x and token_y are stored in a DLMM pool account, so the combined filter narrows to a specific pool pair rather than a single mint.

MetricShyft AMSStandard Node
Avg209.6ms2,104ms
p50208.9ms2,113ms
p75215.0ms2,157ms
p90217.6ms2,189ms
Success10/1010/10

~10× faster on every percentile. Shyft’s spread across all 10 requests was tight — 202–218ms — and the standard node’s was similarly tight at 2.0–2.2 seconds. Both sides are consistent; neither run is a fluke. The gap here is smaller than Raydium CLMM or Pump.fun AMM, but it’s a stable, repeatable 10× — not a one-off.

Program 4: Address Lookup Table — AddressLookupTab1e1111111111111111111111111

Filter: memcmp offset 22

Request cURL:

curl -X POST https://rpc.ams.shyft.to/?api_key=YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getProgramAccounts",
  "params": [
    "AddressLookupTab1e1111111111111111111111111",
    {
      "encoding": "base64",
      "filters": [
        { "memcmp": { "offset": 22, "bytes": "JEGkTJYGN4EAYjW238efuCK1J56AYwd9xnjaQrv5pwuD" } }
      ]
    }
  ]
}'
MetricShyft AMSStandard Node
Avg20ms4,895ms
p5016.0ms4,972ms
p7523.9ms5,050ms
p9053.5ms5,284ms
Success10/1010/10

~245× faster on average. Address Lookup Table has a smaller, more bounded account set than the DeFi programs above, but the standard node still has to walk it on every request — and that walk costs nearly 5 seconds per call. Shyft’s index collapses it to 16–54ms.

Reading the Results Honestly

The Accelerated gPA advantage holds across all four programs tested here, including Address Lookup Table — a program with a comparatively small, bounded account set where a standard scan might be expected to hold its own. It doesn’t: the standard node still pays the cost of a full walk on every request, regardless of how large or small that walk turns out to be.

The size of the win does vary. Raydium CLMM and Pump.fun AMM show the largest multipliers (~280× and ~1,000×) because their account sets are the largest — a full scan is expensive in direct proportion to how many accounts the program owns. Meteora DLMM shows a smaller but still consistent ~10× gap. The pattern across all four: the index removes the scan entirely, so the win scales with how expensive that scan would otherwise be.

Final Thoughts

getProgramAccounts is slow on standard Solana nodes because standard nodes aren’t built to be fast at it — they’re built to maintain consensus. For applications that make this call frequently, that’s a real architectural problem that no amount of retries or connection pooling fixes.

Shyft’s Accelerated gPA solves it at the right layer: a purpose-built accounts engine with a pre-built index, served from RocksDB, decoupled from the validator stack. Across all four programs tested — Raydium CLMM, Pump.fun AMM, Meteora DLMM, and Address Lookup Table — the indexed path wins. The margin varies: ~1,000× on Pump.fun AMM, ~280× on Raydium CLMM, ~245× on Address Lookup Table, ~10× on Meteora DLMM.

Notably, even Address Lookup Table — a program with a small, bounded account set, the kind of case where a standard scan should be competitive — shows a wide gap in Shyft’s favor. If your application touches the major DeFi protocols — and most Solana apps do — the case for indexed gPA is straightforward.

Resources