Cloud glossaries explain horizontal scaling vs vertical scaling as more machines against a bigger machine. They say nothing about the order and the bill. On a managed host a bigger instance usually comes first: on Render it is one setting and no code change, and Railway by default scales a service up to its plan’s limits. More instances work only once the app keeps nothing on the instance.
What we are comparing, and for whom: scaling meaning in software, in one paragraph
Scaling in software means changing how much work a system can handle so it keeps up with load. It has two directions: vertical scaling gives one instance more CPU and memory, and horizontal scaling runs more instances side by side behind a load balancer.
Both directions are the last steps in a longer order of fixes for scaling web applications, and that order sits inside the wider work of web performance optimization. Docs use other names for the same pair. Microsoft’s Azure cloud computing dictionary calls vertical scaling “scaling up or down” and horizontal scaling “scaling out or in”. Scalability is the ability to grow; elasticity is growing and shrinking automatically as load moves. NIST’s definition of cloud computing puts the second idea as capabilities that “can be elastically provisioned and released, in some cases automatically, to scale rapidly outward and inward commensurate with demand.”
The reader I have in mind is a team of one to five people on a managed host such as Render, Railway, Vercel or Supabase, where both directions are settings in a dashboard rather than hardware to buy. Scaling lets a working app handle more of the same work. It does not make a slow query fast.
The Performance & Scale pillar averages 53.3 out of 100 across the third-party apps I audited, and every one of the 21 was scored on it. The 21 were audited in June and July 2026: 11 public vibe-coded apps reviewed across all 12 pillars, plus a held-out group of 10 apps the engine had never seen, audited blind. I picked those apps, so the figure describes that selected set and is not a rate for AI-built apps in general. The host facts on this page are documented analysis from each host’s own docs and pricing pages, read on 4 October 2026, and none of it is a benchmark.
Horizontal scaling vs vertical scaling: the comparison, row by row
Horizontal scaling vs vertical scaling comes down to three differences that matter to a small app: vertical needs no code change but stops at the largest size on the price list, horizontal moves the ceiling to the shared database but needs a stateless app, and only horizontal survives the loss of an instance. Neither fixes a slow query.
The rows are my comparison of the two directions, except the cells in the last column, which come from the hosts’ docs.
| Row | Vertical (scale up) | Horizontal (scale out) | What it means on a managed host |
|---|---|---|---|
| What changes | The size of one instance: CPU, memory, disk throughput | The number of instances behind a load balancer | Render: you scale vertically by changing a service’s compute plan and horizontally by changing its number of running instances |
| What you touch | The plan or instance-type setting | The instance count, the replica count or an autoscaling rule | Render: manual scaling on all workspaces, autoscaling on a Pro workspace plan or higher. Railway: replicas in the service settings, and a service scales up to its plan’s vCPU and memory limits by default |
| Code change needed | None | None if the app is stateless; real work if it is not (the five-row table below) | Render: a service with an attached persistent disk cannot scale to multiple instances |
| The ceiling | The largest size the host sells | The next shared limit, usually the database’s connections | Render: up to 100 instances on paid plans up to 8c-32g. Railway Pro: up to 42 replicas per service |
| What applying it does to availability | Can involve a restart or brief downtime | The running instance keeps serving while others are added | Supabase: compute changes are “usually applied with less than 2 minutes of downtime”. Render’s compute plan docs: a plan change redeploys “with zero downtime”, except for a service with an attached persistent disk, which needs “brief downtime”; new instances are provisioned “immediately”. Railway: replicas change “without triggering a full redeploy” |
| Failure tolerance | One instance is one point of failure at any size | Two or more can lose one and keep serving, and deploys can roll | Render load-balances traffic “evenly” across running instances; Railway distributes it “randomly” within a region |
| Cost curve | A new price for each size step | Linear: one more instance at the same price | Render bills each running instance of a scaled service; Railway bills per GB-second and vCPU-second. Figures are in the cost section below |
| What it cannot fix | A slow query, an N+1 page, a missing index, a third-party timeout | All of those, plus anything limited by the shared database | Supabase’s compute docs ask you to assess whether a bottleneck is hardware or software before upgrading (the order in the next section) |
| Undo | Pick the smaller size again | Lower the count | The same settings as the second row |
The host cells come from Render’s scaling docs, Railway’s scaling docs and Supabase’s compute and disk docs. Diagonal or hybrid scaling is both directions in sequence: a bigger instance first, then more of them once one is not enough.
Which one your app needs first
The scaling order for a small app has three steps: prove the app is short of CPU or memory and not waiting on a slow query, then move to a bigger instance, then make the app stateless and add a second instance when one is not enough.
The third step has a second trigger: one instance can be a risk you will not accept even when it copes with the load. Supabase’s compute docs give the same first step: before buying more compute, “assess whether your bottlenecks are hardware-constrained or software-constrained”, for example by looking at the number of connections or at query performance.
First, neither: rule out the slow query and the connection limit
How to find which limit an AI-built app hits first, symptom by symptom and platform by platform, is its own diagnosis, and on Supabase the detail of what more Supabase compute can and cannot change is worth reading before any upgrade. In short, more compute moves a real CPU or memory limit and only postpones a query problem. The signal this page acts on is narrow. During the slow period, CPU or memory sits near the top of its graph for minutes at a time, while query times stay flat. If query times climb with the load instead, the fix is in the queries or the connections, and neither direction of scaling is the next step.
When vertical scaling is the right first move
Vertical scaling fits when one instance is saturated (CPU pinned, memory climbing, the event loop or worker pool full) and the app has never run as more than one copy. It is one setting, it is reversible, and it needs no code change. That is why it comes first on a managed host.
Its limits are three. The largest size on the price list is the end of the road. Applying it may cost a restart or a short outage, depending on the host (the availability row in the table above). And one bigger instance is still one point of failure. A process that the host killed for running out of memory is a different problem: read up on the OOM kill before paying for a bigger size, because a leak fills any size.
As an illustration, with no timing or throughput claimed: the same service, same code, same host, moved from one instance size to the next one up, with the CPU graph read before and after the move. Which lever to pull in the middle of a sudden traffic spike belongs to what to do when an app goes viral.
When horizontal scaling is the right move, and the five things the app must stop doing first
Horizontal scaling breaks an app that keeps state on the instance, in 5 common places: sessions in memory, uploads on local disk, scheduled jobs inside the web process, in-memory rate limits and caches, and live connections. Each one fails in a way you can reproduce with two instances in staging.
Scale out when the largest sensible instance is not enough, when a deploy or a crash must not take the app down, or when load is spiky enough that paying for a big instance all day is waste. The precondition is in the Twelve-Factor App’s processes factor: “Twelve-factor processes are stateless and share-nothing.” Anything that has to persist goes in a backing service, typically a database.
The table is my own summary of common patterns, not an audit statistic; the cells with a host or tool name come from that vendor’s docs.
| What the app keeps on the instance | What breaks with two instances | The fix |
|---|---|---|
| Sessions in process memory | Users are logged out at random as requests land on the other instance | Sessions in the database, a shared store or signed cookies. Sticky sessions are no way out: the Twelve-Factor App calls them “a violation of twelve-factor”, and Railway does not support them |
| Uploads written to the local disk | A file exists on one instance and returns 404 on the other. On Render, by default, local file changes are lost on every redeploy or restart | Object storage |
| Scheduled jobs started inside the web process | Every instance runs the job, so emails and charges go out once per instance | One scheduler, or a lock every instance checks. PgBouncer’s feature table marks session-level advisory locks “Never” under transaction pooling |
| In-memory rate limits and caches | Each instance counts separately, so the limit is multiplied by the instance count, and cached values disagree | A shared store |
| WebSocket or server-sent event connections | A client attached to one instance misses events published on another | A shared pub/sub channel. Render’s WebSocket docs say its load balancer assigns each incoming WebSocket connection to “a random instance” |
For the lock row, PgBouncer’s feature table lists the features transaction pooling breaks, so a lock taken through a transaction-mode pooler needs a different design. For the cache row, the choice of shared store and its invalidation belong to caching strategies for web applications. Code an AI tool generates often assumes one running copy, so all five rows are worth checking before the instance count changes. Serverless functions scale out by default and hit their own ceilings, such as Lambda concurrency, instead of an instance count.
The database scales on different rules
A relational database is usually scaled vertically first: on a host such as Supabase, a larger compute size raises the connection limits with it. Every app instance opens its own connections, so adding instances multiplies them, and a pooler comes before a second instance. Read replicas scale reads only.
App instances are cheap to multiply and a relational database is not, which is why the database usually gets the bigger size. On Supabase, the maximum number of “database connections, and pooler clients depends on your compute instance size”, and the compute page lists both per size. Supabase’s prices size by size are a separate subject, covered in a breakdown of Supabase pricing.
Adding app instances moves the load onto those limits. The mechanism of every instance opening its own pool is why a second instance can exhaust connections the first never touched. On the server side, PostgreSQL’s connection settings say max_connections “can only be set at server start”, and that PostgreSQL sizes some resources, shared memory included, on its value. So the fix is a connection pooler in front of the database, set up before the second instance, rather than a higher max_connections.
One finding from my audits fits here: a medical app that opened new database connections on every request, a brand-new set each time instead of reusing a pool. My take on the lesson: more app instances would have multiplied that pattern, not relieved it.
Supabase read replicas are horizontal scaling for reads only: “You can only read data from a Read Replica.” Replication is asynchronous, and Supabase calls the delay before a change reaches the replica “replication lag”. A page that writes and then reads its own write straight away should read from the primary. Sharding and distributed SQL split the data itself across servers; for a small app that is a redesign, not the next step.
What each one costs you
Scaling costs take two shapes. Vertical steps are priced per size on the host’s list, so each step up is a new line on the bill. Horizontal cost is linear, one more instance at the same price, but it arrives with the engineering time to make the app stateless. The cheapest capacity is usually a query fix.
| Host | The vertical step | The horizontal step | How it bills | Source |
|---|---|---|---|---|
| Render | Compute plans: 0.5c-512mb at $7/month, 1c-2g at $25/month, 2c-4g at $85/month | More instances on the same compute plan; autoscaling needs a Pro workspace plan ($25/mo + compute) or higher | Render bills each paid instance of a service or datastore, prorated by the second, and documents a monthly spend limit only for pipeline minutes | render.com pricing, scaling and FAQ pages |
| Railway | Vertical autoscaling, on by default, up to the plan’s vCPU and memory limits | Replicas: 1 per service on Free after the trial, up to 42 on Pro | Memory $0.00000386 per GB a second, CPU $0.00000772 per vCPU a second (about $10 per GB and $20 per vCPU over a 30-day month, my arithmetic); Hobby $5 minimum usage, Pro $20 minimum usage | railway.com pricing and scaling docs |
| Supabase (the database) | Compute sizes, priced on Supabase’s pricing page | Read replicas, which run on the same compute size as the primary | Paid Supabase plans include $10 in monthly compute credits, granted per organization rather than per project, which covers one project on Micro or Nano. Replica compute is charged by the hour, sits outside the spend cap and is not covered by the credits | supabase.com compute and read replica usage docs |
| Vercel | Function memory and CPU: 2 GB / 1 vCPU by default, 4 GB / 2 vCPUs on Pro and Enterprise; Hobby stays at 2 GB | Automatic: functions scale up to 30,000 concurrent on Hobby and Pro, 100,000 on Enterprise | Priced on plan and resource usage (Active CPU, Provisioned Memory, Invocations); Hobby is for non-commercial, personal use only | vercel.com concurrency, memory, pricing and Hobby docs |
Prices checked 4 October 2026 on each host’s own pricing and docs pages.
On Render, a second instance of the 1c-2g plan doubles that line, and Render’s own example puts two instances for a whole month at “2x the monthly price” of the compute plan. On Vercel there is no instance count to buy, so the limit moves: Vercel’s function scaling docs describe a burst limit of “1000 concurrent executions per 10 seconds, per region”, and the database behind the functions becomes the shared ceiling.
I’d describe the two cost shapes this way: the vertical bill jumps at each step, while the horizontal bill grows evenly but starts with a cost that never shows on an invoice, the hours spent moving sessions, files, jobs and counters off the instance. Before either, a slow query that runs on every page view is capacity you can win back for no new spend.
Scaling in cloud computing: autoscaling, serverless, and what Azure calls each one
Scaling in cloud computing is the same two directions applied on demand by the provider. Autoscaling adds and removes instances by rule between a floor and a ceiling you set. Serverless runs as many copies as there are requests. None of them scales the database for you.
In cloud computing, scaling becomes something the provider applies by API or by rule, in either direction, which is what makes the elasticity in NIST’s wording possible. An autoscaling rule watches a metric such as CPU, request count or queue depth and adds or removes instances between the minimum and the maximum you set. It is horizontal scaling run by a rule, so every statelessness fix above applies to it unchanged. It also needs a ceiling, because the bill follows the instance count.
Serverless is the limit case. On Vercel, “All functions on Vercel scale automatically based on demand”, so there is no count to set at all. Switching autoscaling on in the middle of a spike is a different decision from the one on this page. What none of these does is scale the database: the provider adds app instances, and every one of them still talks to the same database with the same connection limit.
What is scaling in Azure: scale up, scale out and autoscale
Scaling in Azure uses two terms for the two directions: scale up changes the App Service plan’s pricing tier, which is vertical, and scale out changes the instance count, which is horizontal. Autoscale changes the count by rules you write in Azure Monitor.
Microsoft’s autoscale overview states: “Autoscale does not support vertical scaling.” App Service also has a fourth term, automatic scaling, which the FAQ below separates from autoscale.
| Azure term | Direction | Where it is set, and the tiers |
|---|---|---|
| Scale up | Vertical | The App Service plan’s pricing tier |
| Scale out (manual) | Horizontal | The plan’s instance count, Basic and up |
| Autoscale | Horizontal, by metric and schedule rules | Azure Monitor, Standard and up |
| Automatic scaling | Horizontal, by incoming HTTP traffic | The plan’s scale-out setting, Premium v2 to v4 |
The sources are Azure App Service automatic scaling and Azure Monitor autoscale. The instance ceilings for each App Service tier belong with the full scaling order, not here. AWS uses the same two directions under its own names: on EC2, horizontal scaling by rule runs through Auto Scaling groups.
How to check the scaling change worked
A scaling change is proven by 5 checks: the limit you measured before, the same load test run before and after, a two-instance trial in staging against the five breaks, the database connection count after instances are added, and a written capacity sentence with a number in it, measured or labeled a projection.
Run them on a staging copy on your own host: Render or Railway running two instances of the same production build. Two instances need a paid compute plan on Render, where the free plan allows “1 (scaling unavailable)”, and more than the single replica Railway’s Free plan allows after the trial. Have each response log or return the instance id from the host’s variable, Render’s RENDER_INSTANCE_ID (“The unique identifier of the current service instance. Useful for scaled services with multiple instances.”) or Railway’s RAILWAY_REPLICA_ID (“The replica ID for the deployment.”), so you can see that both instances answered.
- 01 Write down the limit you measured before the change, with its time range and the graph; if nothing was near its limit, stop, because more capacity will not help, unless the change is a second instance added only so one failure cannot take the app down, which goes straight to the third check
- 02 Run the same load test before and after the change, with the same workload and duration, and keep both reports; the change worked only if the after-report shows lower p95 latency at the same load and an error rate no higher than before
- 03 Before going horizontal in production, run two instances in staging and try each of the five rows: sign in and send ten requests (every response still signed in, whichever instance answered); upload a file and fetch it ten times (every fetch returns 200); let a scheduled job fire once (one email or one row, not two); send a rate-limited request past its limit from one account (refused after the limit, not after twice the limit); open a live connection and trigger an event through a request the other instance answers (the event arrives). Keep a pass or fail per row
- 04 After adding instances, read the connection counts at the same load: each instance that keeps its own pool adds about one pool of client connections, and the count at the database stays under its limit
- 05 Write the result as one sentence with a number in it, measured or labeled as a projection
I settle on ten requests and ten fetches: enough for both instances to answer more than once, whether the host spreads traffic evenly or at random. The first check starts from the diagnosis in the first step of the order above. For the second, running the same tool and script both times is what makes the two reports comparable, the core of load testing a web application; judge the result on tail latencies, because an average can improve while the slowest requests get worse. In the third, a stateful build fails the matching row, which is the point: the row tells you which fix is missing. For the fourth on Supabase, the Database report lists a “Database connections” chart for Free and Pro plans, counting pooler connections to the database. The fifth follows the format of what a capacity plan is.
Deliverable 9.2 of the Production Hardening Sprint is verified this way: report the workload, duration, environment, concurrency, latency, and error rate before and after changes.
Where the sprint fits
Three deliverables in the published scope of the sprint touch this page. Deliverable 9.2, Load test and bottleneck fixes: simulate concurrent users, identify the first bottlenecks, fix them, and rerun the workload, with its check as written after the list above. Deliverable 9.6, Written capacity statement: document measured concurrent capacity on the current infrastructure and the changes needed to plan for five times that workload; it is verified by linking capacity claims to load-test evidence and identifying untested projections as projections. The caching one is deliverable 9.1, Appropriate caching: cache repeated reads where it is safe, with explicit invalidation and customer-data boundaries. Your app’s current framework and hosting setup are our starting point, and we refactor or replace components where the production work requires it. Hosting, paid tools, and API usage remain in your accounts.
Common questions about scaling an app
Can microservices scale horizontally?
Yes. Each service can run its own number of instances, and each one that keeps state on the instance still needs the five fixes above. A small app does not need microservices to scale out: one stateless monolith behind a load balancer scales the same way, with fewer moving parts.
What is the difference between Azure autoscale and automatic scaling?
Autoscale changes an App Service plan’s instance count by metric and schedule rules you write in Azure Monitor, on Standard tiers and up; automatic scaling is an App Service option that adds instances on incoming HTTP traffic without any rules, on Premium v2 to v4. Microsoft’s docs say only one scaling method should be active for an App Service plan.
What is the purpose of Auto Scaling?
Auto Scaling keeps the number of running instances matched to the load. In AWS’s words, Amazon EC2 Auto Scaling “helps you ensure that you have the correct number of Amazon EC2 instances available to handle the load for your application.” In my view the floor and the ceiling you set matter as much as the rule, because the bill follows the count. Whether to turn it on during a spike is a separate decision.
What is the difference between horizontal and vertical scaling in business?
In business and ad buying, horizontal usually means more markets, products or ad sets, and vertical means more budget or depth in the one you already have. That is my understanding of how the phrase is used there; this page is about servers, where the same words mean more instances against a bigger instance.
Can you give me an example of scalability?
Yes, as an illustration with stated assumptions. A web service runs as one instance, and during the busy hour its CPU graph sits at the limit at a request rate you measured. Step one moves it one size up and repeats the same load test. Step two, once sessions and uploads have moved off the instance and the five staging checks pass, runs it as two instances and repeats the test again. At each step you keep the CPU graph, the p95 latency and the error rate at that same request rate, so the example proves itself with your own numbers rather than anyone’s benchmark.
If you have a working app built with these tools and need it ready for real customers, this is what we do.
Built your app with AI? Get it ready for what comes next.
Whether you’re preparing for a launch, client handover, internal rollout, or technical review, we help strengthen the security and reliability of the app you already have.
Our engineers implement and verify the production controls inside your existing codebase, then hand over the changes, test results, and operating instructions.
Explore the Sprint →No commitment to proceed.
10 working days · Your existing codebase · Documented verification