TL;DR: Passing a historical
blockNumbertoeth_call,eth_getBalanceoreth_getLogssilently routes your request to the archive tier of hosted RPC providers. In our production metrics, archive calls cost on average 26.7x more compute units than the same call atlatest. This post explains why, shows the exact client code pattern that triggers it, and gives you three Prometheus queries to measure your own archive exposure in under a minute. Full cross provider measurements are published in the call to read a token balance looks like this:
CODEimport { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({
chain: mainnet,
transport: http(process.env.RPC_URL)
})
// Path A, cheap: reads the current state.
const balance = await client.readContract({
address: TOKEN,
abi: erc20Abi,
functionName: 'balanceOf',
args: [WALLET]
})
// Path B, expensive: reads state at a specific historical block.
const historicalBalance = await client.readContract({
address: TOKEN,
abi: erc20Abi,
functionName: 'balanceOf',
args: [WALLET],
blockNumber: 18000000n
})
From a developer perspective the two calls are indistinguishable. Both return a
bigint. TheblockNumberparameter on the second call is what promotes the request to the archive tier on the provider side.
In our codebase we found this pattern in five categories of code paths:
Token balance backfills rebuilding historical wallet snapshots for analytics. Each snapshot iterates through blocks, callingbalanceOfat each snapshot point.
Pool state reconstruction for liquidity analytics, callinggetReserves()on Uniswap v2 pools orslot0()on Uniswap v3 pools at every historical trade block.
Bridge quote engines that need the exact token balance and allowance at the source block when validating a cross chain transfer.
Governance snapshots replaying votes at a historical block to verify quorum.
Audit tools and debuggers recomputing contract state at the block of a suspicious transaction to isolate the root cause of a bug.
None of these paths look expensive when you read them. They pass a single extra parameter to a familiar function call. But every one of those calls goes through the archive tier on the RPC provider.
How Alchemy, Chainstack and QuickNode price archive requests
Each provider publishes a compute unit or credit table in its public pricing documentation.
Alchemy documents its compute unit weights in the , typically between two and five times the equivalent full node call.
The observable behavior is consistent across all three. If you send the same request payload with and without a historical
blockNumberparameter, the request without it hits the fast path and the request with it hits a slower and more expensive path. On heavily loaded indexers this compounds fast, because each iteration of a backfill loop produces one archive request.
What we measured on a production grade cluster
We instrument every outbound RPC call in our services with a Prometheus counter carrying labels for provider, method, chain, and node type. Over a rolling 24 hour window on one chain that receives moderate traffic, the raw breakdown was as follows:
Method
Raw requests
Compute units
Effective ratio
eth_callarchive
2,470,000
65,970,000
26.7
eth_getLogsarchive
355,000
9,500,000
26.8
eth_getBalancearchive
49,200
1,315,000
26.7
eth_callfull
4,000
4,000
1.0
The pattern is clean. Full mode calls average one compute unit per raw request. Archive mode calls on the same methods average close to twenty seven compute units per raw request. The ratio is stable across
eth_call,eth_getLogs, andeth_getBalance, which are the three read paths that carry the vast majority of our traffic.
Translated into monthly cost on a moderately busy indexer on one chain, the archive share of that traffic drove roughly one thousand dollars of overage per month. Multiplied across five chains and three indexer instances, the number grows quickly into the tens of thousands per year.
How to detect archive exposure with 3 Prometheus queries
If you already record outbound RPC calls into a Prometheus counter, the following queries surface archive exposure in about thirty seconds.
Query 1. Absolute compute units by node type over 24 hours:
CODEsum by (node_type) (
increase(rpc_compute_units_total{provider="alchemy"}[24h])
)
Query 2. Archive share of total volume as a percentage:
CODEsum(rate(rpc_compute_units_total{provider="alchemy", node_type="archive"}[1h]))
/
sum(rate(rpc_compute_units_total{provider="alchemy"}[1h]))
* 100
Query 3. Top ten source services and methods contributing to archive volume:
CODEtopk(10,
sum by (app, method) (
increase(rpc_compute_units_total{provider="alchemy", node_type="archive"}[24h])
)
)
How to read the results:
Below 5 percent archive share: you are in good shape.
Between 5 and 20 percent: a specific backfill or indexer is doing more archive reads than it needs to, and the top ten query above will point at the culprit within seconds.
Above 50 percent: archive is your primary traffic pattern and the cost curve is dominated by the multiplier rather than by request volume, which usually means a stalled or looping job.
If you do not yet track this metric, the counter you want to add to your outbound HTTP transport takes labels for
provider,method,chain, andnode_type. Increment it once per response received, including 4xx and 5xx responses, because hosted providers bill errored calls just as they bill successful ones.
For a full working example of this instrumentation across twenty two chains and three geographic regions, the and the full approach is documented on the at
0xcA11bde05977b3631167028862bE2a173976CA11reduces the provider side accounting to one archive request instead of one hundred.
4. Materialize derived state in your own datastore. If your service repeatedly recomputes the same derived quantity from historical state, for example a token holder set at block N, write the result to your own Postgres or ClickHouse the first time and read from there for every subsequent access.
5. Split archive traffic onto a provider with a lower multiplier. The three providers price archive differently. If your workload is archive dominated, running a benchmark against Chainstack, QuickNode, and a self hosted archive node with the same request pattern will reveal cost differences worth thousands of dollars per month at production scale. The .
Are archive responses safe to cache?
Yes, for finalized blocks. State at a finalized block is immutable, so responses can be cached indefinitely with no staleness risk.
Key takeaways
The archive multiplier is not a design flaw of hosted RPC providers. It reflects the real cost of preserving twenty terabytes of historical state on hot storage and answering queries against it. The problem is that the mechanism is invisible from client code, so the cost accumulates behind an opaque call that looks routine in a code review.
Three actions pay off immediately:
- Instrument outbound RPC calls with a Prometheus counter that includes a
node_typelabel.
- Alert when the archive share of your total volume on any provider crosses five percent.
- Audit every code path that passes
blockNumberand either remove the parameter, cache the response, or batch through Multicall3.
Once the three are in place, the next overage alert arrives with the root cause already visible in your dashboard rather than requiring an emergency investigation across five services and two Postgres replicas.
If you want to see the full instrumentation applied to twenty two chains from three regions, . Benchmarks, methodology and raw data are available at openchainbench.com.
↗ Original-Artikel auf dev.to lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
The Archive Multiplier: Why eth_call at a Historical Block
- ▸ What does "archive" mean at the Ethereum node level?
- ▸ The exact client code that triggers archive pricing
- ▸ How Alchemy, Chainstack and QuickNode price archive requests
- ▸ What we measured on a production grade cluster
- ▸ How to detect archive exposure with 3 Prometheus queries
- ▸ 5 strategies to reduce archive RPC costs in production
- ▸ The observability gap in current web3 SDKs
- ▸ FAQ
- ▸ Key takeaways
SOCIAL SHARE CARD GENERATOR