Skip to content
Engineering5 min read

Scalable software development: the architecture and code practices that matter

The data model, caching, queue, observability and testing practices that let software handle more users, more data and a bigger team without a rewrite.

By CTO, Coretus Technologies

Updated

Short answer

Software scales when you find and remove the real bottleneck, which is often the database. Design the data model and indexes around your main queries, keep application servers stateless, cache read-heavy data, move slow work to queues, and monitor latency, traffic, errors and saturation. Load test before big launches, not after an outage.

Scalable software keeps working as users, data and the team grow, without a rewrite. In practice that comes from a few habits: a data model built for your main queries, stateless application servers, caching for read-heavy data, queues for slow work, and monitoring that shows the bottleneck before users feel it. These basics matter more than the choice of architecture style.

Be clear about what needs to scale

Scaling means different things, and the fixes differ. Work out which of these you face:

  • More traffic: more requests per second than the servers can handle.
  • More data: queries that were fast with thousands of rows slow down with millions.
  • Sharper spikes: steady most of the time, then overwhelmed by a sale, a deadline or a morning rush.
  • More engineers: the code runs fine, but it is too tangled for a bigger team to change safely.

Measure before you change anything. A profiler or a slow-query log usually points to a handful of endpoints or queries that cause most of the pain.

Start with the data model

The database is the hardest part to scale later, so it deserves the most care early on.

  • Design tables around how the data is read, not only how it is written. Know your most frequent queries.
  • Add indexes for the filters and sort orders those queries use, and check query plans for full table scans.
  • Paginate every list. Never return an unbounded result set to a client.
  • Watch for N+1 queries, where code runs one query per item in a loop. They look fine in testing and fall over with real data.
  • Keep transactions short. Long transactions hold locks and block other users.
  • Move heavy reporting to a read replica or a separate store, so reports don't slow down the product.

Keep application servers stateless

If any server can handle any request, you scale by adding servers behind a load balancer. That only works when no user state lives in a server's memory or on its local disk. Keep sessions in a shared store, files in object storage and configuration in the environment. Autoscaling then becomes a setting rather than a project.

Choose the scaling signal with care. For a global healthcare provider's telehealth video platform, we scaled on the number of concurrent video rooms rather than on generic CPU load. The rebuilt platform supports 50k+ concurrent sessions with 99.99% service uptime.

Cache data that is read often and changes rarely

A cache stores the result of expensive work so the next request can skip it. Used well, it is one of the cheapest performance wins there is. Used carelessly, it shows users stale or wrong data.

  • Cache at the right layer: a CDN for static files and public pages, an in-memory store such as Redis for computed data, and HTTP cache headers for API responses that allow it.
  • Give every cached item an expiry time, and decide in advance how stale each type of data is allowed to be.
  • Clear the cache on write for data that must be current, such as prices or stock levels.
  • Guard against a stampede, where many requests try to rebuild the same expired item at once.
  • Measure the hit rate. A cache that rarely hits only adds complexity.

Move slow work to queues

Anything that doesn't have to finish before the user gets a response belongs in a background job: sending email, generating PDFs, calling slow third-party APIs, resizing images. A queue absorbs spikes, because work waits instead of failing. It also lets you scale workers separately from web servers.

Queues come with their own rules:

  • Make jobs idempotent, so running one twice does no harm. Many queues deliver each job at least once.
  • Retry with backoff, and send jobs that keep failing to a dead-letter queue for a person to inspect.
  • Don't update the database and publish an event as two separate steps. If one succeeds and the other fails, your systems disagree.

That last rule caused ledger drift for a core banking client. Services wrote to the database and published events separately, so when the message broker faltered, downstream fraud, analytics and notification systems missed updates. We fixed it with a transactional outbox and change data capture. Each event is written in the same transaction as the change and streamed out from the database log, and consumers ignore duplicates. The result was 100% data consistency.

Make the system observable

You can't scale what you can't see. Google's site reliability engineering book names four golden signals to monitor for any user-facing system: latency, traffic, errors and saturation. Start there, then add:

  • Structured logs with a request ID that follows each request across services.
  • Tracing, to see where the time goes inside a slow request.
  • Service level objectives for the journeys that matter, such as sign-in or checkout, with alerts when you are burning through the error budget rather than on every spike.
  • Dashboards the team actually looks at, reviewed after every incident.

Test for scale, not just correctness

  • Unit and integration tests keep refactoring safe as the codebase grows.
  • Contract tests catch breaking changes between services, or with partner APIs.
  • Load tests with realistic data volumes show where the system bends before your users find out. Run them before big launches and seasonal peaks.
  • Failure tests matter too. Stop a server, slow a dependency or fill a disk, and check that the system degrades instead of falling over.

Design for failure

At scale, something is always failing somewhere. Set a timeout on every network call. Use circuit breakers so one slow dependency can't tie up every worker. Decide in advance what the product does when a non-essential service is down. A product page that loads without recommendations is better than one that doesn't load at all.

Keep the architecture as simple as the problem allows

You don't need microservices to scale. A well-structured monolith with clear modules, a tuned database, caching and queues can carry a lot of traffic, and it is far easier to run. Split a service out when one part needs to scale or deploy on its own. Our microservices vs monolith decision guide covers when that point arrives.

Where to start

Find your three slowest endpoints and your three most expensive queries, and fix those first. Add the four golden signals to your monitoring if they aren't there yet. Then load test your busiest journey at a few times today's peak. If you want a second pair of eyes, our secure backend systems and site reliability engineering teams can review the results with you.

Sources

  1. Monitoring distributed systems (Site Reliability Engineering, chapter 6), Google

About the author

Sandeep Thesiya, CTO

CTO at Coretus Technologies. Leads architecture, DevSecOps and applied AI work across client projects.

LinkedInLeadership team

FAQ

Straight
answers

Have a different question? Ask it on a 30-minute call.

Book a call

Look at the database first. Slow queries, missing indexes, unbounded result sets and N+1 query patterns cause many scaling problems. Check the slow-query log and query plans for your busiest endpoints, then fix the worst few before adding servers, caches or a new architecture.

In practice

All case studies
  • HospitalitySaaS & Software

    Hotel system integrations: 85% faster API responses

    We wrapped a hotel group's ageing PMS in a modern API layer with caching and live sync, so new guest features no longer need risky changes to the core system. It runs at 99.99% availability, with 45ms API responses.

    System availability
    99.99%
    API response time
    45ms
  • HospitalitySaaS & Software

    Privilee: A membership platform that grows across venues

    We helped Privilee split its membership platform into independent services, add instant QR entry at venues and personalise venue suggestions with AI. Sync reliability reached 99.9%, and Privilee holds a #1 market ranking.

    Sync reliability
    99.9%
    Market ranking
    #1
  • Professional ServicesWeb & Mobile

    Client collaboration: Live project updates in place of email threads

    We built a live collaboration platform for a professional services business. Clients and delivery teams discuss work, share files, get updates and track project activity without long email chains.

    Connected experience
    One
    Business visibility
    Live
  • LogisticsWeb & Mobile

    Delivery dispatch: Live tracking instead of calling drivers

    We built a live dispatch and delivery platform for a growing logistics company. Operations teams and customers see driver status, job progress, location updates and delivery problems as they happen.

    Connected experience
    One
    Business visibility
    Live

Keep reading

All articles

Turn ideas like these into working software

Tell us where you are. We'll suggest the right team and a rough quote range.