A compute budget on Solana is the maximum number of compute units (CUs) a transaction may consume, plus the price you are willing to pay per requested CU. The budget is declared with ComputeBudget program instructions, and the priority fee is charged against the requested limit rather than actual usage, so over-requesting wastes money and under-requesting fails transactions. This article explains the mechanism, shows runnable code for setting and inspecting the budget, and gives a repeatable measurement procedure so you can size limits from observed data instead of guesswork. It also covers troubleshooting, tradeoffs, and where to go next for RPC access and monitoring.
What a compute budget controls
On Solana, every instruction consumes compute units, a metered unit of execution work. A transaction's compute budget is the ceiling on how many CUs the whole transaction may use. If execution exceeds the declared limit, the transaction fails even if the underlying program logic would otherwise succeed. The budget is therefore both a safety mechanism and a cost lever.
The budget has two independent parts. The compute unit limit caps execution work. The compute unit price sets how many lamports you pay per requested CU as a priority fee. These are configured through the ComputeBudget program, typically with SetComputeUnitLimit and SetComputeUnitPrice instructions placed at the start of the transaction. Treat the limit as a capacity declaration and the price as a bid for block inclusion; they serve different purposes and should be tuned separately.
This article focuses on the mechanism and a reproducible measurement method. For concrete endpoint and provider choices, use the RPC Assistant rather than hardcoding assumptions about which cluster or provider to use.
- Compute unit limit: maximum CUs the transaction may consume.
- Compute unit price: lamports per requested CU, used for priority fees.
- Requested limit, not actual usage, drives the priority fee calculation.
- Limits and prices are set with ComputeBudget program instructions.
Assumptions and prerequisites
The examples assume a Solana JSON-RPC endpoint reachable at RPC_URL, a funded keypair for sending transactions, and a client library that can build and sign transactions. The JavaScript examples use @solana/web3.js; the Python examples use solana-py and solders. Version APIs change, so verify method names against the library version you install.
You should also know which cluster you are targeting. Devnet and mainnet have different fee markets and different observed CU usage for the same program, so measure on the cluster where you intend to deploy. If you are still choosing an endpoint, the guide to the best Solana RPC provider and the RPC endpoints guide explain what to evaluate.
npm install @solana/web3.js
# or
pip install solana soldersHow the limit and price interact
The runtime executes instructions in order and deducts CUs as work is performed. The declared limit is checked against cumulative usage. If the transaction finishes under the limit, it succeeds and the unused headroom is simply not used. If it exceeds the limit, it fails. This means the limit must be at least as large as the worst-case path your transaction can take, including any conditional branches inside invoked programs.
The priority fee is computed from the requested limit multiplied by the requested price, not from actual consumption. A transaction that requests a very high limit and uses a fraction of it still pays priority fees on the full requested amount. Over-requesting is therefore a direct cost, while under-requesting causes failures. The practical goal is a limit slightly above observed worst-case usage, paired with a price that reflects current network conditions.
Because the fee depends on the requested limit, tuning the limit and tuning the price are coupled in cost but independent in mechanism. Lower the limit to reduce fees per transaction, then adjust price to compete for inclusion. Measure both separately so you can attribute changes correctly.
Setting the compute budget in JavaScript
The following example builds a transaction with an explicit limit and price. Replace RPC_URL, PRIVATE_KEY, and the instruction with your own. The helper functions create the ComputeBudget instructions and prepend them to the transaction.
Run this against a test transaction first. The goal is to confirm the instructions are accepted and to observe the resulting signature and confirmation behavior before applying the pattern to production traffic.
import {
Connection, Keypair, Transaction, TransactionInstruction,
ComputeBudgetProgram, sendAndConfirmTransaction, PublicKey
} from '@solana/web3.js';
import bs58 from 'bs58';
const connection = new Connection('RPC_URL', 'confirmed');
const payer = Keypair.fromSecretKey(bs58.decode('PRIVATE_KEY'));
const CU_LIMIT = 200_000; // requested limit
const MICRO_LAMPORTS_PER_CU = 1_000; // price per CU
const ix = new TransactionInstruction({
programId: new PublicKey('YourProgram111111111111111111111111111111'),
keys: [],
data: Buffer.alloc(0),
});
const tx = new Transaction().add(
ComputeBudgetProgram.setComputeUnitLimit({ units: CU_LIMIT }),
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: MICRO_LAMPORTS_PER_CU }),
ix
);
const sig = await sendAndConfirmTransaction(connection, tx, [payer]);
console.log('confirmed', sig);Setting the compute budget in Python
The Python path uses solders for the ComputeBudget instructions and solana-py for the client. The structure mirrors the JavaScript example: build the instructions, prepend them, sign, and send. Keep the limit and price as named constants so you can change them in one place during measurement.
If your client library exposes a simulation method, use it before sending. Simulation reports consumed CUs without committing the transaction, which is the fastest way to gather sizing data.
from solana.rpc.api import Client
from solana.rpc.commitment import Confirmed
from solana.transaction import Transaction
from solders.keypair import Keypair
from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price
from solders.instruction import Instruction
from solders.pubkey import Pubkey
client = Client('RPC_URL')
payer = Keypair.from_base58_string('PRIVATE_KEY')
CU_LIMIT = 200_000
MICRO_LAMPORTS_PER_CU = 1_000
ix = Instruction(Pubkey.from_string('YourProgram111111111111111111111111111111'), b'', [])
tx = Transaction().add(
set_compute_unit_limit(CU_LIMIT),
set_compute_unit_price(MICRO_LAMPORTS_PER_CU),
ix,
)
tx.recent_blockhash = client.get_latest_blockhash(Confirmed).value.blockhash
tx.sign(payer)
resp = client.send_transaction(tx)
print(resp.value)Measuring actual compute usage
Do not size limits from intuition. Simulate the transaction and read the reported units consumed. The simulation response includes the number of CUs the transaction used, which is the empirical input to your limit decision. Run the simulation across representative inputs, including edge cases and the largest payloads your program accepts.
A defensible procedure is: (1) collect a sample of real or realistic transactions; (2) simulate each one and record consumed CUs; (3) compute the maximum and a high percentile; (4) set the limit above the maximum with a small safety margin; (5) re-simulate with the chosen limit to confirm success. Record the sample size and the cluster, because results are not portable across clusters or program versions.
The results table below is a template. Fill it with your own measured values; do not treat any number here as a benchmark.
const sim = await connection.simulateTransaction(tx, [payer]);
console.log('err', sim.value.err);
console.log('unitsConsumed', sim.value.unitsConsumed);
console.log('logs', sim.value.logs);Results table template for limit sizing
Use a table like the following to document your measurement run. The columns separate the input case from the observed usage and the decision, so a reviewer can reproduce the reasoning.
| Case | Input size | Simulated CUs | Max observed | Chosen limit | Margin |
|---|---|---|---|---|---|
| Small payload | (record) | (record) | (record) | (record) | (record) |
| Typical payload | (record) | (record) | (record) | (record) | (record) |
| Largest payload | (record) | (record) | (record) | (record) | (record) |
| Edge case | (record) | (record) | (record) | (record) | (record) |
The margin column is the difference between the chosen limit and the maximum observed usage. A margin that is too small risks failures when program behavior changes; a margin that is too large raises priority fees on every transaction. Choose the margin deliberately and revisit it when the program or its dependencies change.
Troubleshooting common compute budget failures
Most compute budget problems fall into a few patterns. The transaction fails with an exceeded compute budget error, which means the limit is below actual usage. The transaction confirms but costs more than expected, which usually means the limit is far above usage. Or the transaction is slow to confirm despite a high price, which points to fee market conditions rather than the limit itself.
When a transaction fails for exceeded budget, simulate it without a limit override to see the natural consumption, then raise the limit above that value. When costs are high, lower the limit toward observed usage and re-measure. When confirmation is slow, adjust the price independently and compare confirmation times across a sample rather than a single transaction.
Also check instruction ordering. ComputeBudget instructions should appear before the instructions they configure. If they are placed after, the configuration may not apply as intended.
- Exceeded budget: raise the limit above simulated usage.
- Unexpected cost: lower the limit toward observed usage.
- Slow confirmation: tune price separately from limit.
- Misapplied config: verify ComputeBudget instructions come first.
Limitations and tradeoffs
Compute budget tuning is empirical and environment-specific. A limit that works on devnet may be wrong on mainnet, and a limit that works today may fail after a program upgrade. Treat measured limits as versioned configuration, not constants. Re-measure after any change to the program, its dependencies, or the instruction data shape.
There is also a cost tradeoff. A generous limit protects against failures but raises priority fees because fees are based on the requested limit. A tight limit reduces fees but increases failure risk. The right balance depends on how costly a failed transaction is for your application and how predictable your inputs are.
Finally, the compute budget is only one part of transaction economics. Network conditions, account contention, and block space all affect inclusion. For a broader view of infrastructure choices, see dedicated nodes and RPC pricing.
Next steps for production monitoring
Once you have a measured limit, instrument your client to log simulated and actual usage per transaction. Compare the two over time; divergence indicates a program change or an input case you did not sample. Alert when actual usage approaches the chosen limit, because that is the signal to re-measure.
For endpoint selection and reliability planning, review the API service overview and the networks you operate on. If you are comparing providers for high-volume transaction submission, the RPC Assistant can help you choose a concrete endpoint; keep the measurement methodology here as the source of truth for your limits.
A practical next step is to build a small script that simulates a batch of representative transactions, writes the results to a table, and prints the recommended limit. Run it on a schedule and after every program deployment so your compute budget stays aligned with reality.