All chapters Part VI · Quality
Chapter 31

Performance, Cost and Scaling

Your architecture sets your cost curve before you have a single user. Scale doesn't create the curve, it only reveals it. And the free tier that carries you to launch will fail on your best day, not your worst.


The concept

Three related but distinct concerns:

They interact: a slow query costs both patience and money; caching helps all three.

⭐ The cost curve is an architectural choice

  COST PER USER PER MONTH

  $$$ │                                     ╱  AI per interaction
      │                                 ╱      (model call per action)
      │                             ╱
   $$ │                         ╱
      │                     ╱  ────────────── Server-rendered + media
    $ │                 ╱ ────
      │            ╱────
    0 │═══════════════════════════════════  Static + client-computed
      └──────────────────────────────────────
        1k      10k      100k      1M   users

Which line you're on is decided by a handful of early choices:

ChoiceFlattens the curveSteepens it
ContentStatic, bundled or CDNGenerated per request
StateComputed on deviceComputed server-side per view
MediaOptimised, cachedFull-size, re-processed
AINone, or cached/tieredA model call per interaction
DataRead-mostly, indexedN+1 queries, full scans

You cannot refactor a cost curve later without rewriting the product. Decide it deliberately (Chapters 01, 23).

Know every ceiling before launch

Free tiers are a legitimate strategy, not a compromise, provided you know where each one ends and how it fails.

 FOR EACH SERVICE, WRITE DOWN:
   the limit · the PERIOD (per second / day / month / cumulative) ·
   what happens at the limit (throttle? reject? bill?) ·
   whether it fails LOUDLY or SILENTLY ·
   the exact cost of the next tier

The period matters more than the number. A daily email limit fails differently from a monthly one, it fails on the day your post works, silently, to the people you most wanted to reach.

⭐ Scaling problems arrive on your best day. The post that lands, the launch that works, the mention that goes around, that's when you hit the ceiling. Pay the small monthly fee before the campaign, not during it.

Typical breaking points

Approximate, but the shape is reliable:

UsersWhat tends to break first
~1kEmail quotas (bursty), free-tier project pausing
~10kDatabase size, monthly active limits, N+1 queries under real data
~100kBandwidth, egress costs, single-instance capacity, support volume
~1MEverything you deferred; you now need a team

Performance work that actually matters

In order of user-perceived impact:

  1. Time to first meaningful content. Everything else is secondary.
  2. Input responsiveness. Under ~100ms feels instant; over ~200ms feels broken.
  3. Avoiding layout shift. Content that jumps is worse than content that's slow.
  4. Perceived speed. Skeletons, optimistic updates and streaming beat real speed for user satisfaction.

Measure before optimising. Developer intuition about bottlenecks is unreliable; profilers are not.

The usual real culprits, in frequency order: unoptimised images · N+1 queries · missing indexes · blocking JavaScript · unnecessary re-renders · synchronous work on the main thread.

Database scaling, in order

Do these in sequence, most people skip to the last:

  1. Add the missing index. Usually the whole problem.
  2. Fix N+1 queries. One query per row is the most common performance bug in web apps.
  3. Cache read-heavy, rarely-changing data.
  4. Read replicas for read-heavy loads.
  5. Denormalise deliberately, with a labelled cache.
  6. Partition or shard. You are unlikely to need this.

Cost control that must exist before launch

Cloud bill surprises usually come from egress, log volume, or a runaway loop, not from success.


📐 Best practice

Choose your cost curve deliberately, early.

Write down every ceiling, its period, and its failure mode.

Set billing alerts before launch.

Upgrade the bursty-limit services before any campaign.

Measure before optimising.

Optimise images first, usually the largest single win.

Index what's slow; don't index speculatively.

Cache aggressively, with a deliberate invalidation strategy.

Bound every queue and retry.

Know your cost per user per month, and re-check it as features land.

Load-test the one flow that would break, usually signup or the main read path.


💀 Common mistakes

Optimising before measuring. Days on something that wasn't the bottleneck.

Not knowing a limit's period. Daily limits fail on your best day.

No billing alerts. The first signal is an invoice.

Ignoring egress. Bandwidth out is often the largest line item.

N+1 queries. Fine with 10 rows, fatal with 10,000.

Unbounded growth, queues, logs, caches, uploads.

Premature scaling architecture. Microservices and Kubernetes for zero users.

Free tier project pausing. Some providers suspend inactive projects, fine while building, catastrophic the week before launch.

Counting anonymous users as billable actives without realising.

Bundle size treated as a hosting cost. It's a conversion problem first.

Uncapped AI features. An unbounded liability with a marketing budget (Chapter 23).


The professional workflow

 1. CHOOSE THE COST CURVE — static/client-computed vs server vs AI-per-call

 2. INVENTORY LIMITS
    for each service: limit · period · failure mode (loud/silent) ·
    next-tier cost

 3. SET BILLING ALERTS + hard caps where available

 4. INSTRUMENT
    p50/p95 latency · error rate · cost per user · slowest queries

 5. MEASURE BEFORE OPTIMISING

 6. FIX IN ORDER
    images → N+1 → indexes → blocking JS → caching

 7. BOUND EVERYTHING — queues, retries, uploads, logs, caches

 8. LOAD-TEST the one flow that would break

 9. PRE-CAMPAIGN CHECK
    upgrade bursty-limit services BEFORE traffic arrives

10. REVIEW COST PER USER MONTHLY

Tools, websites & costs

