VDS vs VPS by Workload Type: Choosing CPU Isolation for Databases

A database and a web front end can run on identical hardware and still need completely different CPU scheduling behavior. The gap between a VDS (virtual dedicated server) and a VPS is not marketing language about “more power” — it is a difference in how the hypervisor exposes cores, how steal time is accounted for, and whether the guest can rely on cache locality. Those properties determine whether PostgreSQL holds a p99 commit latency target or slowly drifts into a lock convoy when a neighbor on the same host starts a nightly analytics job.

This article walks through the mechanics of CPU isolation, then maps workload classes onto VDS and VPS configurations using measurable signals. It assumes you already know how to size RAM and disk; the focus is on scheduling entitlement and the metrics that tell you whether you got it. Provisioning any of the plans described here starts with a look at the Cloud VPS platform and its sizing options.

How CPU Entitlement Actually Works on Virtualized Hosts

A VPS is typically sold as a slice of a shared vCPU pool. The guest sees N virtual CPUs, but the hypervisor schedules those vCPUs on physical threads alongside every other tenant. When the host is oversubscribed, the guest’s vCPU waits in the run queue and the hypervisor credits that wait as steal time. On a VDS, the provider pins vCPUs to dedicated physical cores (or explicit SMT siblings) and the guest’s CPU model topology is exposed via CPUID, so the scheduler inside the guest sees real cache hierarchy and NUMA layout.

The practical difference shows up in tail latency, not averages. A lightly loaded shared host can show 0.3% steal and still deliver 15 ms p99 spikes when a batch process on another guest saturates an LLC slice. A pinned core set removes that noise floor because nothing else can evict your working set from L3. Check the current picture on any Linux guest with vmstat 1 5 — the st column is steal, and anything sustained above 2% under your own moderate load means you are contending, not just sharing.

Measuring Steal Time and Run Queue Latency Before You Choose

Before migrating a production database, collect a baseline over at least 24 hours. The signals that matter are run queue length per CPU, context switch rate, and the distribution of scheduling delays. Run mpstat -P ALL 1 10 and watch %steal per vCPU; asymuthmic steal (one vCPU at 0%, another at 8%) is a strong indicator of noisy-neighbor placement rather than global host saturation. Pair that with pidstat -u -w 1 10 to see per-thread involuntary context switches.

For a more precise view into scheduler delay, use perf sched latency on a running workload. A healthy dedicated-core guest shows maximum scheduling latency in the tens of microseconds. A contended shared guest regularly shows maxima above 1 ms, and those spikes are what turn a 4 ms query into a 40 ms query once lock waits amplify them. If you see sched: RT throttling activated in dmesg, you also have CPU cgroup throttling on the vCPU, which is common on credit-metered plans.

Database Workloads: Why Cache Locality and Pinning Matter

Databases are the clearest case for dedicated cores. A buffer-pool-resident read path is essentially a memory-locality problem: the query spends much of its time chasing pointers through B-tree pages that should be in L3. When another tenant’s workload thrashes the shared LLC, your effective cache hit ratio drops even though your own hit ratio metric looks unchanged, because the misses now cost DRAM round trips instead of L3 hits. On a pinned VDS, isolate the database threads with systemd CPUAffinity=2 3 4 5 and keep the OS and interrupt handling on the remaining cores.

Then verify the isolation is real: taskset -cp $(pgrep -f postgres | head -1) should show the pinned mask, and cat /proc/interrupts should show NIC and NVMe queues landing on the non-database cores after you set irqaffinity in the kernel command line. Confirm scheduler granularity with sysctl kernel.sched_min_granularity_ns; for OLTP workloads, 2–4 ms combined with kernel.sched_wakeup_granularity_ns=1 reduces preemption churn on pinned cores without starving interactive threads.

Web and Application Tiers: Where Shared vCPUs Are Enough

Stateless HTTP workers behave very differently. Nginx, PHP-FPM, Node, and Go runtimes are short-burst, high-context-switch workloads whose latency is dominated by network and I/O paths rather than L3 residency. A shared VPS with 4 vCPUs and 8 GB RAM can comfortably serve several thousand requests per second of dynamic content, because the scheduler only needs to find any free thread for a few hundred microseconds. The relevant tuning is upstream connections and worker counts, not core pinning: set worker_processes auto, worker_connections 8192, and in /etc/sysctl.d/99-web.conf raise net.core.somaxconn to 4096 and net.ipv4.tcp_max_syn_backlog to 8192.

