A server admin posted to r/admincraft with a confusing problem: a Purpur 1.21.11 server with only 5 players online was running at 17-19 TPS instead of a clean 20. Median MSPT was 54.8ms, over the 50ms-per-tick budget. Five players should never be enough to stress a modern CPU. Something else was eating the tick. (If you’re new to reading Spark profiles, our complete guide to reading a Spark profiler report covers the terms used throughout this post.)
What the community tried first
The thread’s commenters did what most admins do when TPS drops: they looked at the obvious suspects. Plugin count, world size, entity count, view distance. One commenter got close, flagging ServerFunctionManager.tick as a possible culprit after skimming the flame graph, but stopped short of identifying which function or why.
That is the ceiling of manual flame-graph reading: you can spot a wide bar, but tracing it to a specific plugin or datapack means manually walking the call tree method by method.
What the call-tree analysis found
Running the same Spark profile through automated call-tree analysis surfaced a single dominant hotpath immediately: ExecuteCommand.lambda consuming 25% of the server thread, every tick.
That method name is the implementation detail behind Minecraft’s /execute command. Something was invoking /execute on a per-tick loop, and at 25% of the tick budget, it was large enough to single-handedly explain the TPS drop.
The hardware context mattered too: the server was running on an i5-1235U, a laptop U-series chip with only 2 performance cores. A desktop or server-grade CPU might have absorbed a 25%-of-thread command loop without dropping below 20 TPS. On 2 P-cores, there was no headroom left.
Root cause confirmed
The admin dug into their datapacks based on the finding and reported back: two specific datapacks, Tubm’s Armor Trim Abilities and MoVariants, were both running /execute logic on every tick to check player state. Removing or reworking the offending datapacks was the fix.
This is the gap between config-based checkers and call-tree analysis. A config checker reads server.properties, plugin lists, and known compatibility tables. It has no visibility into what a datapack’s function files are actually doing at runtime. Two datapacks quietly issuing /execute every tick will not show up in any config audit, because there is nothing misconfigured. The datapacks are doing exactly what their functions say. The problem only exists in the timing data.
A second finding, unrelated to TPS
The same report surfaced something the original thread had not discussed at all: the server’s disk was 89.9% full (33.5 of 37.2 TiB). A commenter, not the automated analysis at the time, spotted it manually from the viewer.
A nearly-full disk does not directly cause low TPS, but it slows down world saves and chunk writes, which shows up as periodic tick stalls, and a disk that fills up completely can corrupt world saves mid-write. It is exactly the kind of secondary problem that is easy to miss when you are focused on chasing the primary lag source. The admin later moved the server to a larger SSD.
That gap is also why our analyzer now flags disk usage directly: reports above roughly 85% full trigger a warning, and reports that hit the disk’s actual limit trigger a critical finding, so this class of problem surfaces automatically instead of depending on a sharp-eyed commenter.
Takeaways
- Low player count does not rule out CPU-bound lag. 5 players at 54.8ms median MSPT is a real problem, not noise. If MSPT is over budget, something specific is consuming the tick, regardless of how few players are online.
- Datapacks are a blind spot for config checkers. Anything that only reads
server.propertiesor plugin metadata cannot see what a datapack’s functions execute per tick. If you run custom datapacks and see unexplained lag, profile before ruling them out. - CPU class matters more on small servers. A command loop that a server-grade CPU shrugs off can be the dominant cost on a laptop-class chip with few performance cores. Know your hardware ceiling before assuming a plugin or datapack is “too small to matter.”
- Profile for secondary issues, not just the one you are chasing. Disk space, GC behavior, and JVM flags rarely show up in a Reddit post’s problem description, but they are sitting in the same report you already pulled.
For another case where the same diagnostic approach caught a different kind of hotpath, see our Cobblemon/AllTheMons entity overload case study, which uses two profiles at different severities to confirm a diagnosis rather than guessing from one.
Have a Spark report you can’t make sense of? Paste it into our analyzer for a free, instant breakdown, no signup required.