NeedToolCost
Web performanceLighthouse, WebPageTest, PageSpeedFree
Real-user monitoringVercel Analytics, SpeedCurve, Sentry$0-$$$
APM / tracingSentry, Datadog, Grafana CloudFree tiers
Query analysisEXPLAIN ANALYZE, pganalyze$0-$$$
Load testingk6, ArtilleryFree (OSS)
Image optimisationsharp, Squoosh, CDN transforms$0
CDNCloudflare, Bunny$0-20/mo
Cost monitoringProvider dashboards, Vantage, InfracostFree tiers
CachingRedis (Upstash), platform cachesFree tiers
UptimeBetter Stack, UptimeRobotFree tiers

A typical indie stack serving ~100k users runs roughly $50-150/month, infrastructure is rarely the constraint. Distribution is.


Alternatives & trade-offs

Optimise now vs later. Premature optimisation wastes time; deferred optimisation becomes a rewrite. Architectural decisions (cost curve, data model) early; implementation optimisation later, when measured.

Cache vs compute. Caching is fast and cheap and introduces staleness and invalidation bugs. Computing is always correct and costs more. Cache what's read-heavy and changes rarely.

Client vs server compute. Client compute is free to you and limited by the weakest device. Server compute is consistent and costs money per user.

Scale up vs out. Vertical (a bigger machine) is simpler and hits a ceiling. Horizontal is unbounded and needs statelessness and coordination. Scale up until it genuinely stops working, that's much further than most people assume.

Managed vs self-hosted. Managed costs more per unit and removes an operational burden. Self-hosting saves money at scale and costs your attention. Below ~$1k/month in infrastructure, managed almost always wins.


Checklist


📓 Case Study: a flat cost curve, arrived at by accident

Project: SOLIS. At launch scale, running costs were $0/month, against fixed annual costs of a developer account and a domain.

The architecture that produced it:

DecisionEffect on the curve
Content bundled in the app, not servedNo per-read cost
Local-first; state computed on deviceMost actions generate zero backend load
Derived values (streak, rank, progress) computed client-sideNo server aggregation
No AI at runtimeNo variable cost per interaction
Static marketing siteEffectively free

Projected ceilings, from the real stack:

LayerFree ceilingBreaks atFixCost
Transactional email300/day~1k users (bursty)Paid tier~$9/mo
Database size500 MB~30k usersPro$25/mo
Monthly actives50k~50k usersProincluded
Web bandwidth100 GB~250k page viewsPro$20/mo
Payments layerunder $2.5k/mo revenue~$30k/yr revenue1%scales with revenue

Roughly $55/month plus 1% of revenue to serve ~100k users.

⚠️ Deviation 1: the flat curve was an accident, not a design.

It fell out of an engineering preference for offline-first, not a business decision. It could easily have gone the other way, the product plan's Phase 2 was an AI feature replying to user writing, which would have moved cost per user from ~$0 to a real per-message number.

To its credit, that feature was costed before building (Chapter 23), tiered models, free tier excluded, caps, and a margin check. But that analysis was triggered by a feature that happened to be expensive, rather than being standard practice from chapter one.

The daily limit that fails on your best day. The email service allowed 300 sends per day. A single post that produced 400 waitlist signups would mean 100 people silently receiving nothing, on the best day the project had. That's the shape this chapter warns about: the limit is bursty, the failure is silent, and it lands precisely when you'd most notice.

Nine dollars a month, paid before a campaign rather than during it.

⚠️ Deviation 2: bundle size was treated as a hosting question, not a conversion question.

The app bundled 525 MB of images. The decision was defensible, it fixed genuine reliability problems with remotely-loaded assets and made the app fully offline.

But 525 MB is an install-conversion problem before it's a cost problem: slow downloads, cellular-download limits, and abandonment on the store's size line. The correct architecture, bundle the first portion, lazy-download the rest with a local cache, is about a day of work and was never done.

⚠️ Deviation 3: one image was optimised; the pipeline wasn't.

A hero image went from a 1.9 MB PNG to a 117 KB JPEG, 16× smaller, visually identical. The same folder still contained a 1.9 MB logo, two ~1.8 MB scene images, and a 1.5 MB avatar.

📐 Optimising one file is a fix; optimising the pipeline is a solution. A pre-deploy check that fails on any image over ~300 KB would have caught all of them.

One thing anticipated correctly: anonymous users were gated behind a meaningful action rather than created on app launch. Without that gate, every casual install would have counted toward the billable monthly-active metric, a cost problem disguised as an auth decision (Chapter 19).

🚩 Unvalidated. No users, so no observed load. These are projections from the real architecture against published service limits, the method is sound; the numbers are untested.


Lessons

  1. ⭐ Architecture determines the cost curve; scale only reveals it. Choose it deliberately and early.
  2. Local-first and static content are cost strategies, not just UX ones.
  3. Know the period of every limit, not just the number.
  4. The limits that fail silently are the dangerous ones. Email is the usual offender.
  5. ⭐ Scaling problems arrive on your best day. Upgrade before the campaign.
  6. Compute AI costs before building. Uncapped is unbounded liability.
  7. Bundle size is a conversion problem first.
  8. Optimise the pipeline, not the file. Add a size check to your deploy.
  9. Measure before optimising. Intuition about bottlenecks is unreliable.
  10. Anonymous users can be billable users. Gate their creation.
  11. ~$55/month serves ~100k users on a modern stack. Infrastructure is not your constraint, distribution is.

Next: Chapter 32: Content and Asset Production at Scale →

This completes Part VI, Quality.

Useful? Share this chapter