I expected to find a bug in my application.
Maybe Node.js was overloaded. Maybe Nginx was queuing requests. Maybe memory was running out. Maybe the disk was stalling.
Instead, mpstat showed this:
Average CPU steal: 32.73%
Average CPU idle: 0.00%
That changed the investigation completely.
This is not a negative review of AVA Hosting, and I am not claiming that every VPS they operate behaves like mine did.
This is simply what happened to one KVM VPS I used under a real production workload, how I diagnosed it, and why I eventually cancelled it.
The VPS
The configuration was small:
1 vCPU
2 GB RAM
25 GB NVMe
KVM
The workload was not synthetic.
Nginx was running.
The backend was running.
The server still had available RAM.
The filesystem was not full.
But requests were accumulating and the backend was not keeping up.
Instead of immediately upgrading the server, I wanted to know what resource was actually exhausted.
So I monitored it for 60 seconds.
No stress-ng.
No benchmark.
No artificial HTTP flood.
Just the real workload.
The 60-second CPU profile
The summary was:
vCPU count: 1
Average CPU user: 52.52%
Average CPU system: 8.07%
Average CPU softirq: 6.69%
Average CPU steal: 32.73%
Average CPU iowait: 0.00%
Average CPU idle: 0.00%
Maximum runnable queue: 11
The important part is how those percentages fit together.
About 52% was normal userspace work.
About 8% was kernel work.
Almost 7% was softirq.
And then:
steal: 32.73%
idle: 0.00%
The VM had no spare CPU.
But roughly one third of CPU accounting was also being reported as steal.
What %steal means
On a virtual machine, high CPU utilization and high CPU steal are not the same problem.
Suppose I had measured:
user + system: ~100%
steal: 0%
idle: 0%
That would be straightforward.
The workload consumed the CPU it was given.
I would either optimize the workload or buy more CPU.
steal means something different.
Conceptually:
guest has runnable work
↓
vCPU wants to run
↓
vCPU is not being executed
↓
Linux accounts the time as steal
KVM provides steal-time accounting specifically so a guest can observe time in which its virtual CPU was not running.
That does not tell me why the hypervisor did not schedule the vCPU.
From inside the VM I cannot see:
physical host utilization
number of neighbouring VMs
CPU overcommit ratio
scheduler policy
vCPU pinning
host-side quotas
But I can observe the effect from the guest.
And in this case the effect was large.
It wasn't one bad sample
A momentary 30% steal value would not have been enough for me.
Scheduler noise happens.
So I looked at the per-second samples.
They repeatedly looked like this:
%usr %sys %soft %steal %idle
53.54 10.10 5.05 31.31 0.00
56.44 6.93 5.94 30.69 0.00
51.52 7.07 6.06 35.35 0.00
55.45 6.93 5.94 31.68 0.00
51.49 8.91 5.94 33.66 0.00
52.53 7.07 7.07 33.33 0.00
52.48 6.93 7.92 32.67 0.00
Near the end:
50.98 10.78 6.86 31.37 0.00
48.48 8.08 9.09 34.34 0.00
55.00 5.00 7.00 33.00 0.00
The pattern persisted throughout the observation.
This was the average:
steal: 32.73%
idle: 0.00%
For intuition:
60 × 0.3273 = 19.64 seconds
That does not mean the VPS froze continuously for 19.64 seconds.
It means the smaller periods accounted as steal added up to roughly 19.6 seconds during that minute.
For a busy one-vCPU server, that is a huge amount of unavailable execution time.
Linux PSI showed the CPU was under constant pressure
I also checked Linux Pressure Stall Information:
some avg10=99.00
some avg60=99.12
some avg300=98.92
Later:
some avg10=99.19
some avg60=99.16
some avg300=98.98
So for almost the entire observation window, at least some runnable work was stalled waiting for CPU.
The load average was also around:
3.5 – 4.0
on a machine with:
1 vCPU
And the runnable queue reached:
11
None of those measurements alone proves a hypervisor problem.
Together with 32.73% steal, though, they described a machine with persistent CPU contention.
The application queue confirmed that this wasn't just an ugly metric
The backend process was alive.
That is an important distinction.
This was not:
backend crashed
It was:
backend running
but unable to keep up
Before the 60-second observation, its listening queue showed:
Recv-Q: 168
Send-Q: 511
After one minute:
Recv-Q: 166
Send-Q: 511
The interesting part is not 168 versus 166.
It is that the queue essentially did not drain.
The server spent a full minute processing traffic and remained just as far behind.
That connected the CPU metrics with an actual application symptom.
I checked memory
Before blaming the virtualization layer, I wanted to eliminate the boring explanations.
Memory looked like this:
RAM total: ~1.9 GiB
RAM available: ~959 MiB
Swap total: 2 GiB
Swap used: ~33 MiB
Nearly a gigabyte was still available.
There was no obvious OOM event explaining the behavior.
So this did not look like memory exhaustion.
I checked the disk
The filesystem looked like this:
24 GB total
14 GB used
9.4 GB available
60% used
And, more importantly:
Average CPU iowait: 0.00%
The server was not sitting around waiting for storage.
The useful combination was:
iowait: 0.00%
idle: 0.00%
steal: 32.73%
At this point, adding RAM or cleaning the disk would have been solving a different problem.
Then I looked at AVA Hosting's CPU claims
This is where the incident became particularly interesting.
AVA currently describes its VPS resources as:
“Guaranteed resources — no sharing”
and says each VPS receives guaranteed vCPU resources whose performance is not affected by other customers.
Its Linux VPS page is more specific: AVA says that a CPU-intensive workload from another tenant cannot introduce steal time into another instance and describes its vCPU allocation as dedicated.
Its unmanaged VPS page goes further again, saying:
“CPU steal time is eliminated at the hypervisor level”
and describing CPU resources as isolated rather than a shared or burstable pool.
That is a much stronger claim than simply advertising:
1 vCPU
A vCPU does not automatically mean one private physical CPU core.
Virtualization does not work that way by default.
But AVA's own wording specifically addresses resource sharing and CPU steal.
My VM reported:
Average CPU steal: 32.73%
That is the contradiction that mattered to me.
What this does and does not prove
I want to keep this technically narrow.
My measurements establish what happened inside my VM.
I observed:
1 vCPU
32.73% average CPU steal
0% CPU idle
~99% CPU pressure
runnable queue up to 11
backend queue that would not drain
0% average CPU iowait
substantial RAM still available
I cannot use those measurements to prove:
intentional overselling
exact host occupancy
number of neighbouring VMs
specific neighbour activity
AVA's physical CPU topology
exact hypervisor configuration
I also tested one VPS, not AVA Hosting's entire infrastructure.
So my conclusion is deliberately narrower:
The VPS I received exhibited severe CPU steal under a real workload, and that behavior was difficult to reconcile with AVA's explicit claims about dedicated CPU allocation and the absence of neighbour-induced steal time.
That's enough.
Why I didn't upgrade to more vCPUs
At this point I could have bought a larger plan.
But I wanted to answer a basic question first:
Was my application simply consuming the CPU I purchased?
If the answer had been yes:
steal ≈ 0%
CPU saturated
then buying another vCPU would have been reasonable.
Instead:
steal = 32.73%
Buying additional vCPUs might still have improved throughput.
But I did not want to pay more before understanding why so much of the first vCPU's requested execution time was already being recorded as steal.
So I cancelled the VPS.
AVA refunded me in full
This part deserves to be included because it was handled well.
I requested a full refund.
AVA Hosting returned the entire amount quickly.
They did not spend days arguing with me about the measurements or trying to turn the issue into a long support exchange.
I explained the problem, requested the refund, and received it.
So my experience has two separate parts:
The VPS I received had a serious CPU-availability problem.
AVA handled the refund well.
Both are true.
The VPS check I use now
The practical lesson was not “never use one vCPU.”
It was not “KVM is bad.”
And it was not “every AVA VPS must behave like this.”
The lesson was that I should validate CPU behavior under the actual workload instead of trusting the specification alone.
These are now some of my first checks on a new Linux VPS:
mpstat 1 60
vmstat 1 60
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io
ss -ltnp
free -h
df -h
iostat -xz 1 60
I am looking for relationships between metrics.
High CPU, almost no steal
steal ≈ 0%
idle ≈ 0%
The workload probably needs more CPU.
High iowait
Investigate storage.
Memory pressure and swap activity
Investigate RAM.
High steal + zero idle + high CPU PSI + growing queues
Now I start looking beyond the application.
That combination is much more useful than simply saying:
“The VPS feels slow.”
A running service is not a healthy service
This incident also reinforced something easy to forget during production debugging.
These checks:
systemctl status nginx
systemctl status my-backend
can both say:
active (running)
while the system is already failing operationally.
A process can exist.
A TCP socket can listen.
Nginx can accept requests.
And the machine can still be unable to process incoming work fast enough.
That is why I now care much more about:
CPU scheduling
pressure
queues
latency
than whether a process merely exists.
The three numbers I remember from this incident
If I had to reduce the whole debugging session to three lines, they would be:
CPU steal: 32.73%
CPU idle: 0.00%
CPU pressure: ~99%
AVA Hosting says its VPS CPU resources are guaranteed without sharing and explicitly describes protection from neighbour-induced CPU steal.
My VPS reported otherwise.
I cancelled it.
AVA refunded everything quickly and without an argument.
And I added %steal to the list of metrics I check before trusting a new VPS with production traffic.
Top comments (0)