Specific cases where an assumption about performance, traffic or user behaviour turned out to be wrong, and what the measurement was that settled it.
13 September 2026
Photo: Clément Bucco-Lechat, CC BY-SA 3.0, via Wikimedia Commons
The cost of guessing at load
Premature optimisation examples often stem from assuming that a single server process can handle all traffic without contention. When you run multiple distinct products on one machine, the first thing to measure is not raw speed, but how resources are shared. Pracandy runs four sites through one Next.js process, routed by host. Initially, it seemed logical to treat the film publication and the Android wallpaper app as identical workloads. They are not. The publication serves heavy HTML and image assets, while the wallpaper app serves lighter JSON and static files. Without measuring the specific memory footprint of each route, it is easy to assume that a 12GB RAM box has infinite headroom. It does not. Processes on one box compete for memory, and if the publication spikes during a new release, it can starve the wallpaper app of resources. The fix was not to add more hardware, but to monitor memory usage per route. Once the data showed that the publication consumed the majority of the heap, the optimisation effort shifted to caching strategies for that specific site, rather than blindly tuning the global server configuration.
Static files are not free
A common error is assuming that serving static files requires no server resources. GamingCap and Invoiceful are static builds served directly from disk by Caddy. They have no server process and no database. This setup is efficient, but it is not invisible. The disk I/O for serving thousands of small files can become a bottleneck if the filesystem is not tuned. It is easy to assume that because there is no Node.js process running for these tools, they cannot impact the rest of the system. This is wrong. The kernel still handles the system calls for file access. If the disk is busy writing logs or database backups, serving static assets can slow down. The measurement that settled this was monitoring disk seek time. When the disk activity spiked during scheduled database maintenance, the latency for loading Invoiceful increased. The solution was not to add a CDN, but to adjust the timing of the systemd oneshot services that run the backups. By moving the heavy I/O tasks to a time when user traffic was low, the static sites remained fast without any code changes.
Database sharing is a risk
Instalaz runs as its own separate Node process on port 3050, but it shares the same PostgreSQL 16 database as the Next.js app. This architecture is efficient because it reduces the number of moving parts. However, it creates a single point of contention. If the Instagram scheduling tool runs a heavy query to process a batch of posts, it can lock tables or consume CPU cycles that the main application needs. The assumption that the two processes are independent because they run in different containers or ports is a trap. They are not independent at the data layer. The measurement that revealed this was the query execution time. When the scheduling tool ran its nightly batch, the response times for the main site increased. The fix was to add connection pool limits to the Instalaz process. By capping the number of concurrent database connections, the scheduling tool could no longer starve the main application. This is a classic case of assuming isolation where none exists.
Network latency is not local
The final assumption that often fails is that network latency is constant. Caddy terminates TLS for every site and obtains certificates from Let's Encrypt automatically. This is a reliable setup, but it is not immune to network issues. DNS is managed at Spaceship, not Cloudflare. This means that the time to resolve the domain name depends on the user's local DNS server and the propagation of records. It is easy to assume that because the server is fast, the user experience is fast. This is not always true. If the DNS records are not cached, or if the local resolver is slow, the user will experience delays before the request even reaches the server. The measurement that settled this was the time to first byte, broken down by DNS resolution and TLS handshake. When the DNS times were high, the issue was not the server, but the network path. The solution was to ensure that the DNS records had a reasonable TTL, so that changes propagated quickly, and that the local resolvers were not overloaded. This is a reminder that the server is only one part of the system. The network, the