Infrastructure growing in stages from a single server to multi-region data centers

Most applications do not need Kafka, microservices or a sharded database on day one, and building them early wastes money and makes systems harder to run. That is the argument of a Medium article by the developer who writes as bektiaw, which walks through how an architecture should evolve from a single server to 100 million daily active users. The milestones are rough, but the logic behind them is sound: measure first, then fix only the bottleneck in front of you.

Key points on scaling an architecture from 1 to 100M users in 20 seconds

  • Up to about 10,000 daily users, a single well-run server is usually enough.
  • The first real changes are a separate database, a connection pooler, a CDN and two app servers.
  • Sharding and microservices solve data volume and team coordination problems, so in most cases they should come late.

The original article starts from a familiar frustration: system design diagrams full of load balancers, Redis, Kafka and microservices that look impressive but describe companies far larger than most readers work for. Its rule of thumb is that each layer of infrastructure should arrive only when a measured problem demands it. Below is that roadmap, expanded with the numbers and tooling that sysadmins actually deal with at each stage.

Turning users into requests per second

User counts mean little until they are converted into load. A useful back-of-the-envelope formula is daily active users multiplied by requests per user per day, divided by 86,400 seconds, and then multiplied by a peak factor. If 10,000 users each generate 20 requests a day, that is 200,000 requests, or about 2.3 per second on average. Traffic is never flat, so a peak factor of three to five puts the busy hour at roughly 10 requests per second, which matches the article’s estimate. The same arithmetic gives about 100 requests per second at 100,000 users and around 1,000 at one million, assuming similar behaviour.

Those figures are modest for modern hardware. A single virtual machine with a properly indexed PostgreSQL or MySQL database can handle far more than 10 requests per second, which is why the article recommends running app and database on one box at the start. What matters early is getting the basics right: a clean data model, stateless application servers, sessions kept out of local memory, a plan for user uploads, and backups, logs and metrics from day one.

10,000 users: removing single points of failure

The first changes are about contention and availability rather than raw traffic. Moving the database to its own server or a managed service stops it competing with the application for CPU and memory. Adding a connection pooler such as PgBouncer matters more than many teams expect: PostgreSQL creates one operating system process per connection and ships with max_connections set to 100 by default, so a few app instances with generous pools can exhaust it. In transaction pooling mode, PgBouncer lets hundreds of clients share a few dozen real database connections.

A content delivery network is described in the article as the cheapest, highest-leverage scaling move available, because it takes static files off the application servers entirely. Running two app servers behind a load balancer is mainly about safe deployments: one instance can be updated while the other keeps serving. The single server from the first stage is also the moment to run a proper audit, along the lines of this Linux server checklist, before it becomes a template for everything else.

100,000 users: cache, replicas and queues

At this point the database is usually the first component to feel the pressure, and the cheapest fix is still the query plan. Running EXPLAIN ANALYZE on the slowest queries regularly reveals missing indexes and N+1 patterns that no amount of hardware will fix. After that comes caching with Redis or Memcached, typically with the cache-aside pattern: the application checks the cache, falls back to the database on a miss and stores the result.

Each tool brings its own failure mode. Caches serve stale data and can trigger a stampede when a popular key expires and thousands of requests hit the database at once. Read replicas suffer replication lag, so a user who has just saved something may not see it if the follow-up read goes to a replica; the usual fix is to route reads that follow a write back to the primary for a short time. Queues such as RabbitMQ or Amazon SQS move emails, image processing and reports out of the request path, but need monitoring, because a growing backlog is often the first visible sign of trouble.

1 million users: data size and write load

Application servers scale horizontally with little effort. Databases do not, because every write has to land on the primary and stay consistent. The article’s advice is to shrink the problem before splitting it. In PostgreSQL, declarative partitioning, available since version 10, allows large tables to be split by time so that old partitions can be detached, archived to cheaper storage or dropped without heavy delete operations. Unused indexes should go too, since every index slows down writes.

Write volume can also be reduced by moving analytics events, audit logs and counters to stores designed for append-only data, and by batching what remains. This is also the stage for autoscaling and distributed tracing, so that slow requests can be followed across services rather than guessed at.

10 and 100 million users: teams, regions and physics

At 10 million daily users, the article argues, the main problem becomes organisational: letting dozens of engineers change the system without breaking each other’s work. Microservices make sense here when they map to business domains or teams that need independent deployments, not before. Event-driven communication needs the transactional outbox pattern, which writes the database change and the event in the same transaction and publishes the event afterwards, so that an update cannot succeed while its event is lost. Rate limiting, backpressure, timeouts with exponential backoff and circuit breakers stop one slow service from dragging others down, and on-call rotations, runbooks, service-level objectives and blameless postmortems become part of the architecture.

At 100 million users, distance and cost dominate. Multi-region deployments bring users closer to the servers and survive the loss of a region, but only if failover is tested regularly. Sharding finally becomes necessary, and the choice of shard key decides whether load spreads evenly or piles onto a few hot shards. Cell-based architectures, which AWS documents in its Well-Architected guidance, split the platform into independent copies so that a failure affects only a fraction of users.

The article’s closing advice is the most useful part for anyone running infrastructure: build the simplest system that can handle the next tenfold growth, know what will change after that, and do not copy the architecture of a company a thousand times larger.

Frequently asked questions

How many users can a single server handle?

It depends on the workload, but around 10,000 daily active users, roughly 10 requests per second at peak, is usually comfortable for one well-configured server with an indexed database.

When should you add a connection pooler like PgBouncer?

As soon as several app instances share a PostgreSQL database, because each connection is a separate process and the default limit is 100 connections.

When is sharding necessary?

Only after query optimisation, caching, read replicas, partitioning and archiving are no longer enough, typically at very large scale.

Should a new project start with microservices?

Usually not. A well-structured monolith is simpler to run; services make sense when teams, deployments or scaling needs really diverge.

Sources:

Scroll to Top