Scale is not a feature you add at the end. It is an architectural decision you make at the beginning — or a problem you solve expensively later.
After building and scaling software for 500+ clients across fintech, healthcare, logistics, and consumer apps, here are the lessons that matter most.
Design your data model for reads
Most production systems read far more than they write. Denormalise early where it makes sense. Use read replicas. Cache aggressively with a clear invalidation strategy. Know your access patterns before you design your schema.
Write down the ten queries the product will run most often before you draw a single table. A schema designed around those queries ages far better than one designed around a tidy diagram.
Separate your write and read models early
CQRS is not always the right answer, but separating command and query paths gives you flexibility to optimise each independently as your load profile changes.
Queue everything that does not need to be synchronous
Payments, emails, notifications, report generation — if the user does not need the result before the next page load, put it in a queue. This single change removes more production incidents than almost anything else.
Queued work must be safe to run twice, because sooner or later it will be. Give each job a unique identifier and make its effect repeatable without harm.
Instrument before you optimise
You cannot optimise what you cannot see. Set up distributed tracing from day one, not after you hit a performance problem in production.
Keep services stateless
If any server can handle any request, you can add capacity by adding servers. Keep sessions, uploads and caches in shared stores, not on the machine that happened to receive the request.
The database is usually the bottleneck
Application servers are easy to multiply; the database is not. Most scaling problems we are called in for come down to missing indexes, queries that fetch too much, one query per row in a loop, or too many open connections. Fix those before reaching for anything exotic.
Plan for failure, because it will happen
- Set timeouts everywhere. One slow dependency should not stall the whole system.
- Degrade gracefully. If recommendations are down, the product page should still load.
- Limit what one customer can consume. Rate limits protect everyone else.
- Apply backpressure. When a queue grows faster than it drains, slow the intake instead of falling over.
Load test before your users do it for you
Test with realistic data volumes and realistic traffic shapes, including the spike you hope for on launch day. A system that is fast with a thousand rows can be unusable with ten million.
Do not over-engineer for scale you do not have
The opposite mistake is just as expensive. A simple, well-structured system with good measurements can grow a long way, and it lets you see where the real limits are. Our piece on monoliths and microservices covers when added complexity pays for itself.
Scaling a product or planning one that needs to? See our software development services or hire senior engineers who have done it before.