Client-Server Architecture & Scaling
The client-server model is the structural backbone of modern web applications. Clients send requests over a network, and servers process them, apply business rules, and return data or HTML.
flowchart LR
subgraph Clients
B["🌐 Browser"]
M["📱 Mobile App"]
D["🖥️ Desktop Client"]
end
subgraph Server
API["⚙️ API Server"]
end
subgraph Data
DB[("🗄️ Database")]
Cache["⚡ Cache (Redis)"]
end
B -- "HTTP / WebSocket" --> API
M -- "HTTP / WebSocket" --> API
D -- "HTTP / WebSocket" --> API
API -- "JSON / HTML" --> B
API -- "JSON / HTML" --> M
API -- "JSON / HTML" --> D
API --> DB
API --> Cache
1. Overview & Data Flow
sequenceDiagram
participant C as 🖥️ Client (Browser / Mobile)
participant S as ⚙️ Server (API)
participant D as 🗄️ Database
C->>S: HTTP Request (GET /api/resource)
S->>D: Query Data
D-->>S: Result Set
S-->>C: JSON / HTML Response
C->>S: POST /api/resource (Create)
S->>D: Insert / Update
D-->>S: Acknowledgement
S-->>C: 201 Created
- Client Layer: User interface running on mobile, browser, or third-party client.
- Server Layer: Business logic execution, authentication, validation, orchestration.
- Data Layer: Persistent databases, object storage, and in-memory caches.
2. Stateless vs Stateful Architecture
Stateless Architecture
flowchart LR
C["🖥️ Client"] -- "Request 1" --> S1["⚙️ App Server 1"]
C -- "Request 2" --> S2["⚙️ App Server 2"]
C -- "Request 3" --> S3["⚙️ App Server 3"]
S1 --> Store[("🗄️ Shared Store\n(Redis / DB)")]
S2 --> Store
S3 --> Store
Any server can handle any request — state is externalized.
Stateful Architecture
flowchart LR
C["🖥️ Client"] -- "Request 1" --> S1["⚙️ App Server 1\n(Holds session in memory)"]
C -. "Request 2\n(MUST go to same server)" .-> S1
C -. "Request 3\n(MUST go to same server)" .-> S1
S2["⚙️ App Server 2"] -. "❌ Cannot serve\nthis client" .-> C
Requires sticky sessions — if the server crashes, the session is lost.
| Aspect | Stateless Architecture | Stateful Architecture |
|---|---|---|
| State Storage | No client context stored on server memory; stored in DB/JWT/Redis | Server maintains session state in local memory |
| Scalability | Extremely easy — any server can handle any request | Difficult — requires sticky sessions / session replication |
| Failure Recovery | Trivial — failed nodes can be replaced without session loss | Hard — if server crashes, active sessions are lost |
| Load Balancing | Simple round-robin or least-connections | Complex IP hash / session pinning required |
| Typical Use Cases | REST APIs, microservices, cloud-native apps | Multiplayer game servers, persistent raw socket connections |
Best Practice: Keep app servers completely stateless and externalize state to Redis or databases.
3. Vertical vs Horizontal Scaling
flowchart TB
subgraph Vertical["⬆️ Vertical Scaling (Scale Up)"]
direction TB
V1["🖥️ Small Server\n2 CPU / 4GB RAM"]
V2["🖥️ Medium Server\n8 CPU / 32GB RAM"]
V3["🖥️ Large Server\n64 CPU / 256GB RAM"]
V1 --> V2 --> V3
end
subgraph Horizontal["➡️ Horizontal Scaling (Scale Out)"]
direction TB
LB["⚖️ Load Balancer"]
H1["🖥️ Server 1"]
H2["🖥️ Server 2"]
H3["🖥️ Server 3"]
H4["🖥️ Server N..."]
LB --> H1
LB --> H2
LB --> H3
LB --> H4
end
| Aspect | Vertical Scaling (Scale Up) | Horizontal Scaling (Scale Out) |
|---|---|---|
| Mechanism | Add more CPU, RAM, or faster NVMe disks to 1 server | Add more server instances behind a load balancer |
| Cost Curve | Becomes exponentially expensive at high tier | Linear, cost-effective commodity instances |
| Hard Limit | Bounded by maximum hardware motherboard limits | Practically unlimited |
| High Availability | Single Point of Failure (SPOF) remains | Fault-tolerant (individual node loss doesn't bring system down) |
| Complexity | Simple, no code changes required | Requires distributed system design, load balancing, statelessness |
| Downtime | Hardware upgrades usually require downtime | Zero downtime rolling deployments |
4. Monolith vs Microservices
flowchart TB
subgraph Monolith["🏢 Monolithic Architecture"]
direction TB
MA["Single Deployment Artifact"]
MA --- UI["UI Layer"]
MA --- BL["Business Logic"]
MA --- DAL["Data Access Layer"]
MA --- AUTH["Auth Module"]
MA --- PAY["Payment Module"]
MA --- NOTIFY["Notification Module"]
end
subgraph Micro["🧩 Microservices Architecture"]
direction TB
GW["🚪 API Gateway"] --> US["👤 User Service"]
GW --> PS["💳 Payment Service"]
GW --> NS["🔔 Notification Service"]
GW --> IS["🖼️ Image Service"]
US --> DB1[("DB 1")]
PS --> DB2[("DB 2")]
NS --> MQ["📨 Message Queue"]
IS --> S3["☁️ Object Storage"]
end
| Aspect | Monolithic Architecture | Microservices Architecture |
|---|---|---|
| Structure | Single unified codebase and deployment artifact | Suite of independently deployable, modular services |
| Deployment | All-or-nothing deployments | Granular, independent service rollouts |
| Scaling | Must scale entire application proportionally | Independently scale resource-intensive services |
| Tech Stack | Usually single programming language / framework | Polyglot (different languages & databases per service) |
| Inter-Service Latency | Extremely fast in-memory function calls | Network overhead (HTTP, gRPC, RPC, message queues) |
| Complexity & Ops | Low operational overhead | High operational overhead (tracing, service discovery, mesh) |
| Best Fit For | Early startups, MVPs, small teams, well-defined domains | Large engineering orgs, complex domains, massive scale |
When to Migrate from Monolith to Microservices:
- Team size exceeds 30-50 engineers and merge conflicts / release bottlenecks occur.
- Distinct modules have vastly different scaling requirements (e.g., Image Processing vs Profile View).
- The domain has clear boundaries (Bounded Contexts in Domain-Driven Design).