Skip to content
All essays
ArchitectureFebruary 26, 202512 min

System Design: Scalability and Performance

Master system design principles. Learn scalability, caching, load balancing, and more.

Ü
Ümit Uz
Mobile & Full Stack Developer

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 tolerance

Load Balancing

                    +-----------------+
                    |   Load Balancer  |
                    +--------+--------+
                             |
            +----------------+----------------+
            |                |                |
    +-------+-------+ +-------+-------+ +-----+-------+
    |   Server 1    | |   Server 2    | |   Server 3  |
    +---------------+ +---------------+ +-------------+

Algorithms:
- Round Robin
- Least Connections
- IP Hash
- Least Response Time

Caching 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 locations

Application 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 unavailable

Microservices

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

  1. 1Functional requirements: What does it do?
  2. 2Non-functional requirements: Scale, latency, consistency
  3. 3Capacity estimation: QPS, data size, storage
  4. 4Data model: SQL vs NoSQL
  5. 5API design: REST, GraphQL, gRPC
  6. 6Caching: Where and how to cache
  7. 7Load balancing: How to distribute traffic
  8. 8Database scaling: Replication, sharding
  9. 9Message queue: Async processing
  10. 10Monitoring: Metrics, alerts, logs

Design for scale!

Related essays

Next essay
Vue.js 3 Guide: Composition API and Nuxt