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:

  1. Team size exceeds 30-50 engineers and merge conflicts / release bottlenecks occur.
  2. Distinct modules have vastly different scaling requirements (e.g., Image Processing vs Profile View).
  3. The domain has clear boundaries (Bounded Contexts in Domain-Driven Design).