The risk on shared vCPU for web tiers is not average throughput but queue buildup when a downstream dependency slows. If the database call goes from 5 ms to 200 ms, your worker pool saturates and the shared vCPU behind it has less headroom to absorb the burst. Budget so that steady-state CPU utilization on the web tier stays under 50% of entitlement, and watch nginx stub status active and waiting counts. If waiting grows while active is flat, you have a pool-sizing issue; if both grow together, CPU is the constraint.

Benchmarking the Difference: A Repeatable Comparison Method

To decide empirically, run the same synthetic workload on both plans. For CPU-bound comparison, use sysbench cpu --threads=$N --time=120 run and record events per second and the 95th percentile latency together — events per second alone hides jitter. For a database-shaped test, pgbench -c 32 -j 8 -T 120 -S against a read-mostly dataset larger than buffer pool shows exactly how neighbor noise affects cache-dependent throughput. Run each test three times at different hours to catch diurnal noise on the host.

Log %steal, sched latency max, and p99 query time in the same timeline. A dedicated-core VDS should show near-zero steal and p99 within 2× of p50 on a well-tuned database. A shared VPS under a database workload often shows p99 at 5–10× p50 even when averages look acceptable, which is the signature of scheduling interference rather than slow queries. That ratio, not the raw benchmark score, is the decision criterion for databases.

Right-Sizing: Memory Bandwidth and NUMA Effects

CPU isolation alone does not fix bandwidth contention. Even on pinned cores, a shared memory controller means another tenant’s streaming copy can halve your effective DRAM bandwidth. OLAP-style scans and large sort operations are bandwidth-hungry: check with mbw 256 or STREAM and compare against the theoretical figure for the platform. If you measure under 60% of expected throughput with low steal, the bottleneck is memory bandwidth, and a plan with dedicated memory channels or a smaller NUMA domain will help more than extra vCPUs.

Keep NUMA in mind when sizing. A guest spanning sockets will show higher remote-access latency for the buffer pool, visible in numastat -p $(pgrep -f mysqld) as a nonzero numa_foreign count. Pin the database to a single NUMA node where the plan allows it. If you are still weighing the trade-offs between fully managed service and self-managed isolation, the managed versus unmanaged comparison covers who owns these kernel-tuning decisions.

Decision Matrix by Workload Class

For OLTP databases with a p99 SLO, analytical engines, real-time bidding, and anything that holds large hot working sets, choose a VDS with pinned vCPUs and verify steal stays under 0.5% during peak. For stateless web tiers, queue consumers, CI runners, and batch ETL where throughput matters more than tail latency, a shared VPS with a 50% utilization headroom budget is cost-efficient and simpler to scale horizontally. Mixed stacks usually do best with a small VDS for data and elastic VPS instances for the stateless layer.

One caveat on burstable plans: credit-based instance families are excellent for spiky web traffic but dangerous for databases, because a CPU credit exhaustion event causes throttling exactly when a long-running query needs the most CPU. Treat burstable capacity as a latency-tolerant tier only.

Verifying Isolation After Migration

After moving to a dedicated plan, confirm the promise holds. Compare lscpu -e topology and cat /sys/devices/system/cpu/cpu0/cache/index3/size against the provider’s stated specification, then re-run the sysbench and pgbench suites at the same concurrency. Also watch /proc/pressure/cpu for a week; sustained some avg10 above 10 under your normal load means the guest is being throttled by CPU cgroup limits rather than core contention.

Isolation should be re-checked after any provider-side maintenance window, since live migration can change physical placement. A short automated test — 60 seconds of sysbench cpu comparing events per second to a stored baseline — catches placement regressions before your users do.

Summary: Match Entitlement to Latency Sensitivity

The VDS-versus-VPS decision is a scheduling question, not a spec-sheet question. If your workload’s value depends on cache locality and bounded p99 latency, pay for pinned cores and validate steal time continuously. If your workload is horizontally scalable and latency-tolerant, shared vCPUs deliver better economics. Instrument first, choose second, and re-measure after every placement change.

Leave a Reply