An open and honest benchmark for blockchain indexers. Every number below comes from code in this repository, so you can run it yourself and check, and the tables are refreshed automatically by scheduled CI runs.
If you want to know how the numbers are produced, or what a column means, that is all in METHODOLOGY.md.
The benchmark started in May 2025 as a fork of Sentio's research. That repository was later closed, so Envio picked it up and has kept it current since. We are not affiliated with Sentio, and although the project now lives under the Envio organisation — its data is what the Envio landing page and the Blockchain Indexers in 2026 article cite — the point of it is a fair comparison.
Contributions are welcome — we already have some from the SQD team. Open an issue or a pull request to add an indexer, add a scenario, report a result that looks wrong, or improve the methodology. Indexer teams especially: nobody knows your tool better than you do. Or just come and ask on Discord or Telegram.
What does an indexer do when something goes wrong? These scenarios restart its database mid-write, rewrite the chain underneath it, make the node it reads from fail, and hand it values that are legal but awkward, then check what ended up in the database. They also time how long a new block takes to become readable. Every check runs on a generated chain, so no credentials are needed.
| tool | source | crash recovery | reorgs | rpc faults | data fidelity | head latency | overall |
|---|---|---|---|---|---|---|---|
| Envio Indexer | RPC | 11/11 ✅ (2 restarts) | 7/7 ✅ | 16/16 ✅ | 6/6 ✅ | 4/4 ✅ (7ms, WS) | 44 / 44 |
| Envio Subgraph | RPC | 11/11 ✅ (2 restarts) | 7/7 ✅ | 16/16 ✅ | 6/6 ✅ | 4/4 ✅ (7ms, WS) | 44 / 44 |
| Squid SDK | RPC | 11/11 ✅ (2 restarts) | 7/7 ✅ | 12/16 🔴 | 6/6 ✅ | 4/4 ✅ (7ms, WS) | 40 / 44 |
| Subgraph | RPC | 9/11 🔴 (no restarts) | 7/7 ✅ | 13/14 🔴 | 6/6 ✅ | 4/4 ✅ (901ms) | 39 / 42 |
| Ponder | RPC | 8/9 🔴 (no restarts) | 7/7 ✅ | 14/16 🔴 | 5/6 🔴 | 4/4 ✅ (12ms, WS) | 38 / 42 |
| SubQuery | RPC | 11/11 ✅ (no restarts) | 0/7 🔴 | 13/14 🔴 | 6/6 ✅ | 3/4 🔴 (3.8s) | 33 / 42 |
| Rindexer | RPC | 11/11 ✅ (no restarts) | 1/7 🔴 | 8/14 🔴 | 6/6 ✅ | 4/4 ✅ (159ms) | 30 / 42 |
What failed, and what it means for you - 32 failing checks across 5 tools
- Squid SDK
- rpc faults
- Stops indexing: crashes when the RPC node errors or times out
- Stops indexing: does not resume after the RPC node recovers
- Stops indexing: crashes when the RPC provider fails some of its requests
- Stops indexing silently: stops following the chain when its block subscription goes quiet
- rpc faults
- Subgraph
- crash recovery
- Stops indexing: never recovers after the database restarts mid-sync (only 1 of 3 runs)
- Slow deploys: ignores the shutdown signal for over 15 seconds and gets force-killed
- rpc faults
- Stops indexing: does not resume after the RPC node recovers
- not tested: keeps following the chain when its block subscription goes quiet
- not tested: fills in the blocks it was never told about
- crash recovery
- Ponder
- crash recovery
- Stops indexing: never recovers after the database restarts mid-sync
- not tested: keeps picking up new blocks after the database restarts
- not tested: recovers by itself when an unresponsive database comes back
- rpc faults
- Missing data: deletes rows when an RPC node briefly reports an older block
- Stops indexing silently: stops following the chain when its block subscription goes quiet
- data fidelity
- Missing data: drops events with very large log indexes, which some providers emit
- crash recovery
- SubQuery
- reorgs
- Stale data: keeps an event after the chain replaced its block (only 2 of 3 runs)
- Stale data: keeps blocks from a fork the chain abandoned for a shorter one
- Stale data: keeps an event the chain removed
- Wrong data: carries on after a chain rewrite deeper than it can undo, instead of stopping
- Stale data: misses a chain rewrite that happened while it was offline
- Wrong data: loses track when the chain rewrites several times in a row
- Stale data: misses a chain rewrite in blocks it was still syncing
- rpc faults
- Wrong balances: counts a transfer twice when the RPC node sends it twice (only 2 of 3 runs)
- not tested: keeps following the chain when its block subscription goes quiet
- not tested: fills in the blocks it was never told about
- head latency
- Stale reads: new data usually takes longer than one block to show up
- reorgs
- Rindexer
- reorgs
- Stale data: keeps an event after the chain replaced its block
- Stale data: keeps blocks from a fork the chain abandoned for a shorter one
- Stale data: keeps an event the chain removed
- Wrong data: carries on after a chain rewrite deeper than it can undo, instead of stopping
- Wrong data: loses track when the chain rewrites several times in a row
- Stale data: misses a chain rewrite in blocks it was still syncing
- rpc faults
- Stops indexing: does not resume after the RPC node recovers
- Stops indexing: never catches up after a spell of flaky RPC
- Stops indexing: cannot cope with a provider's response-size limit
- Wrong balances: counts a transfer twice when the RPC node sends it twice
- Missing data: skips a block the RPC node briefly failed to return
- Stops indexing: crashes when a block it asked for has just been replaced
- not tested: keeps following the chain when its block subscription goes quiet
- not tested: fills in the blocks it was never told about
- reorgs
Each cell is the checks a tool passed out of the checks it was asked. The number in brackets is a measurement beside the score, not part of it.
What every check means, and how to run it →
How well does an indexer cope with data it has to read back? Every rETH transfer changes a balance, so for each one the indexer has to find the right row, update it, and save it again. The scenario follows the benchmark on the Ponder landing page.
| tool | source | events/s | blocks/s | vs best | data | storage |
|---|---|---|---|---|---|---|
| Envio Indexer | HyperSync | 6,007.9 | 46,273.3 | — | ✅ | Postgres 2.2 MB |
| Envio Subgraph | HyperSync | 5,396.1 | 43,461.0 | 1.1x slower | ✅ | Postgres 2.2 MB |
| Envio Indexer | RPC | 2,432.0 | 19,654.4 | 2.5x slower | ✅ | Postgres 2.2 MB |
| Rindexer | HyperSync | 982.5 | 7,927.4 | 6.1x slower | ✅ | Postgres 5.2 MB |
| Squid SDK | SQD Network | 679.8 | 5,124.4 | 8.8x slower | ✅ | Postgres 2.2 MB |
| Envio Subgraph | RPC | 268.3 | 2,355.1 | 22.4x slower | ✅ | Postgres 2.2 MB |
| Rindexer | RPC | 229.0 | 2,047.4 | 26.2x slower | ✅ | Postgres 5.0 MB |
| Ponder | RPC | 49.6 | 653.3 | 121.1x slower | ✅ | Postgres 3.3 MB |
| Subgraph | RPC | 34.7 | 457.2 | 173x slower | ✅ | Postgres 6.9 MB |
| Substreams | StreamingFast | 28.7 | 377.8 | 209.3x slower | ✅ | Postgres 2.2 MB |
| SubQuery | RPC | 25.2 | 331.5 | 238.1x slower | ❓ (1) | Postgres ~4.4 MB |
| Squid SDK | RPC | 16.6 | 187.3 | 361.7x slower | ❓ (2) | Postgres ~2.4 MB |
(1) SubQuery — missing 0.22% of the data: the verification range was not finished within 300s (2) Squid SDK — missing 34% of the data: the verification range was not finished within 300s
How this case works, and how to run it →
How fast can an indexer write? Every USDC transfer is stored once, with nothing to aggregate and nothing to look up first. This is the ingestion path on its own.
| tool | source | events/s | blocks/s | vs best | data | storage |
|---|---|---|---|---|---|---|
| Rindexer | HyperSync | 97,821.2 | 10,129.6 | — | ✅ | Postgres 3.4 MB |
| Envio Indexer | HyperSync | 64,756.2 | 7,048.5 | 1.5x slower | ✅ | Postgres 1.4 MB |
| Envio Subgraph | HyperSync | 38,253.0 | 4,145.7 | 2.6x slower | ✅ | Postgres 1.4 MB |
| Squid SDK | SQD Network | 15,470.5 | 1,813.0 | 6.3x slower | ✅ | Postgres 1.4 MB |
| Envio Indexer | RPC | 12,393.6 | 1,474.0 | 7.9x slower | ✅ | Postgres 1.4 MB |
| Rindexer | RPC | 7,447.7 | 934.9 | 13.1x slower | ✅ | Postgres 3.4 MB |
| Substreams | StreamingFast | 2,653.1 | 310.7 | 36.9x slower | ✅ | Postgres 1.4 MB |
| Squid SDK | RPC | 1,451.3 | 183.3 | 67.4x slower | ✅ | Postgres 1.4 MB |
| Envio Subgraph | RPC | 1,306.5 | 166.2 | 74.9x slower | ✅ | Postgres 1.4 MB |
| Ponder | RPC | 230.5 | 29.4 | 424.3x slower | ✅ | Postgres 2.5 MB |
| Subgraph | RPC | 111.2 | 13.7 | 879.8x slower | ✅ | Postgres 2.8 MB |
| SubQuery | RPC | 26.8 | 3.3 | 3655.9x slower | ❓ (1) | Postgres ~2.0 MB |
(1) SubQuery — missing 0.36% of the data: the verification range was not finished within 300s
How this case works, and how to run it →
Not everything an indexer needs is in the logs. Every approval on the eight busiest ERC-20s is followed by a read of the allowance at that block: 15,703 calls, 200ms each, answered by the benchmark so every tool waits the same. Nothing limits how many a tool may have outstanding, so the rows differ by how many of those waits it takes at once.
| tool | source | events/s | blocks/s | vs best | data | storage |
|---|---|---|---|---|---|---|
| Envio Indexer | HyperSync | 12,715.6 | 838.4 | — | ✅ | Postgres 8.4 MB |
| Squid SDK | SQD Network | 12,374.6 | 815.8 | — | ✅ | Postgres 8.3 MB |
| Envio Indexer | RPC | 11,100.3 | 728.7 | 1.1x slower | ✅ | Postgres 8.2 MB |
| Rindexer | HyperSync | 7,728.9 | 495.5 | 1.6x slower | ✅ | Postgres 7.3 MB |
| Rindexer | RPC | 7,362.5 | 473.0 | 1.7x slower | ✅ | Postgres 7.3 MB |
| Envio Subgraph | HyperSync | 2,149.4 | 153.3 | 5.9x slower | ✅ | Postgres 8.6 MB |
| Envio Subgraph | RPC | 1,282.3 | 79.4 | 9.9x slower | ✅ | Postgres 8.6 MB |
| Squid SDK | RPC | 838.7 | 44.4 | 15.2x slower | ✅ | Postgres 8.3 MB |
| Subgraph | RPC | 71.8 | 4.5 | 177.1x slower | ✅ | Postgres 20.3 MB |
| Ponder | RPC | 34.8 | 2.5 | 365.7x slower | ❓ (1) | Postgres ~11.8 MB |
| SubQuery | RPC | 4.9 | 0.3 | 2611.4x slower | ❓ (2) | Postgres ~16.6 MB |
| Substreams | StreamingFast | — | — | — | — (3) | — |
(1) Ponder — missing 45% of the data: the verification range was not finished within 300s (2) SubQuery — missing 92% of the data: the verification range was not finished within 300s (3) Substreams — its contract calls run against the Substreams server's own node, not a given endpoint
How this case works, and how to run it →
What happens when you do not know the contracts up front? The indexer watches the Safe proxy factories, and every one of the 82,268 proxies they create becomes another contract it has to follow from that moment on.
| tool | source | events/s | blocks/s | vs best | data | storage |
|---|---|---|---|---|---|---|
| Envio Indexer | HyperSync | 9,435.5 | 3,330.9 | — | ✅ | Postgres 14.0 MB |
| Envio Subgraph | HyperSync | 8,280.9 | 2,923.3 | 1.1x slower | ✅ | Postgres 14.0 MB |
| Envio Indexer | RPC | 6,931.4 | 2,446.9 | 1.4x slower | ✅ | Postgres 14.0 MB |
| Rindexer | HyperSync | 6,024.1 | 2,126.6 | 1.6x slower | ✅ | Postgres 11.4 MB |
| Envio Subgraph | RPC | 4,075.6 | 1,438.8 | 2.3x slower | ✅ | Postgres 14.0 MB |
| Squid SDK | SQD Network | 4,056.7 | 1,451.3 | 2.3x slower | ❌ (1) | Postgres 13.8 MB |
| Rindexer | RPC | 3,987.5 | 1,407.7 | 2.4x slower | ✅ | Postgres 11.4 MB |
| Squid SDK | RPC | 352.7 | 124.5 | 26.7x slower | ❌ (2) | Postgres 13.8 MB |
| Substreams | StreamingFast | 266.7 | 66.5 | 35.4x slower | ❓ (3) | Postgres ~14.5 MB |
| Ponder | RPC | 265.9 | 59.2 | 35.5x slower | ❓ (4) | Postgres ~25.4 MB |
| Subgraph | RPC | 60.7 | 27.2 | 155.4x slower | ❓ (5) | Postgres ~36.8 MB |
| SubQuery | RPC | 0.0 | 0.0 | — | ❓ (6) | — |
(1) Squid SDK — 921 of 927 safe setups missing; 10 of 11 fallback handler changes missing; 211 of 293 module enables missing (2) Squid SDK — 921 of 927 safe setups missing; 10 of 11 fallback handler changes missing; 211 of 293 module enables missing (3) Substreams — missing 5.6% of the data: the verification range was not finished within 300s (4) Ponder — missing 6.1% of the data: the verification range was not finished within 300s (5) Subgraph — missing 79% of the data: the verification range was not finished within 300s (6) SubQuery — indexed nothing in 300s, so there was no data to verify
How this case works, and how to run it →
Every USDC transfer on Solana, through the chain's busiest program. Solana makes that harder than it sounds: transfers hide inside swaps and routers, and many never say which token they moved. The scenario follows StreamingFast's SPL token Substreams.
| tool | source | events/s | blocks/s | vs best | data | storage |
|---|---|---|---|---|---|---|
| Envio Indexer | HyperSync | 25,146.8 | 394.7 | — | ✅ | Postgres 37.8 MB |
| Squid SDK | SQD Network | 8,867.8 | 278.0 | 2.8x slower | ✅ | Postgres 37.7 MB |
| Substreams | StreamingFast | 3,856.5 | 126.9 | 6.5x slower | ✅ | Postgres 62.7 MB |
| Carbon | RPC | 563.2 | 18.9 | 44.6x slower | ✅ | Postgres 40.4 MB |
How this case works, and how to run it →
Six scenarios from the original 2025 research, kept here for reference. They are total sync times rather than throughput rates, and they predate the current methodology, so do not compare them with the tables above.
| Case | Sentio | Envio HyperSync | Envio HyperIndex | Ponder | Subsquid | Subgraph | Sentio_Subgraph | Goldsky_Subgraph |
|---|---|---|---|---|---|---|---|---|
| case_1_lbtc_event_only | 8m | 3m | 1h40m | 10m | 3h9m | 2h36m | ||
| case_2_lbtc_full | 6m | 1m | 45m | 34m | 1h3m | 56m | ||
| case_3_ethereum_block | 18m | 7.9s | 33m | 1m‡ | 10m | 15m | ||
| case_4_on_transaction | 17m | 1m26s | 33m | 7m | N/A | |||
| case_5_on_trace | 16m | 41s | N/A§ | 2m | 8m | 1h21m | ||
| case_6_template | 19m | 8s | 21m | 2m | 19m | 10m | 20h24m |
Want to try it yourself? Each scenario page above has its own setup instructions, or you can run the whole suite the way CI does:
ENVIO_API_TOKEN=your-token SQD_API_KEY=your-key node scripts/run-benchmarks.tsArguments are passed straight through, so node scripts/run-benchmarks.ts envio ponder --duration=100 picks which indexers to run and how long the window is, and --cases=erc20-transfer-events narrows it to one scenario. You will need an Envio API token for the RPC endpoint and the ground truth; the SQD key is only needed for the Squid SDK run that reads from SQD Network.