Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 88

Designing Stateless Web Applications for Scalability

Recommended resources

Hello! Welcome to the final lesson in our module on CI/CD and Scalability.

In our last two lessons, we've explored how to scale different parts of our application. We started by horizontally scaling the application servers with a load balancer, and then we scaled the database read-workload using read replication. A common theme underpinning both strategies is the need to treat our application servers as interchangeable units.

Today, we will formalize this crucial concept. This lesson addresses the learning outcome: Design a web application to be stateless to facilitate horizontal scaling. We'll break down exactly what "state" means in a web context, understand why storing it in your application is a barrier to scaling, and outline the design principles for building stateless systems.

This principle is one of the most important in modern backend engineering, and it's the glue that holds scalable architectures together.

1. Stateful vs. Stateless: A Conceptual Overview

At its core, the "state" of an application refers to any data about a user's session that is remembered across multiple requests. This could be their login status, the items in their shopping cart, or their progress in a multi-page form. The key question is: where is this information stored?

  • A stateful architecture stores this session data directly in the memory of the application server that is handling the user's requests.
  • A stateless architecture stores this data in an external, shared location (like a database or a cache) that all application servers can access.

To build a clear mental model, let's start with a video that uses a simple restaurant analogy to explain this difference.

Stateful vs Stateless Architectures Explained

The video 'Stateful vs Stateless Architectures Explained' by Hayk Simonyan provides an excellent, high-level introduction to these concepts.

Please watch from the beginning to 01:39. Pay attention to the two restaurant analogies: one where the waiter must remember your order (stateful), and one where the order is written down for any waiter to see (stateless).

As the video illustrates, in a stateful system, the client is tethered to a specific server. If that server goes down, the session state is lost. In a stateless system, any server can process the client's request because the state is stored centrally.

This diagram provides a clear visual summary of the two architectures.

Stateful vs. Stateless Web Application Architectures
This image contrasts stateful and stateless designs. On the left (stateful), each user's session is tied to a specific server. On the right (stateless), any web server can handle any user's request because they all access a shared database for state.

2. Why Statelessness is Essential for Horizontal Scaling

Now, let's connect this concept directly to horizontal scaling. Imagine your application becomes popular and you need to add more servers behind a load balancer, just as we discussed in a previous lesson.

If your application is stateful, the load balancer creates a major problem. A user might log in (request 1), and the load balancer sends them to Server A, which stores their session information in its memory. When the user tries to access their profile (request 2), the load balancer might send them to Server B to balance the load. But Server B has no idea who this user is—their session exists only on Server A. The user would appear to be logged out.

This next video provides a more technical demonstration of this exact problem using a rate-limiting example, which should resonate with your programming background.

Stateful vs Stateless Applications

The video 'Stateful vs Stateless Applications' by Mehul Mohan demonstrates technically why in-memory state breaks horizontal scaling and introduces the solution of externalizing state.

Watch from 00:20 to 05:58. Focus on how the simple in-memory object for rate-limiting fails when a second server is added, and how moving that state to an external service like Redis solves the problem.

As the video explains, the solution is to make the application's code layer stateless by moving the state to an external service that is built to handle it. This is the fundamental design principle that enables horizontal scaling.

This architecture diagram shows what a horizontally scaled, stateless Django application looks like in practice.

Horizontal Scaling Architecture with Django and Traefik
This diagram illustrates the goal. Multiple stateless Django servers can be added or removed ('horizontal scaling'). They are interchangeable because they all rely on external, shared services for state: Postgres for the resource state, Redis for caching/session state, and S3 for file storage.

3. Design Principles for a Stateless Application

Designing for statelessness means ensuring that no client-specific data is saved within the application instance itself. Each request from a client must be independent and contain all the information necessary for the server to process it.

Let's explore the key principles for achieving this, using a more formal article to ground our understanding.

What Is the Stateless Architecture Pattern?

The article 'What Is the Stateless Architecture Pattern?' from Rheinwerk Computing provides precise definitions and examples that are crucial for a deeper understanding.

Read from the beginning down to the heading 'Stateless Despite a Status in the Database?'. This will help clarify the core concepts and motivation.

Based on this, we can distill three core design principles:

Principle 1: Externalize All Application State

The article makes a crucial distinction between:

  • Application State: Data held by the server for a specific client (e.g., a shopping cart, login session).
  • Resource State: Data managed by the server that is independent of any client (e.g., product data, blog posts).

