SparkUpVPNGet early access

All guides

Why VPN speed drops over time: the bottleneck may not be the network

Our VPN speed test fell from 77.4 to 16.6 Mbps in twelve minutes. The link was not getting worse. A control loop was measuring the wrong delay.

· 5 min read

Our speed cap fell from 77.4 Mbps to 16.6 Mbps in twelve minutes. It never bounced back during the test. Each run had the same shape: a quick start, a peak, then a steady slide until the tunnel felt broken.

The obvious suspect was the network. It wasn't. The link had not become five times worse. Our quality controller was watching the time taken to write an accounting record, then treating that number as if it described the link.

That distinction sounds narrow, but it explains a lot of VPN connections that start fast and slow down. A tunnel isn't just encryption and packets. It may also contain queues, usage meters, authorisation windows, retries and safety limits. Any one of those can quietly become the speed limit.

Why did the speed test look like congestion?

SparkUp Public sharing has to stop a client from using more data than it has been authorised to use. The node receives byte grants, serves traffic inside those grants and asks for more before the remaining window runs out. We added a pacer so a delayed top-up would produce a gentle slowdown instead of full speed followed by a hard stop.

The first controller used a plausible formula: available bytes multiplied by a safety margin, divided by the observed grant round-trip time. If 16 MiB remained and another grant usually arrived in one second, the tunnel could safely move about 134 Mbps. If the refill took two seconds, the cap fell to about 67 Mbps.

The formula did what we asked. The problem was what its timer included. A successful top-up changed the ledger, and the file-backed ledger rewrote an 11.9 MB snapshot. A refused request only read state. In one production trace, refused requests took 36 ms at the median while approved requests took 1,362 ms. The controller learned the second number.

More traffic caused more top-ups. More top-ups caused more slow writes. Slow writes made the estimated refill time rise, so the pacer lowered the cap. The speed test was not revealing deteriorating Wi-Fi or an overloaded relay. It was drawing a graph of storage latency.

One dead session made the live session slower

A second fault made the effect worse. The client and node pair could hold five outstanding grants. Four still belonged to a stream that had died after a process restart, and they would not expire for 65 minutes. The live stream was left with exactly one 8 MiB grant in every one of the 78 pacing decisions we inspected.

The limit itself was necessary. Without it, a node could reserve grants indefinitely and empty an account without carrying useful traffic. The bug was counting authorisation held by a stream that could no longer spend it.

That halved the window the pacer expected. Instead of keeping two grants of headroom, the live connection ran one refill away from empty. It would burst, wait, burst again and eventually crawl. The node recorded 188 refusals and one uninterrupted stall lasting 5 minutes 12 seconds.

The client was told it owed an acknowledgement. Its own stream state said it owed nothing. Both sides were technically correct because they were looking at counters with different scopes. That's another QoS lesson: an accurate metric attached to the wrong object is still misinformation.

Why smoothing can reduce real throughput

We next tested a simpler controller: pace according to the authorisation left, not how quickly the last refill arrived. As the remaining budget approaches zero, the permitted rate falls with it. The tunnel lands gradually rather than hitting a wall.

The first simulation looked almost too good. With a 16 MiB window it held roughly the same 37.6 Mbps while cutting stalled time from 38.3% to 1.3%. That is about thirty times fewer stalled ticks without buying more throughput.

Then review found the catch. Grants arrive in chunks. If the controller drains too slowly, the next chunk reaches a nearly full window and part of it cannot be accepted. With two grants of headroom, smoothness is close to a free adjustment. With only one grant, every smoother setting costs throughput.

WindowAverage speedStalled ticksWhat it means
16 MiB, two grants37.6 Mbps1.3%Enough headroom to smooth without losing much rate
8 MiB, one grant24.9 Mbps1.9%Smooth, but accepted refills are constrained
8 MiB, one grant, gentler braking15.9 Mbps0%No stalls, at a large throughput cost

We haven't shipped that proportional controller. The result changed the order of work instead: reclaim dead grants safely, make the real cause visible, improve ledger writes, then tune pacing. Smoothing first would hide starvation behind a nicer graph.

What should VPN QoS measure?

Throughput alone is not connection quality. RFC 9439 separates delay, delay variation, packet loss, available bandwidth and TCP throughput because each describes a different failure. A tunnel can post an impressive peak while spending half its time stalled.

We now ask a more awkward question of every input to a controller: is this a property of the path, or a property of our own machinery? Grant latency was real, but it included storage. A speed cap was real, but a dead stream had changed the available window. A client warning was real, but it named an acknowledgement the client could not send.

The useful dashboard therefore needs both network and control-plane facts. For the path: delivered bytes, round-trip time, variation, loss and stall duration. For the controller: authorised bytes left, refill state, queue depth, refusal reason and the time spent reading or writing accounting data. Put those on separate axes. Never let one impersonate the other.

What to check when your VPN starts fast then slows down

Run a long test rather than three short ones. If every run decays with a similar curve, look for a stateful limiter before blaming random congestion. Compare tunnel throughput with CPU use, queue delay and any quota or token refresh. If speed falls exactly when a periodic control action runs, that's evidence.

For SparkUp, this investigation also changed what we consider a successful test. A high peak is interesting. A flat ten-minute run with bounded stalls is better. We still don't have enough field data to publish a universal QoS score, and a single number would probably conceal more than it explains.

If you only need maximum benchmark speed, a nearby conventional VPN server may be the simpler choice. SparkUp is being built for controlled sharing across real home and mobile links, where explaining a slowdown matters as much as hiding it.