Abhishek Roka
Abhishek Roka

What I Learned About Scaling to One Million Users While Building My Placement Portal


Yesterday, I was reading the first chapter of System Design Interview Volume 1 by Alex Xu.

The chapter walks through how applications can evolve from a single server into systems capable of supporting millions of users.

What interested me most wasn't the list of technologies.

It was the way the architecture changes as the requirements change.

While reading, I started mapping those ideas to my own Placement Portal.

My project is nowhere near the scale discussed in the book.

And that's exactly why the exercise was interesting.

Instead of asking:

"How do I build a system for one million users?"

I started asking:

"What architectural problems will appear if this system actually grows?"

That changed the way I think about several decisions I'm making today.

1. Horizontal Scaling Is Usually More Flexible

One of the first concepts that stood out to me was horizontal scaling.

Instead of continuously upgrading a single machine, a system can add more application instances and distribute traffic between them.

For example:

             Load Balancer
                  │
        ┌─────────┼─────────┐
        ▼         ▼         ▼
    Django     Django     Django
    Instance   Instance   Instance

This allows capacity to be increased by adding additional instances.

But that doesn't mean horizontal scaling is always the correct answer.

For a small application, vertical scaling can be simpler and more economical.

The appropriate strategy depends on things such as:

That's one of the biggest lessons I'm taking away from system design:

Scalability is a trade-off, not a checkbox.

2. Stateless Web Applications Make Scaling Easier

Another concept that immediately reminded me of my Placement Portal was the idea of a stateless web tier.

If multiple application instances are serving requests, each instance should ideally be capable of handling any request.

That becomes much harder if important user state is tied to one particular application server.

A stateless architecture makes the system easier to scale because requests don't have to keep returning to the same server.

This also connected with the JWT-based authentication approach I implemented in my Placement Portal.

The important lesson for me wasn't simply:

"Use JWT."

It was understanding why stateless application servers make horizontal scaling easier.

3. Databases Can Become the Bottleneck

Adding more application servers doesn't automatically solve every scalability problem.

Eventually, the database can become a bottleneck.

One common strategy for read-heavy applications is database replication.

A simplified architecture might look like:

             Application
                  │
          ┌───────┴───────┐
          ▼               ▼
       Primary        Read Replica
       Database       Database

The primary database handles writes while read replicas can handle some read workloads.

This can improve scalability and availability when the workload justifies it.

My Placement Portal doesn't need this architecture today.

But understanding it changes how I think about database design.

The important question isn't:

"Can PostgreSQL handle my application?"

It's:

"What happens to my database when the application's workload changes?"

4. Background Tasks Shouldn't Block Requests

This was probably the easiest concept for me to relate to because I've already encountered the problem in my own project.

Some operations simply don't belong inside the request-response cycle.

For example:

Instead of forcing the user to wait for those operations, they can be delegated to background workers.

That's why I've introduced Celery and RabbitMQ into my Placement Portal architecture.

The basic flow becomes:

Django
   │
   ▼
RabbitMQ
   │
   ▼
Celery Worker
   │
   ▼
Background Task

Reading about message brokers in system design gave me another perspective on the architecture I was already building.

I wasn't simply adding Celery because it was a popular Django tool.

I was learning the architectural reason for separating task creation from task execution.

5. Caching Can Reduce Expensive Work

Another concept that stood out to me was caching.

If the same data is requested repeatedly, it doesn't always make sense to perform the same expensive operation every time.

A cache can store frequently accessed information and reduce pressure on the underlying systems.

In my Placement Portal, possible candidates for caching could include:

I have also been considering Redis as part of the project's future infrastructure.

But again, there's an important distinction:

Understanding caching doesn't mean everything should be cached.

Caching introduces its own problems around:

So the lesson isn't simply "cache everything."

It's:

Cache the things where the performance benefit justifies the additional complexity.

6. Multiple Data Centers Change the Failure Model

One of the more interesting concepts I encountered was deploying infrastructure across multiple data centers.

At first, it sounds like pure performance optimization.

But the bigger benefit is resilience.

If an entire infrastructure location becomes unavailable, having another location can help keep the system operational or support recovery.

This introduces concepts such as:

Obviously, deploying a multi-data-center architecture for my current Placement Portal would be excessive.

But understanding the concept helps me think beyond:

"What happens when the server crashes?"

and toward:

"What happens when an entire infrastructure environment becomes unavailable?"

That's a very different level of system thinking.

7. Database Sharding Is a Different Level of Scale

Another concept discussed in large-scale system design is database sharding.

Instead of putting all data into one database, data can be distributed across multiple database instances.

For extremely large systems, this can help distribute:

But sharding also introduces significant complexity.

Applications need to understand how data is distributed.

Queries can become more complicated.

Transactions and consistency can become harder to manage.

My Placement Portal obviously doesn't need sharding today.

But learning about it helped me understand something important:

Scaling isn't free.

Every scaling strategy solves certain problems while introducing new ones.

What Would My Placement Portal Look Like at Scale?

This was the most interesting exercise I did while reading.

If I had to design a cloud architecture for the Placement Portal with significantly larger traffic, I might start thinking in terms of multiple environments:

A production architecture could potentially contain:

                    Load Balancer
                         │
              ┌──────────┴──────────┐
              ▼                     ▼
        Django Instance       Django Instance
              │                     │
              └──────────┬──────────┘
                         │
              ┌──────────┴──────────┐
              ▼                     ▼
         Primary DB            Read Replicas
              │
              │
        ┌─────┴─────┐
        ▼           ▼
      Redis      RabbitMQ
                    │
                    ▼
              Celery Workers

I could then add supporting infrastructure for the frontend, monitoring, scheduled tasks, and other workloads as the requirements justified them.

A possible starting configuration might include:

But I want to emphasize something:

This is a thought exercise, not my current production architecture.

My Placement Portal is still being developed locally.

The value of this exercise isn't predicting the exact number of servers I'll eventually need.

It's understanding what kinds of architectural problems will appear as the system grows.

The Biggest Constraint Isn't Technology

This may actually be the most important lesson.

When looking at large architecture diagrams, it's easy to get excited about adding:

But infrastructure costs money.

And infrastructure introduces operational complexity.

So the real question isn't:

"What is the most scalable architecture I can design?"

It's:

"What is the simplest architecture that satisfies the current requirements while leaving a reasonable path for growth?"

That's a much harder question.

And it's also much closer to real engineering.

Don't Design for One Million Users on Day One

If I try to build my Placement Portal for one million users before I have even acquired my first real users, I would probably spend more time solving imaginary problems than real ones.

Today, I don't need database sharding.

I don't need multiple data centers.

I don't need dozens of application instances.

I don't need an enormous distributed architecture.

What I do need is an architecture that doesn't make future growth unnecessarily painful.

That's a much more practical goal.

Build for today's requirements.

Understand tomorrow's problems.

Avoid decisions that unnecessarily prevent tomorrow's architecture.

Final Thoughts

Reading about systems designed for millions of users taught me something I didn't expect.

System design isn't really about one million users.

It's about understanding how a system evolves.

A small application might start with:

Application
    │
    ▼
Database

Then requirements appear.

Background jobs are introduced.

Caching becomes useful.

Read traffic increases.

Workers need to scale.

Databases need replication.

Eventually, reliability requirements become more demanding.

The architecture evolves with them.

That's the mindset I'm trying to develop while building my Placement Portal.

I don't want to build a million-user architecture today.

I want to understand what a million-user architecture looks like so that I can recognize the problems when they eventually become real.

System design isn't about preparing for success by over-engineering everything.

It's about making sure success doesn't become the reason your system fails.

— Abhishek Roka