
August 24, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Most production outages are not caused by bad code but by unanticipated load exceeding available resources. Capacity planning for growing systems is the engineering discipline of predicting resource needs before they become incidents, ensuring your Laravel or PHP application handles traffic spikes without degrading user experience or burning cash on idle servers. Instead of guessing when to upgrade, you establish measurable baselines and trigger points based on actual application behaviour.
What metrics actually matter for capacity planning for growing systems?
You cannot plan capacity based on page views alone. In my experience maintaining legal-tech portals and eCommerce platforms, raw traffic numbers often correlate poorly with server load. A single complex search query can consume more resources than 1,000 static page loads. Effective Laravel development requires tracking four specific resource dimensions that directly predict failure points.
CPU saturation is your first warning sign, but measure it correctly. Load average divided by core count gives true utilisation; a load of 4.0 on a 4-core machine means 100% saturation, not 4%. On production Laravel applications running PHP-FPM 8.4, I watch for sustained load above 70% of core count during peak hours. Context switches per second matter equally — values exceeding 50,000/s indicate excessive process thrashing even if CPU percentage looks acceptable.
Memory pressure kills silently. Monitor swap usage as a binary signal: any non-zero swap activity means your working set exceeds RAM. Track RSS growth rate of PHP-FPM worker processes over time; consistent growth indicates memory leaks in application code or third-party packages. OOM kill counts from dmesg confirm you have already failed.
Database connection pool utilisation predicts outages better than query speed alone. When active connections consistently exceed 80% of your configured maximum, new requests queue at the application layer. For MySQL 8.4, monitor Threads_connected against max_connections, but also track Innodb_row_lock_waits — high lock waits with low connection counts indicate contention problems that adding connections will not solve.
Queue backlog is the most overlooked metric in Laravel queue scaling. Measure both pending job count and processing lag (time between job creation and execution). A stable backlog of 100 jobs processing within seconds is healthy; a growing backlog with increasing lag means your workers cannot keep pace regardless of current queue depth.
How do you establish realistic baselines through load testing?
Theoretical capacity calculations fail because they assume uniform workload distribution. Real applications have hot paths, cache miss storms, and seasonal patterns that only emerge under synthetic load. Before deploying any production web system, run structured load tests that mirror actual user behaviour rather than abstract benchmarks.
Use k6 for scripted load testing because it integrates with CI pipelines and produces reproducible results. Install via package manager and create test scripts that exercise complete user journeys, not individual endpoints:
// load-test-checkout.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 20 }, // Ramp up
{ duration: '5m', target: 50 }, // Sustained peak
{ duration: '2m', target: 100 }, // Spike test
{ duration: '3m', target: 50 }, // Recovery
{ duration: '2m', target: 0 }, // Cool down
],
thresholds: {
http_req_duration: ['p(95)<800'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.post('https://staging.example.com/api/checkout', {
product_id: 'prod_123',
quantity: 2,
});
check(res, {
'status is 200': (r) => r.status === 200,
'response time OK': (r) => r.timings.duration < 800,
});
sleep(1);
} Run tests against staging environments that match production specifications exactly. Testing on undersized hardware produces misleading baselines. Execute during off-peak hours to avoid contaminating results with background maintenance tasks. Capture system metrics simultaneously using vmstat 1, iostat -x 1, and application-level telemetry to correlate response times with resource consumption.
Document three baseline values from each test: P95 response time at target load, maximum sustainable throughput before error rate exceeds 1%, and resource utilisation at both levels. These become your scaling triggers. Re-run baselines quarterly or after significant architectural changes — application evolution invalidates old assumptions faster than traffic growth does.
When should you scale vertically versus horizontally for PHP applications?
This decision determines operational complexity and cost trajectory. Neither approach is universally superior; the right choice depends on your bottleneck type, team size, and budget constraints. On client projects ranging from legal service portals to multi-vendor marketplaces, I have deployed both strategies successfully by matching architecture to constraint characteristics.
| Factor | Vertical Scaling | Horizontal Scaling |
|---|---|---|
| Bottleneck Type | Single-threaded workloads, large datasets requiring shared memory | Stateless request handling, parallelisable background jobs |
| Implementation Effort | Low — resize instance, restart services | High — session storage, file sharing, database replication |
| Cost Trajectory | Exponential — doubling RAM/CPU costs 3-4x more | Linear — add identical nodes at constant unit cost |
| Fault Tolerance | Single point of failure remains | Automatic failover possible with load balancer health checks |
| Downtime During Scale | Required for resize operations | Zero-downtime with rolling deployments |
| Team Expertise Required | Basic Linux administration | Distributed systems knowledge, orchestration tooling |
| Best For Nepal SMBs | Initial growth phase, predictable traffic, limited DevOps staff | Established products, variable load, dedicated ops support |
Start vertical until you hit diminishing returns. For most Laravel applications on PHP 8.4 with OPcache enabled, a single well-tuned server handles 200-500 concurrent users comfortably. Vertical scaling makes sense when your bottleneck is database query performance on large joins, full-text search across substantial datasets, or report generation requiring significant memory. The simplicity advantage matters enormously for small teams managing multiple client sites.
Transition to horizontal when vertical upgrades cost disproportionately more or when fault tolerance becomes a business requirement. Horizontal scaling demands externalised session storage (Redis), shared file systems for uploads (S3-compatible object storage), and database read replicas to distribute query load. If your team lacks experience with these components, the operational overhead may outweigh benefits until traffic justifies hiring dedicated DevOps support.
A hybrid approach often works best for growing Nepal-based businesses: vertical scale the primary application server while horizontally scaling queue workers independently. Background job processing is inherently parallelisable and stateless, making it ideal for horizontal expansion without touching request-handling architecture. This lets you address email sending, PDF generation, and data import bottlenecks separately from web request capacity.
How do you configure auto-scaling triggers that actually work?
Auto-scaling fails when triggers react too slowly or oscillate wildly. Production systems need hysteresis — different thresholds for scaling up versus scaling down — to prevent flapping. Configure triggers based on leading indicators identified during baseline testing, not lagging metrics like error rates that signal you have already failed.
For Laravel applications deployed with Deployer 7 on Ubuntu 24.04, implement custom scaling logic using system metrics exported to Prometheus or CloudWatch. Define separate thresholds for scale-up and scale-down actions:
# /etc/prometheus/rules/laravel-capacity.yml
groups:
- name: laravel_scaling
rules:
- alert: ScaleUpTrigger
expr: |
(
node_load1 / count(node_cpu_seconds_total{mode="idle"})
) > 0.75
or
(
mysql_global_status_threads_connected
/ mysql_global_variables_max_connections
) > 0.80
for: 3m
labels:
action: scale_up
- alert: ScaleDownTrigger
expr: |
(
node_load1 / count(node_cpu_seconds_total{mode="idle"})
) < 0.30
and
(
mysql_global_status_threads_connected
/ mysql_global_variables_max_connections
) < 0.25
for: 15m
labels:
action: scale_down The asymmetric timing is deliberate. Scale up after 3 minutes of sustained pressure to catch genuine load increases quickly. Scale down only after 15 minutes of confirmed low utilisation to avoid premature contraction during brief lulls. Adjust these windows based on your application's typical traffic pattern volatility — e-commerce sites during festival seasons need longer cool-down periods than B2B portals with predictable business-hour traffic.
Test auto-scaling behaviour under controlled conditions before relying on it. Simulate gradual load increases and sudden spikes to verify scale-up responsiveness. More importantly, simulate load decreases to confirm scale-down does not trigger during temporary dips. Document observed behaviour and refine thresholds iteratively; theoretical configurations rarely survive first contact with production variability.
What common mistakes undermine capacity planning efforts?
Even experienced teams repeat predictable errors when planning capacity. Recognising these patterns helps you avoid costly rework and emergency upgrades. In my experience shipping production systems since 2010, these five mistakes account for most capacity-related incidents.
- Planning for average instead of peak: Designing for mean traffic ignores the reality that systems fail at extremes. Your 99th percentile day determines required capacity, not your median day. Buffer at least 40% above observed peaks to absorb unexpected surges without degradation.
- Ignoring dependency chains: Application servers may have headroom while Redis saturates or database replication lag grows. Map every external dependency and monitor each independently. A single saturated component creates cascading failures across otherwise healthy infrastructure.
- Treating capacity as one-time project: Applications evolve, dependencies update, and traffic patterns shift. Quarterly capacity reviews catch drift before it becomes incidents. Schedule load tests alongside feature releases, not just during initial launch preparation.
- Over-provisioning to avoid thinking: Throwing resources at unclear bottlenecks wastes money and masks underlying problems. Profile first, then provision. A Rs 50,000/month server running inefficient code costs more long-term than fixing the code and running on Rs 15,000/month hardware.
- Neglecting non-functional requirements: Capacity planning focuses on throughput but forgets latency budgets, recovery time objectives, and compliance constraints. Define these explicitly alongside raw capacity targets. A system handling 1,000 RPS with 5-second responses may technically meet throughput goals while failing user expectations completely.
The most insidious mistake is conflating infrastructure spending with engineering progress. Buying bigger servers feels productive but teaches nothing about your application's actual behaviour. Invest time in understanding why resources are consumed before investing money in acquiring more. This discipline separates teams that grow sustainably from those that accumulate technical debt disguised as cloud bills.
Making Capacity Planning for Growing Systems Sustainable
Sustainable capacity planning for growing systems integrates into existing development workflows rather than existing as separate ceremony. Embed load tests in CI pipelines alongside unit tests. Review capacity metrics during sprint retrospectives, not just during incidents. Treat infrastructure decisions with the same rigour as feature architecture decisions.
Start small if this feels overwhelming. Pick one critical metric from each dimension discussed earlier and instrument it properly. Run a single baseline load test this month. Document findings in a shared location accessible to developers and stakeholders alike. Incremental improvement beats perfect planning that never ships.
If your team needs help establishing capacity planning practices or diagnosing persistent performance issues in Laravel, PHP, or eCommerce systems, reach out to discuss your specific situation. Sometimes an outside perspective identifies blind spots that internal teams have normalised.