A stateless application does not store application state. The resource state naturally lives in your primary database (e.g., PostgreSQL). The application state must also be moved to a shared store. For a shopping cart, instead of storing it in the Django server's memory, you would store it in a database table, linking the cart items to a user ID or a session ID.

Principle 2: Ensure Every Request is Self-Contained

Since the server remembers nothing between requests, each request must bring all the context it needs. This is the natural behavior of the HTTP protocol itself.

Consider pagination:

  • Stateful approach: The server remembers you are on page 1 of a search result. Your next request is simply GET /nextPage, and the server retrieves page 2 based on your stored session state.
  • Stateless approach: The server remembers nothing. Your first request is GET /search?q=Golang&page=1. To get the next page, your client must explicitly ask for it in a new, self-contained request: GET /search?q=Golang&page=2.

Principle 3: Use Tokens for Stateless Authentication

This is one of the most common applications of stateless design.

  • Stateful authentication (Session-based): A user logs in, the server creates a session ID, stores it in its own memory or a session database, and sends the ID back to the client as a cookie. On each subsequent request, the server uses the ID to look up the user's session data. This lookup is a stateful operation.
  • Stateless authentication (Token-based): A user logs in, and the server generates a token (like a JSON Web Token or JWT) that contains the user's identity information. This token is sent to the client. On each subsequent request, the client includes the token in the Authorization header. The server can validate the token's signature and extract the user's identity without needing to look anything up. Any server in the cluster can perform this validation.

You've already worked with JWTs in Module 5. This context shows why they are so powerful for scalable systems. As the article "JWT Authentication in Django" (resource LINK) states, the key benefit is that "the server doesn't need to query the database every time to retrieve the user associated with a given token." This decouples authentication from a single server or even a single database, making it truly stateless.

Test your understanding!

Imagine you are building a multi-step user registration wizard (e.g., Step 1: personal details, Step 2: address, Step 3: payment info). The user should be able to close their browser and resume where they left off.

How would you design this feature in a stateless way? Where would you store the partially completed form data between steps?

Show answer

A stateless design would avoid storing the in-progress form data in the web server's memory. Instead, after each step, the data submitted by the user would be saved to a shared, persistent store, like your PostgreSQL database.

You could have a RegistrationProgress table with columns for each field in the wizard, linked to the User model. When the user completes Step 1, you would create or update their row in this table. When they return, the view would first load any existing data from that table before rendering the form.

This way, it doesn't matter which server handles their request for Step 2; that server can fetch the data from Step 1 from the shared database and continue the process seamlessly.


4. Trade-offs and Workarounds

Stateless architecture is the industry standard for scalable applications, but it's not without trade-offs.

  1. Performance: Accessing state from an external network service (like Redis or Postgres) will always be slower than reading it from the local server's RAM. This is the performance price you pay for scalability and resilience.
  2. Complexity for Legacy Systems: Sometimes you must work with stateful legacy systems. In these cases, a workaround called sticky sessions (or session affinity) can be used. This is a feature of a load balancer that ensures all requests from a specific user are always routed to the same server. While this allows a stateful app to function behind a load balancer, it's considered an anti-pattern because it negates many benefits of horizontal scaling: if that one server goes down, the user's session is still lost.

Conclusion

You have now learned one of the most critical design principles for building modern, cloud-native applications. Designing for statelessness is a prerequisite for achieving true horizontal scalability and building resilient, fault-tolerant systems.

Key Takeaways:

  • A stateless architecture keeps client-specific session data out of the application server's memory.
  • State is externalized to shared services like databases (PostgreSQL) or caches (Redis).
  • Statelessness is what allows you to treat your application servers as interchangeable units, making horizontal scaling simple and effective.
  • Key design patterns include making every request self-contained and using token-based authentication (like JWTs) instead of server-side sessions.
  • The primary trade-off is a slight increase in latency for state access in exchange for massive gains in scalability and resilience.

Preview of the Next Lesson:
Our discussion on scalability naturally leads to a higher-level architectural question. If you keep breaking functions out of your main application—first the database, then the cache, then the file storage—at what point does the application itself need to be broken apart? In the final lesson of the course, we will contrast monolithic and microservices architectures, exploring the trade-offs of building one large, unified application versus a suite of smaller, independent services. Your understanding of statelessness will be a critical foundation for that discussion.