Autoscaling tuned for a two-week admissions spike
An education client in Noida runs admissions through a web portal. For ten and a half months, their traffic is flat: 200 concurrent users, a handful of form submissions per minute, mostly admin staff checking applicant status. Then admissions open, and for two weeks they get 8,000 concurrent users hitting the same forms. The rest of the year, that capacity sits idle.
The default autoscaling setup most teams land on is a target tracking policy against CPU utilization, set to scale at 60%. This works fine for gradual load increases. It is the wrong tool for a spike that goes from 200 to 8,000 users in under five minutes.
The problem is boot time. A fresh EC2 instance takes 90 seconds to come up, plus another 30 for the application to pass its health check and start receiving traffic from the ALB. By the time CPU crosses 60% and the autoscaling group provisions a new instance, the existing fleet is already saturated. Users see 502s for two minutes before capacity catches up. We watched this happen on the first day of admissions season last year.
We reconfigured the group with two changes.
First, scheduled scaling. Admissions dates are known months in advance. We set a recurring scheduled action that bumps the group's desired capacity to 12 instances at 8:00 AM on the day admissions open, and drops it back to 2 at 6:00 PM on the closing date. The instances are warm before the first user arrives. This is not predictive scaling — there is nothing to predict. The calendar is published on the client's website.
Second, step scaling on request count per target, not CPU. CPU is a lagging indicator for web traffic. By the time CPU is high, the request queue is already backing up. We set a CloudWatch alarm on RequestCountPerTarget from the ALB metrics:
Alarm:
Metric: RequestCountPerTarget
Threshold: 500
Period: 60
EvaluationPeriods: 1
DatapointsToAlarm: 1When requests per target exceed 500 for one minute, the group adds 4 instances. If it exceeds 1,000, it adds 8. The step adjustments are coarse on purpose. Fine-grained steps (add 1 at 500, add 1 at 600, add 1 at 700) produce a lot of provisioning churn for little benefit. We would rather overshoot by a few instances during a two-week window than undershoot and lose applicants.
The warmup time on the group is set to 120 seconds, matching the observed boot-plus-health-check interval. The default 300 seconds is too long for this workload — it delays the next scaling step by three minutes, which is three minutes of degraded service.
We left CPU-based target tracking in place as a fallback, set to 70%. It has never fired. The request-count policy reacts before CPU gets there. But if the traffic shape changes next year — say, a heavy background job starts consuming CPU without driving HTTP requests — the fallback catches it.
One thing we considered and dropped: predictive scaling. AWS's predictive scaling uses machine learning to forecast traffic patterns based on historical data. For a pattern that repeats on known dates, a cron entry is simpler, more predictable, and does not require 17 days of historical data to train on. Predictive scaling is worth setting up when you have variable traffic with a daily pattern you cannot control. This is not that case.
The client pays for 12 instances for two weeks and 2 instances for the other 50. That is the tradeoff they accepted: overprovisioned during the spike, underprovisioned risk eliminated. The admissions portal stayed up through the full two weeks this year with no 502s. We check the CloudWatch dashboards during admissions season and adjust the thresholds if the traffic shape drifts, but we have not had to since the first iteration.