Scalability vs Performance
Performance: Fast response times
Scalability: Handle growing load
Vertical Scaling (Scale Up):
- Add more resources to single machine
- Expensive hardware
- Single point of failure
Horizontal Scaling (Scale Out):
- Add more machines
- Commodity hardware
- Better fault toleranceLoad Balancing
+-----------------+
| Load Balancer |
+--------+--------+
|
+----------------+----------------+
| | |
+-------+-------+ +-------+-------+ +-----+-------+
| Server 1 | | Server 2 | | Server 3 |
+---------------+ +---------------+ +-------------+
Algorithms:
- Round Robin
- Least Connections
- IP Hash
- Least Response TimeCaching Strategies
Client-Side Caching
javascript
// HTTP Cache headers
app.get('/api/data', (req, res) => {
res.set('Cache-Control', 'public, max-age=3600'); // 1 hour
res.set('ETag', '"abc123"');
if (req.headers['if-none-match'] === '"abc123"') {
return res.status(304).end(); // Not Modified
}
res.json(data);
});CDN Caching
javascript
// Cloudflare, AWS CloudFront
// Cache static assets at edge locationsApplication Caching
javascript
import Redis from 'ioredis';
const redis = new Redis();
async function getUser(id) {
// Check cache
const cached = await redis.get(`user:${id}`);
if (cached) {
return JSON.parse(cached);
}
// Cache miss, fetch from DB
const user = await db.users.findById(id);
// Store in cache
await redis.set(`user:${id}`, JSON.stringify(user), 'EX', 3600);
return user;
}Database Scaling
Read Replicas
+-------+
| Master | (Writes)
+-------+
|
+--------+--------+
| | |
+-------+ +-------+ +-------+
| Replica| | Replica| | Replica| (Reads)
+-------+ +-------+ +-------+Sharding
javascript
// Hash-based sharding
function getShard(key) {
const hash = crypto.createHash('md5').update(key).digest('hex');
const shardId = parseInt(hash.substring(0, 8), 16) % totalShards;
return shardId;
}
// Range-based sharding
function getShard(userId) {
if (userId < 1000000) return 'shard1';
if (userId < 2000000) return 'shard2';
return 'shard3';
}Message Queues
javascript
// Producer
import { Producer } from 'kafkajs';
const producer = new Producer({
brokers: ['localhost:9092']
});
await producer.send({
topic: 'user-events',
messages: [
{ key: 'user123', value: JSON.stringify({ type: 'signup', userId: 'user123' }) }
]
});
// Consumer
import { Consumer } from 'kafkajs';
const consumer = new Consumer({
groupId: 'user-processor',
brokers: ['localhost:9092']
});
await consumer.subscribe({ topic: 'user-events' });
await consumer.run({
eachMessage: async ({ message }) => {
const event = JSON.parse(message.value.toString());
await processEvent(event);
}
});CAP Theorem
In a distributed system, you can have only 2 of:
Consistency: All nodes see same data
Availability: Every request gets response
Partition Tolerance: System works despite network failures
CA: RDBMS (MySQL, PostgreSQL) - Single node
AP: NoSQL (Cassandra, DynamoDB) - Eventually consistent
CP: NoSQL (MongoDB, HBase) - Strong consistency, may be unavailableMicroservices
Monolith:
+-------------------------+
| Application |
| +-------+ +-------+ |
| | Auth | | Users | ...|
| +-------+ +-------+ |
+-------------------------+
Microservices:
+----------+ +----------+ +----------+
| Auth | | Users | | Orders |
| Service | | Service | | Service |
+----------+ +----------+ +----------+
| | |
+------+------+-------------+
|
+-------------------+
| API Gateway |
+-------------------+Rate Limiting
javascript
// Token bucket algorithm
class RateLimiter {
constructor(capacity, refillRate) {
this.capacity = capacity;
this.tokens = capacity;
this.refillRate = refillRate;
}
async consume(userId) {
const key = `rate_limit:${userId}`;
const tokens = await redis.get(key);
if (tokens > 0) {
await redis.decr(key);
return true;
}
return false; // Rate limited
}
}Monitoring
javascript
// Metrics collection
import { Counter, Histogram } from 'prom-client';
const httpRequestDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests',
labelNames: ['method', 'route', 'status']
});
const requestCounter = new Counter({
name: 'http_requests_total',
help: 'Total HTTP requests',
labelNames: ['method', 'route', 'status']
});
// Middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration
.labels(req.method, req.route?.path || req.path, res.statusCode)
.observe(duration);
requestCounter
.labels(req.method, req.route?.path || req.path, res.statusCode)
.inc();
});
next();
});Design Checklist
- 1Functional requirements: What does it do?
- 2Non-functional requirements: Scale, latency, consistency
- 3Capacity estimation: QPS, data size, storage
- 4Data model: SQL vs NoSQL
- 5API design: REST, GraphQL, gRPC
- 6Caching: Where and how to cache
- 7Load balancing: How to distribute traffic
- 8Database scaling: Replication, sharding
- 9Message queue: Async processing
- 10Monitoring: Metrics, alerts, logs
Design for scale!