Week 14

Architecture Patterns

Communication Patterns

Background Processing

Architectural Trade-offs

Messaging

Hands-on: RabbitMQ

Practice

Assignment

Backend Track

Introduction

Before a system grows into more instances or more services, one property decides whether it can grow at all: where its state lives. State is everything the application remembers โ€” database rows, session tokens, uploaded files. This chapter explains stateless services, and why you have quietly been building one all along.

Stateless services

A service is stateless when the running process itself holds nothing that cannot be lost. All the state lives in shared places outside the process.

The test is simple: kill the container and start a fresh one. Did any user lose anything? If not, the service is stateless.

Look at the choices you have already made in this track:

<aside> ๐Ÿ’ก

This is why your Week 11 and 12 deployments could simply replace the running container with a new one: nobody was logged out and no file was lost, because the container itself held nothing worth keeping.

</aside>

Horizontal scaling

When one instance is not enough, there are two directions to grow. Vertical scaling means a bigger machine โ€” easy, but it has a ceiling and gets expensive. Horizontal scaling means running more identical copies (instances) of the same service behind one address, with a load balancer spreading the requests across them (you met load balancers briefly in the Core Program's cloud introduction).

flowchart LR
    Browser ==> LB["Load balancer<br/>one address"]
    LB ==> A["instance 1"]
    LB ==> B["instance 2"]
    A ==> DB[(PostgreSQL)]
    B ==> DB
    A ==> S3[["Object storage"]]
    B ==> S3
    style LB fill:#e8f3ff,stroke:#2f80ed,color:#123
    style A fill:#eaf8ed,stroke:#2f9e44,color:#123
    style B fill:#eaf8ed,stroke:#2f9e44,color:#123

This only works because any instance can serve any request โ€” no instance "owns" a user. In other words: horizontal scaling requires stateless services.

Session affinity โ€” the trap

Imagine sessions were kept in each instance's memory instead of the database. A user logs in on instance 1; their next request lands on instance 2 โ€” which has never heard of them. "Randomly logged out" bugs everywhere.

The workaround is session affinity (also called sticky sessions): the load balancer pins each user to one instance. It works, but it is fragile โ€” if that instance dies, all its users are logged out, and load spreads unevenly. The better fix is the one you already use: keep sessions in shared storage.

๐Ÿ’ฌ A developer stores logged-in users in a HashMap inside the service. It works perfectly on their laptop. What breaks when the service runs as two instances?

Where state lives

Kind of state Where it belongs Why
Business data (users, orders) Relational database Durable, consistent, transactional
Files (images, PDFs) Object storage Cheap, scalable, served by URL
Sessions and other short-lived data Database table or a shared cache Must be visible to every instance

<aside> ๐Ÿ’ก

A cache is a copy of data kept close by for speed โ€” usually an in-memory key-value store such as Redis, which you met in Week 9. Caches make reads fast; the hard part is invalidation, knowing when the copy has gone stale. Awareness is enough for now: teams add caching when measurements show it is needed, not by default.

</aside>

Summary

A stateless service keeps its state in shared places โ€” database, object storage โ€” which makes every instance disposable. That is what makes deployments safe and horizontal scaling possible, and you have been following the pattern since Weeks 9 and 10 without calling it that. Next up: what happens to work that is waiting to be done โ€” messaging.

Extra resources


The HackYourFuture curriculum is licensed underย CC BY-NC-SA 4.0 *https://hackyourfuture.net/*

CC BY-NC-SA 4.0 Icons

Built with โค๏ธ by the HackYourFuture community ยท Thank you, contributors

Found a mistake or have a suggestion? Let us know in the feedback form.