Solana VDS: pinned cores, isolated RAM, zero CPU steal time
When shared vCPU stops giving you consistent latency, this is the next rung: physical EPYC cores pinned to your VM, RAM on the same NUMA node, bandwidth committed, not borrowed. Same room as the validators, dedicated to you. <0.5 ms to every NLN Solana service. From $245/mo.
$ lscpu | grep -E "^(CPU\(s\)|Thread|Core)"CPU(s): 16Thread(s) per core: 1 # no shared hyperthreads$ numactl --hardware | head -2node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15node 0 size: 65536 MB$ stress-ng --cpu 16 -t 30 & mpstat -P ALL 5 6 | grep -i steal%steal: 0.00 0.00 0.00 0.00 0.00 0.00# every core, every sample. that is the contract.
Configure a VDS
Eight cores nobody else can touch. Steal time: 0.00% by contract.
- ·Physical cores pinned, single socket, no shared hyperthreads
- ·RAM allocated from the same NUMA node as your cores
- ·Committed bandwidth in the QoS class, not borrowed
- ·Dedicated NVMe namespaces, verifiable with lscpu / numactl / mpstat
- ·Provisioning: 2–3 minutes · capacity is held hot
- ·SLO: 99.99% monthly · host-failure migration target < 8 min
Not for: Bursty, mostly-idle workloads: a vps.pro does that for $55 less.
The indexer workhorse: ingest, decode, and Postgres on one NUMA node.
- ·Physical cores pinned, single socket, no shared hyperthreads
- ·RAM allocated from the same NUMA node as your cores
- ·Committed bandwidth in the QoS class, not borrowed
- ·Dedicated NVMe namespaces, verifiable with lscpu / numactl / mpstat
- ·Provisioning: 2–3 minutes · capacity is held hot
- ·SLO: 99.99% monthly · host-failure migration target < 8 min
Not for: Mainnet validators. The RAM ceiling and shared drives rule it out.
The top of virtualization. Past this point, buy the machine.
- ·Physical cores pinned, single socket, no shared hyperthreads
- ·RAM allocated from the same NUMA node as your cores
- ·Committed bandwidth in the QoS class, not borrowed
- ·Dedicated NVMe namespaces, verifiable with lscpu / numactl / mpstat
- ·Provisioning: 2–3 minutes · capacity is held hot
- ·SLO: 99.99% monthly · host-failure migration target < 8 min
Not for: Mainnet voting or >128 GB RAM: step up to a physical 64-core machine.
Cross-shopnln.metal.base: a full 64-core EPYC 9554P, 128 GB, physical NVMe, no hypervisor at all.
View nln.metal.base →You stop buying averages. You start buying the distribution.
VDS is what you graduate to when shared vCPU stops giving you consistent performance. A virtual dedicated server pins specific physical cores to your VM using cgroups, with RAM allocated from the same NUMA node, and network bandwidth committed in the QoS class rather than borrowed from a shared pool. You still SSH into a Linux box, but the performance distribution tightens because there is nobody else competing for the silicon. Three tiers in Frankfurt, all private-networked to NLN validator nodes with <0.5 ms to every NLN Solana service: vds.standard at $245/mo (8 dedicated EPYC cores at 3.0 GHz, 32 GB RAM, 2x 250 GB NVMe, 5 Gbps, 10 TB egress), vds.performance at $495/mo (16 dedicated EPYC cores at 3.0 GHz, 64 GB RAM, 2x 480 GB NVMe, 10 Gbps), and vds.max at $895/mo (32 dedicated EPYC cores, 128 GB RAM, 4x 480 GB NVMe, 10 Gbps). If your workload runs hot, runs constantly, or has a measurable PnL hit when the kernel scheduler twitches, this is the tier.
The mental model: a VPS sells you average performance; a VDS sells you the distribution. The silicon under your hot loop stops being a shared resource, the latency histogram loses its right tail, and a dedicated private VLAN to the NLN RPC and gRPC fleet comes with the box, 0.21 ms to the chain.
cgroup-pinned physical cores on one socket. Not "dedicated-ish", not a priority class on shared threads: pinned.
RAM is allocated from the same NUMA node as your cores. No cross-socket hops in the middle of a decode.
lscpu, numactl, mpstat. Every claim on this page can be checked from your own shell in 60 seconds.
Dedicated, with failure conditions you can test
“Dedicated resources” is the most abused phrase in hosting, so we wrote ours as a contract with failure conditions. Your cores are pinned via cgroups to specific physical cores, no shared hyperthreads, ever. Your RAM is allocated NUMA-local. Your bandwidth is committed in the QoS class, not borrowed from a pool. If we ever pack two VDS tenants onto the same physical core, that is a breach, and it is the kind we fix within the hour.
The enforcement mechanism is that you can catch us. Steal time on a VDS reads 0.00 every core, every sample, under any load. If you ever see a non-zero value, open a ticket: it means a host misconfiguration, and it jumps the queue.
lscpu | grep -E 'Thread|Core' # Thread(s) per core: 1 <- no shared HT numactl --hardware | head -3 # node 0 cpus: 0-15 # node 0 size: 65536 MB <- RAM with cores mpstat -P ALL 1 30 | grep -i steal # 0.00, every core, every sample
non-zero steal on a VDS = host config error = our pager, not your problem
Consistency is the product
Tail events are not slower on average, they are unpredictable. A neighbor bursts, your decode loop loses the cache, and the one frame you needed this slot arrives late. On a 400 ms slot, a 15 ms tail event inside the decision window is a missed trade your backtest says you won.
Three signals it's time to move up from VPS: steal time above 1% in peak hours, tail performance drifting on a consistent code path, or a workload that simply never idles. Any one of them, and the $55/mo step to vds.standard pays for itself with the first tail event it deletes.
Where VDS stops making sense
| vds.max | nln.metal.base | nln.metal.max | |
|---|---|---|---|
| Price | $895/mo | $1,227/mo | $1,902/mo |
| Compute | 32 pinned cores @ 3.0 GHz | 64 physical cores @ 3.1 GHz | 64 physical cores @ 3.3 GHz |
| RAM | 128 GB | 128 GB | 256 GB |
| Storage | 4 × 480 GB NVMe namespaces | 2 × 1.92 TB physical | 2 × 3.84 TB physical |
| Hypervisor | KVM (thin, but present) | None | None |
| Resize | Minutes | New hardware | New hardware |
| Mainnet validator | No | No (RAM floor) | Yes |
Above vds.max, virtualization stops earning its overhead. If the workload is permanent and its shape is known (an RPC mirror, a Geyser pipeline), nln.metal.base undercuts vds.max by $203/mo with physical drives. Keep VDS while you still want to resize in minutes; take metal when the next size up would be the whole machine anyway.
Tell us the workload, we’ll tell you the box
Bursts CPU on signals, idles between slots. Network-bound 95% of the time.
The bot spends its life waiting on the wire. 8 shared vCPU covers the duty cycle with room to spare, and the money stays in the strategy.
Step up ifmpstat shows steal > 1% in peak hours, or your loop decodes every transaction.
Hot loop on the gRPC feed, decode everything, race a single thread to the trigger.
Pinned cores keep the decode loop consistent: a tail event during your neighbor's burst is a missed trade.
Step up ifthe race comes down to one thread: 4.3 GHz Ryzen metal beats 3.0 GHz virtualized.
Constant ingestion from Yellowstone into Postgres on the same box. Never idles.
16 pinned cores and NUMA-local RAM let ingest and Postgres share a box without sharing tails. Unix-socket DB writes, zero network hop.
Step up ifyou index more than a couple of programs or keep > 40 GB hot in Postgres.
Receive events, transform, fan out over HTTP. Spiky concurrency, light CPU.
16 vCPU absorbs concurrency spikes; shared vCPU is irrelevant when the work is mostly I/O.
Step up ifconcurrency is modest, same architecture, $160 less.
Your own read-only RPC node, ledger and accounts on separate volumes.
32 pinned cores, 128 GB, and four NVMe namespaces handle a non-voting mirror, virtualized, so you can resize as it grows.
Step up ifthe mirror is permanent: a full 64-core physical box, physical drives, no hypervisor.
Learning the operator workflow: vote, restart, snapshot, monitor.
16 vCPU / 64 GB carries a testnet validator. The point is the runbook practice, not the hardware.
Step up ifyou want the exact I/O behavior you will see on mainnet.
Voting on mainnet. Skipped slots cost stake. Hypervisor jitter is disqualifying.
64 high-frequency EPYC 9575F cores, 256 GB, and dual 3.84 TB NVMe is the field-standard build. The vote pipeline never crosses a hypervisor.
Step up ifyou prefer a dual-socket 64-core layout with the same 256 GB.
Custom Geyser plugin feeding your own downstream: full-chain firehose in, structured data out.
Full-chain Geyser is a sustained-everything workload: real drives for the sink, real cores for the decode, 128 GB to breathe.
Step up ifyou want more RAM headroom for the working set: 192 GB on the EPYC 9555P.
The next validator client: tile architecture, AF_XDP, large memory appetite.
Built for it: 64 high-frequency cores, 256 GB, and a Firedancer-tuned image (governor, IRQ affinity, AF_XDP) on request. Need more RAM? We quote custom builds.
Determinism inside the box, proximity outside it
The field guide: pin, verify, migrate
# After provisioning a vds.performance, SSH in and verify topology
lscpu | grep -E '^(CPU\(s\)|Thread|Core|Socket)'
# CPU(s): 16
# Thread(s) per core: 1
# Core(s) per socket: 16
# Socket(s): 1
# Confirm steal-time stays at zero under load
mpstat -P ALL 1 5 | grep -E '%steal'
# all 0.00 (across all cores, all samples)
# NUMA affinity (RAM is on the same node as the cores)
numactl --hardware
# node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# node 0 size: 65536 MB
# node distances: node 0: 10use core_affinity;
use yellowstone_grpc_client::GeyserGrpcClient;
fn main() -> anyhow::Result<()> {
// vds.performance: cores 0-3 reserved for OS + IRQ, 4-15 for the bot
let cores: Vec<_> = core_affinity::get_core_ids()
.unwrap()
.into_iter()
.skip(4)
.collect();
let mut handles = vec![];
for (i, core) in cores.into_iter().enumerate() {
handles.push(std::thread::Builder::new()
.name(format!("worker-{i}"))
.spawn(move || {
core_affinity::set_for_current(core);
run_worker(i)
})?);
}
for h in handles { h.join().unwrap(); }
Ok(())
}
fn run_worker(_id: usize) {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all().build().unwrap();
rt.block_on(async {
let client = GeyserGrpcClient::build_from_shared(
"https://grpc.nln.clr3.org:443"
).unwrap()
.x_token(Some(std::env::var("NLN_API_KEY").unwrap())).unwrap()
.connect().await.unwrap();
let _ = client;
})
}import asyncio
import asyncpg
from yellowstone_grpc_proto import geyser_pb2, geyser_pb2_grpc
import grpc
# Postgres on the same VDS, same NUMA node, unix socket.
DB_URL = "postgresql:///nln_indexer?host=/var/run/postgresql"
GRPC_HOST = "grpc.nln.clr3.org:443"
async def ingest(pool):
creds = grpc.composite_channel_credentials(
grpc.ssl_channel_credentials(),
)
chan = grpc.aio.secure_channel(GRPC_HOST, creds)
stub = geyser_pb2_grpc.GeyserStub(chan)
async def reqs():
yield geyser_pb2.SubscribeRequest(
transactions={"ray": geyser_pb2.SubscribeRequestFilterTransactions(
vote=False, failed=False,
account_include=["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"],
)},
commitment=geyser_pb2.PROCESSED,
)
async for upd in stub.Subscribe(reqs()):
async with pool.acquire() as conn:
await conn.execute(
"INSERT INTO swaps_raw (slot, sig, payload) VALUES ($1, $2, $3)",
upd.transaction.slot,
bytes(upd.transaction.transaction.signature),
upd.SerializeToString(),
)
asyncio.run(ingest(asyncpg.create_pool(DB_URL, min_size=8, max_size=32)))# On a VPS (shared vCPU):
mpstat -P ALL 1 30 | awk '/Average/ {print "CPU " $2 " steal=" $6}'
# CPU all steal=1.82
# CPU 0 steal=2.41
# CPU 3 steal=3.67 <-- this core shares a busy neighbor
# On a VDS (pinned cores):
mpstat -P ALL 1 30 | awk '/Average/ {print "CPU " $2 " steal=" $6}'
# CPU all steal=0.00
# Every core, every sample: 0.00. That is the contract.
# If you see non-zero steal time on a VDS, open a support ticket.
# It means a host configuration error and we fix it immediately.How the fleet is run
- ·VPS pools run fair-share vCPU scheduling with headroom held in reserve
- ·VDS and metal capacity is never shared; pinned capacity is held hot
- ·Spare capacity stays racked and powered, so orders never wait for a host to be carved
- ·AMD EPYC platforms, DDR4 ECC across the fleet
- ·Datacenter-grade storage with redundancy (mdraid1) on virtualized pools
- ·Every metal box burn-in tested before handover: CPU, RAM, drive surface, NIC
- ·VPS: nightly snapshots, restore to a healthy host < 15 min
- ·VDS: live migration off a failing host, target < 8 min
- ·Metal: IPMI / KVM-over-IP always on, hardware MTTR < 4 h in the business window
- ·Committed QoS bandwidth on VDS; 10–100 Gbps dedicated ports on metal
- ·Dedicated private VLAN to the NLN RPC / gRPC / WS fleet included on every VDS and bare-metal server
- ·Fair-share scheduler on shared uplinks; one tenant cannot saturate a host
- ·Full root. No locked kernels, no restricted outbound ports, no inspection
- ·Key-only SSH from first boot; password auth ships disabled
- ·Bring your own ISO: Ubuntu, Debian, Rocky, Arch, NixOS all routine
- ·99.99% monthly rolling, fleet-wide
- ·Credits applied automatically on breach, no ticket required
- ·Only enforcement on your box: outbound SMTP abuse limits
Frequently asked questions
Related products
Shared-tenancy hosting at lower cost. Fine for testnet bots and bursty workloads.
Full physical machines for validators or 128+ GB RAM workloads. Cheaper than VDS at the high end.
The streaming backend most VDS workloads spend their time consuming.
Private VLAN endpoint your VDS resolves directly to.
Custom Geyser plugins running on VDS with managed publishing.
Move the hot loop off shared silicon
vds.standard $245/mo · vds.performance $495/mo · vds.max $895/mo. Pinned EPYC cores, NUMA-local RAM, 0.00% steal time by contract. Provisioned in 2–3 minutes, validator-adjacent in Frankfurt on the same private VLAN as the NLN data fleet, <0.5 ms to every NLN Solana service.
Migrating from a VPS? The move is an rsync and an endpoint swap: 15–30 minutes end to end.
- 01Create an accountEmail and a password. No card required to look around.
- 02Pick tier + paste your SSH keyThe exact specs and prices on this page. No checkout surprises.
- 03Root SSH lands in your inboxKey-only auth, your chosen OS image, ready for the NLN data fleet.