Skip to content
All essays
ArchitectureMarch 22, 202520 min

Scalability Patterns: Horizontal vs Vertical Scaling in 2025

Master scalability patterns. Learn horizontal vs vertical scaling, caching strategies, database scaling, and real-world examples.

Ü
Ümit Uz
Mobile & Full Stack Developer

Understanding Scalability

Scalability is the ability of a system to handle growing amounts of work by adding resources to the system. In 2025, with cloud-native technologies and modern infrastructure, choosing the right scalability pattern is crucial for cost-effective performance.

Scalability Spectrum:

Vertical Scaling (Scale Up):
Single Server → More Powerful Server
2 cores → 8 cores → 32 cores → 128 cores
8 GB RAM → 32 GB RAM → 128 GB RAM → 512 GB RAM

Horizontal Scaling (Scale Out):
Multiple Servers → More Servers
1 server → 3 servers → 10 servers → 100 servers

+------------+     +------------+     +------------+
|  Server 1  |     |  Server 2  |     |  Server 3  |
|  (small)   |     |  (small)   |     |  (small)   |
+------------+     +------------+     +------------+
      |                  |                  |
      +------------------+------------------+
                            |
                     Load Balancer
                            |
                         Client

Vertical Scaling Deep Dive

When to Use Vertical Scaling

Vertical Scaling Best Use Cases:
1. Simple applications without distribution complexity
2. Monolithic databases (initial stages)
3. Applications with high single-threaded performance needs
4. Memory-intensive workloads (in-memory databases)
5. Development and testing environments

Advantages:
- Simple to implement
- No code changes required
- Lower operational complexity
- No distributed system challenges
- Consistent performance

Disadvantages:
- Upper limit on hardware
- Single point of failure
- Expensive at scale
- Vendor lock-in
- Downtime during upgrades

Real-World Example: High-Performance Database
class VerticalScalingStrategy {
  // Analyze if vertical scaling is appropriate
  async shouldScaleVertically(metrics: SystemMetrics): Promise<boolean> {
    // CPU is not the bottleneck
    if (metrics.cpuUsage < 70) {
      // Memory is the constraint
      if (metrics.memoryUsage > 90) {
        // Database benefits from more memory
        if (this.isDatabase()) {
          return true;
        }

        // Application can use larger cache
        if (this.isCacheIntensive()) {
          return true;
        }
      }
    }

    // Single-threaded workload
    if (metrics.singleThreaded && metrics.cpuUsage > 90) {
      // Better CPU will help
      return true;
    }

    return false;
  }

  // Calculate optimal instance size
  calculateOptimalSize(metrics: SystemMetrics): InstanceSpec {
    // Memory sizing
    const currentMemory = metrics.totalMemory;
    const memoryUtilization = metrics.memoryUsage / 100;

    // Target 70% utilization
    const targetMemory = metrics.usedMemory / 0.7;

    // Round up to available instance sizes
    const memorySizes = [8, 16, 32, 64, 128, 256, 512];  // GB
    const optimalMemory = memorySizes.find(
      size => size >= targetMemory
    ) || memorySizes[memorySizes.length - 1];

    // CPU sizing
    const cpuCores = this.calculateCPUCores(metrics, optimalMemory);

    return {
      memory: optimalMemory,
      cpuCores,
      storage: this.calculateStorage(metrics)
    };
  }

  private calculateCPUCores(metrics: SystemMetrics, memory: number): number {
    // Ratio based on instance type
    const cpuMemoryRatio = {
      'general-purpose': 4,  // 1 CPU per 4GB RAM
      'compute-optimized': 2,  // 1 CPU per 2GB RAM
      'memory-optimized': 8   // 1 CPU per 8GB RAM
    };

    const workloadType = this.analyzeWorkloadType(metrics);
    const ratio = cpuMemoryRatio[workloadType];

    return Math.ceil(memory / ratio);
  }
}

Vertical Scaling Implementation

typescript
class AWSVerticalScaler {
  async scaleDatabase(
    instanceId: string,
    targetInstanceType: string
  ): Promise<void> {
    const rds = new AWS.RDS();

    // Get current instance
    const currentInstance = await rds.describeDBInstances({
      DBInstanceIdentifier: instanceId
    }).promise();

    const currentType = currentInstance.DBInstances[0].DBInstanceClass;

    console.log(`Scaling ${instanceId} from ${currentType} to ${targetInstanceType}`);

    // Modify instance
    await rds.modifyDBInstance({
      DBInstanceIdentifier: instanceId,
      DBInstanceClass: targetInstanceType,
      ApplyImmediately: true,
      AllocatedStorage: this.calculateNewStorage(currentInstance, targetInstanceType),
      Iops: this.calculateNewIOPS(currentInstance, targetInstanceType)
    }).promise();

    // Wait for modification to complete
    await this.waitForModification(instanceId);

    console.log('Scaling complete');
  }

  private calculateNewStorage(
    instance: any,
    instanceType: string
  ): number {
    // Storage scales with instance type
    const storageRatios: Record<string, number> = {
      'db.t3.micro': 20,
      'db.t3.small': 30,
      'db.t3.medium': 50,
      'db.m5.large': 100,
      'db.m5.xlarge': 200,
      'db.m5.2xlarge': 400,
      'db.m5.4xlarge': 800,
      'db.r5.large': 150,
      'db.r5.xlarge': 300,
      'db.r5.2xlarge': 600,
      'db.r5.4xlarge': 1200
    };

    const ratio = storageRatios[instanceType] || 100;
    const currentStorage = instance.AllocatedStorage;

    return Math.max(currentStorage, ratio);
  }

  private calculateNewIOPS(
    instance: {
      AllocatedStorage: number;
      StorageType: string;
    },
    instanceType: string
  ): number {
    // IOPS scale with storage for io1
    if (instance.StorageType === 'io1') {
      // 50 IOPS per GB, max 64,000
      return Math.min(instance.AllocatedStorage * 50, 64000);
    }

    // General Purpose SSD (gp3) gets baseline IOPS
    return 3000;  // Baseline for gp3
  }

  private async waitForModification(instanceId: string): Promise<void> {
    const rds = new AWS.RDS();

    return new Promise((resolve, reject) => {
      const checkStatus = async () => {
        const response = await rds.describeDBInstances({
          DBInstanceIdentifier: instanceId
        }).promise();

        const instance = response.DBInstances[0];
        const status = instance.DBInstanceStatus;

        if (status === 'available') {
          resolve();
        } else if (status === 'modifying') {
          setTimeout(checkStatus, 30000);  // Check every 30 seconds
        } else {
          reject(new Error(`Unexpected status: ${status}`));
        }
      };

      checkStatus();
    });
  }
}

Horizontal Scaling Deep Dive

When to Use Horizontal Scaling

Horizontal Scaling Best Use Cases:
1. Stateless applications (web servers, APIs)
2. High availability requirements
3. Geographic distribution
4. Variable/ unpredictable workloads
5. Cost-effective scaling at scale

Advantages:
- No upper limit
- Better fault tolerance
- Cost-effective at scale
- Geographic distribution
- Flexible scaling (up and down)

Disadvantages:
- Increased complexity
- Requires stateless design
- Network overhead
- Consistency challenges
- Load balancing required

Real-World Example: Web Application
class HorizontalScalingStrategy {
  async shouldScaleHorizontally(metrics: SystemMetrics): Promise<boolean> {
    // Stateful workloads not suitable
    if (this.isStateful()) {
      return false;
    }

    // CPU scaling
    if (metrics.cpuUsage > 70) {
      return true;
    }

    // Request queue increasing
    if (metrics.requestQueueDepth > 100) {
      return true;
    }

    // Response time degrading
    if (metrics.averageResponseTime > 1000) {  // 1 second
      return true;
    }

    return false;
  }

  // Calculate optimal instance count
  async calculateInstanceCount(
    metrics: SystemMetrics,
    targetUtilization: number = 70
  ): Promise<number> {
    const currentInstances = metrics.instanceCount;
    const currentLoad = metrics.cpuUsage;

    // Target utilization percentage
    const targetLoad = targetUtilization;

    // Calculate required instances
    const requiredInstances = Math.ceil(
      (currentInstances * currentLoad) / targetLoad
    );

    // Add buffer for spikes
    const buffer = Math.ceil(requiredInstances * 0.2);  // 20% buffer
    const totalInstances = requiredInstances + buffer;

    // Respect min/max limits
    return Math.min(
      Math.max(totalInstances, this.minInstances),
      this.maxInstances
    );
  }

  // Auto-scaling implementation
  async autoScale(metrics: SystemMetrics): Promise<void> {
    const currentCount = metrics.instanceCount;
    const desiredCount = await this.calculateInstanceCount(metrics);

    if (desiredCount !== currentCount) {
      const action = desiredCount > currentCount ? 'Scaling out' : 'Scaling in';

      console.log(`${action} from ${currentCount} to ${desiredCount} instances`);

      await this.updateInstanceCount(desiredCount);

      // Wait for instances to be healthy
      if (desiredCount > currentCount) {
        await this.waitForInstancesHealthy(desiredCount);
      }
    }
  }
}

Auto-Scaling Implementation

typescript
import { AutoScaling, CloudWatch } from '@aws-sdk/client-auto-scaling';

class AWSAutoScaler {
  private autoscaling: AutoScaling;
  private cloudwatch: CloudWatch;

  constructor() {
    this.autoscaling = new AutoScaling({ region: 'us-east-1' });
    this.cloudwatch = new CloudWatch({ region: 'us-east-1' });
  }

  async setupAutoScaling(
    groupName: string,
    config: ScalingPolicy
  ): Promise<void> {
    // Create scaling policies
    await this.createScaleOutPolicy(groupName, config);
    await this.createScaleInPolicy(groupName, config);

    // Create CloudWatch alarms
    await this.createHighCPUAlarm(groupName, config);
    await this.createLowCPUAlarm(groupName, config);
    await this.createHighMemoryAlarm(groupName, config);

    console.log(`Auto-scaling configured for ${groupName}`);
  }

  private async createScaleOutPolicy(
    groupName: string,
    config: ScalingPolicy
  ): Promise<void> {
    await this.autoscaling.putScalingPolicy({
      AutoScalingGroupName: groupName,
      PolicyName: `${groupName}-scale-out`,
      PolicyType: 'TargetTrackingScaling',
      TargetTrackingConfiguration: {
        PredefinedMetricSpecification: {
          PredefinedMetricType: 'ASGAverageCPUUtilization'
        },
        TargetValue: config.targetCPUUtilization,
        ScaleOutCooldown: config.scaleOutCooldown,
        ScaleInCooldown: config.scaleInCooldown
      }
    }).promise();
  }

  private async createScaleInPolicy(
    groupName: string,
    config: ScalingPolicy
  ): Promise<void> {
    await this.autoscaling.putScalingPolicy({
      AutoScalingGroupName: groupName,
      PolicyName: `${groupName}-scale-in`,
      PolicyType: 'SimpleScaling',
      AdjustmentType: 'ChangeInCapacity',
      ScalingAdjustment: -1,
      Cooldown: config.scaleInCooldown
    }).promise();
  }

  private async createHighCPUAlarm(
    groupName: string,
    config: ScalingPolicy
  ): Promise<void> {
    await this.cloudwatch.putMetricAlarm({
      AlarmName: `${groupName}-high-cpu`,
      AlarmDescription: 'Alert on CPU > 80%',
      Namespace: 'AWS/EC2',
      MetricName: 'CPUUtilization',
      Dimensions: [{
        Name: 'AutoScalingGroupName',
        Value: groupName
      }],
      Statistic: 'Average',
      Period: 300,  // 5 minutes
      EvaluationPeriods: 2,
      Threshold: 80,
      ComparisonOperator: 'GreaterThanThreshold'
    }).promise();
  }

  private async createLowCPUAlarm(
    groupName: string,
    config: ScalingPolicy
  ): Promise<void> {
    await this.cloudwatch.putMetricAlarm({
      AlarmName: `${groupName}-low-cpu`,
      AlarmDescription: 'Alert on CPU < 30%',
      Namespace: 'AWS/EC2',
      MetricName: 'CPUUtilization',
      Dimensions: [{
        Name: 'AutoScalingGroupName',
        Value: groupName
      }],
      Statistic: 'Average',
      Period: 300,
      EvaluationPeriods: 4,  // 20 minutes
      Threshold: 30,
      ComparisonOperator: 'LessThanThreshold'
    }).promise();
  }

  async manualScale(groupName: string, desiredCount: number): Promise<void> {
    await this.autoscaling.setDesiredCapacity({
      AutoScalingGroupName: groupName,
      DesiredCapacity: desiredCount,
      HonorCooldown: false
    }).promise();

    console.log(`Scaled ${groupName} to ${desiredCount} instances`);
  }

  async scheduleScaling(
    groupName: string,
    schedules: ScheduledScaling[]
  ): Promise<void> {
    for (const schedule of schedules) {
      await this.autoscaling.putScheduledUpdateGroupAction({
        AutoScalingGroupName: groupName,
        ScheduledActionName: schedule.name,
        StartTime: schedule.startTime,
        EndTime: schedule.endTime,
        Recurrence: schedule.recurrence,
        MinSize: schedule.minSize,
        MaxSize: schedule.maxSize,
        DesiredCapacity: schedule.desiredCapacity
      }).promise();
    }

    console.log(`Scheduled scaling configured for ${groupName}`);
  }
}

Load Balancing Strategies

Load Balancer Algorithms

typescript
enum LoadBalancingAlgorithm {
  ROUND_ROBIN,
  LEAST_CONNECTIONS,
  IP_HASH,
  LEAST_RESPONSE_TIME,
  WEIGHTED_ROUND_ROBIN,
  RANDOM
}

class LoadBalancer {
  private servers: Server[];
  private algorithm: LoadBalancingAlgorithm;
  private connections: Map<string, number>;

  constructor(servers: Server[], algorithm: LoadBalancingAlgorithm) {
    this.servers = servers;
    this.algorithm = algorithm;
    this.connections = new Map();
    servers.forEach(s => this.connections.set(s.id, 0));
  }

  selectServer(request: Request): Server {
    switch (this.algorithm) {
      case LoadBalancingAlgorithm.ROUND_ROBIN:
        return this.roundRobin();
      case LoadBalancingAlgorithm.LEAST_CONNECTIONS:
        return this.leastConnections();
      case LoadBalancingAlgorithm.IP_HASH:
        return this.ipHash(request);
      case LoadBalancingAlgorithm.LEAST_RESPONSE_TIME:
        return this.leastResponseTime();
      case LoadBalancingAlgorithm.WEIGHTED_ROUND_ROBIN:
        return this.weightedRoundRobin();
      case LoadBalancingAlgorithm.RANDOM:
        return this.random();
      default:
        return this.roundRobin();
    }
  }

  private roundRobin(): Server {
    const server = this.servers[this.currentRoundRobinIndex];
    this.currentRoundRobinIndex =
      (this.currentRoundRobinIndex + 1) % this.servers.length;
    return server;
  }

  private leastConnections(): Server {
    let minConnections = Infinity;
    let selectedServer: Server = this.servers[0];

    for (const server of this.servers) {
      const connections = this.connections.get(server.id) || 0;

      if (connections < minConnections) {
        minConnections = connections;
        selectedServer = server;
      }
    }

    return selectedServer;
  }

  private ipHash(request: Request): Server {
    const clientIP = request.ip;
    const hash = this.hashCode(clientIP);
    const index = Math.abs(hash) % this.servers.length;
    return this.servers[index];
  }

  private leastResponseTime(): Server {
    let minTime = Infinity;
    let selectedServer: Server = this.servers[0];

    for (const server of this.servers) {
      if (server.averageResponseTime < minTime) {
        minTime = server.averageResponseTime;
        selectedServer = server;
      }
    }

    return selectedServer;
  }

  private weightedRoundRobin(): Server {
    // Weighted round robin based on server capacity
    const totalWeight = this.servers.reduce(
      (sum, s) => sum + s.weight,
      0
    );

    let random = Math.random() * totalWeight;

    for (const server of this.servers) {
      random -= server.weight;

      if (random <= 0) {
        return server;
      }
    }

    return this.servers[this.servers.length - 1];
  }

  private random(): Server {
    const index = Math.floor(Math.random() * this.servers.length);
    return this.servers[index];
  }

  private hashCode(str: string): number {
    let hash = 0;

    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;  // Convert to 32-bit integer
    }

    return hash;
  }

  recordConnection(serverId: string): void {
    const current = this.connections.get(serverId) || 0;
    this.connections.set(serverId, current + 1);
  }

  releaseConnection(serverId: string): void {
    const current = this.connections.get(serverId) || 0;
    this.connections.set(serverId, Math.max(0, current - 1));
  }
}

Session Persistence

typescript
class SessionAwareLoadBalancer extends LoadBalancer {
  private sessions: Map<string, string>;  // sessionID -> serverID
  private stickySessions: boolean;

  constructor(
    servers: Server[],
    algorithm: LoadBalancingAlgorithm,
    stickySessions: boolean = true
  ) {
    super(servers, algorithm);
    this.stickySessions = stickySessions;
    this.sessions = new Map();
  }

  selectServer(request: Request): Server {
    // Check for existing session
    if (this.stickySessions) {
      const sessionId = request.cookies['session_id'];

      if (sessionId && this.sessions.has(sessionId)) {
        const serverId = this.sessions.get(sessionId);
        const server = this.servers.find(s => s.id === serverId);

        // Verify server is healthy
        if (server && server.isHealthy) {
          return server;
        } else {
          // Server unhealthy, remove session mapping
          this.sessions.delete(sessionId);
        }
      }
    }

    // No session or unhealthy server, select new server
    const server = super.selectServer(request);

    // Store session mapping
    if (this.stickySessions && request.cookies['session_id']) {
      this.sessions.set(
        request.cookies['session_id'],
        server.id
      );
    }

    return server;
  }
}

// Redis-based session sharing
class RedisSessionStore {
  private redis: Redis;

  constructor(redis: Redis) {
    this.redis = redis;
  }

  async setSession(
    sessionId: string,
    data: SessionData,
    ttl: number = 3600
  ): Promise<void> {
    await this.redis.setex(
      `session:${sessionId}`,
      ttl,
      JSON.stringify(data)
    );
  }

  async getSession(sessionId: string): Promise<SessionData | null> {
    const data = await this.redis.get(`session:${sessionId}`);

    if (data) {
      return JSON.parse(data);
    }

    return null;
  }

  async deleteSession(sessionId: string): Promise<void> {
    await this.redis.del(`session:${sessionId}`);
  }

  // Session affinity across multiple load balancers
  async getServerForSession(sessionId: string): Promise<string | null> {
    const data = await this.getSession(sessionId);
    return data?.serverId || null;
  }

  async setServerForSession(
    sessionId: string,
    serverId: string
  ): Promise<void> {
    const data = await this.getSession(sessionId) || {};
    data.serverId = serverId;

    await this.setSession(sessionId, data);
  }
}

Database Scaling Patterns

Read Replicas

Read Replicas Architecture:
          Write
            |
    +-------+-------+
    |   Primary    |
    |   Database   |
    +-------+-------+
            |
    +-------+-------+-------+
    |       |       |       |
 Read   Read   Read   Read
    |       |       |       |
+-------+ +-------+ +-------+ +-------+
|Replica| |Replica| |Replica| |Replica|
+-------+ +-------+ +-------+ +-------+

Benefits:
- Offload read traffic
- Geographic distribution
- Improved read performance
- Backup/recovery
typescript
class ReadReplicaManager {
  private primary: DatabaseConnection;
  private replicas: DatabaseConnection[];
  private readWriteRatio: number;

  constructor(
    primary: DatabaseConnection,
    replicas: DatabaseConnection[],
    readWriteRatio: number = 0.8  // 80% reads, 20% writes
  ) {
    this.primary = primary;
    this.replicas = replicas;
    this.readWriteRatio = readWriteRatio;
  }

  async query(sql: string, params?: any[]): Promise<any> {
    const isWrite = this.isWriteQuery(sql);

    if (isWrite) {
      // All writes go to primary
      return await this.primary.query(sql, params);
    } else {
      // Reads go to replicas
      const replica = this.selectReplica();
      return await replica.query(sql, params);
    }
  }

  private selectReplica(): DatabaseConnection {
    // Random replica for load distribution
    const index = Math.floor(
      Math.random() * this.replicas.length
    );
    return this.replicas[index];
  }

  private isWriteQuery(sql: string): boolean {
    const writeKeywords = [
      'INSERT', 'UPDATE', 'DELETE', 'CREATE',
      'ALTER', 'DROP', 'TRUNCATE', 'REPLACE'
    ];

    const upperSQL = sql.trim().toUpperCase();

    return writeKeywords.some(keyword =>
      upperSQL.startsWith(keyword)
    );
  }

  async addReplica(connectionString: string): Promise<void> {
    const replica = new DatabaseConnection(connectionString);
    await replica.connect();

    this.replicas.push(replica);

    console.log('Replica added successfully');
  }

  async removeReplica(replicaId: string): Promise<void> {
    const index = this.replicas.findIndex(
      r => r.id === replicaId
    );

    if (index !== -1) {
      const replica = this.replicas[index];
      await replica.disconnect();

      this.replicas.splice(index, 1);

      console.log(`Replica ${replicaId} removed`);
    }
  }

  async getReplicationLag(): Promise<number> {
    const lags = await Promise.all(
      this.replicas.map(async replica => {
        const result = await replica.query(
          'SHOW SLAVE STATUS'
        );
        return result[0].Seconds_Behind_Master;
      })
    );

    // Return average lag
    const avgLag = lags.reduce((a, b) => a + b, 0) / lags.length;
    return avgLag;
  }
}

Database Sharding

typescript
class DatabaseShardingManager {
  private shards: Map<string, DatabaseConnection>;
  private shardingStrategy: ShardingStrategy;

  constructor(shardingStrategy: ShardingStrategy) {
    this.shards = new Map();
    this.shardingStrategy = shardingStrategy;
  }

  addShard(shardId: string, connection: DatabaseConnection): void {
    this.shards.set(shardId, connection);
  }

  async query(
    sql: string,
    params?: any[],
    shardKey?: string
  ): Promise<any> {
    // Determine target shard
    const shardId = shardKey
      ? this.shardingStrategy.getShard(shardKey)
      : this.shardingStrategy.getDefaultShard();

    const shard = this.shards.get(shardId);

    if (!shard) {
      throw new Error(`Shard ${shardId} not found`);
    }

    return await shard.query(sql, params);
  }

  async broadcastQuery(sql: string, params?: any[]): Promise<any[]> {
    // Send query to all shards
    const results = await Promise.all(
      Array.from(this.shards.values()).map(
        shard => shard.query(sql, params)
      )
    );

    return results.flat();
  }
}

class HashShardingStrategy implements ShardingStrategy {
  private shardCount: number;

  constructor(shardCount: number) {
    this.shardCount = shardCount;
  }

  getShard(key: string): string {
    const hash = this.hashCode(key);
    const shardIndex = hash % this.shardCount;

    return `shard${shardIndex}`;
  }

  getDefaultShard(): string {
    return 'shard0';
  }

  private hashCode(str: string): number {
    let hash = 0;

    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }

    return Math.abs(hash);
  }
}

class RangeShardingStrategy implements ShardingStrategy {
  private ranges: Map<string, Range>;

  constructor() {
    this.ranges = new Map();
  }

  addRange(shardId: string, range: Range): void {
    this.ranges.set(shardId, range);
  }

  getShard(key: string): string {
    const value = this.extractValue(key);

    for (const [shardId, range] of this.ranges) {
      if (value >= range.min && value < range.max) {
        return shardId;
      }
    }

    throw new Error(`No shard found for key: ${key}`);
  }

  getDefaultShard(): string {
    return 'shard0';
  }

  private extractValue(key: string): number {
    // Extract numeric value from key
    const match = key.match(/\d+/);
    return match ? parseInt(match[0]) : 0;
  }
}

interface Range {
  min: number;
  max: number;
}

interface ShardingStrategy {
  getShard(key: string): string;
  getDefaultShard(): string;
}

Caching Strategies

Multi-Level Caching

typescript
class MultiLevelCache {
  private l1: InMemoryCache;     // Local cache (fastest)
  private l2: Redis;             // Distributed cache
  private l3: Database;          // Persistent storage

  constructor(l1: InMemoryCache, l2: Redis, l3: Database) {
    this.l1 = l1;
    this.l2 = l2;
    this.l3 = l3;
  }

  async get(key: string): Promise<any> {
    // L1: Check local cache
    const l1Data = await this.l1.get(key);

    if (l1Data !== null) {
      console.log('L1 cache hit');
      return l1Data;
    }

    // L2: Check Redis
    const l2Data = await this.l2.get(key);

    if (l2Data !== null) {
      console.log('L2 cache hit');

      // Populate L1 cache
      await this.l1.set(key, l2Data, 60);  // 1 minute

      return l2Data;
    }

    // L3: Query database
    console.log('Cache miss, querying database');
    const l3Data = await this.l3.get(key);

    // Populate caches
    await this.l1.set(key, l3Data, 60);
    await this.l2.set(key, l3Data, 3600);  // 1 hour

    return l3Data;
  }

  async set(key: string, value: any): Promise<void> {
    // Write to all levels
    await this.l1.set(key, value, 60);
    await this.l2.set(key, value, 3600);
    await this.l3.set(key, value);
  }

  async invalidate(key: string): Promise<void> {
    // Invalidate at all levels
    await this.l1.delete(key);
    await this.l2.del(key);
    await this.l3.delete(key);
  }

  async invalidatePattern(pattern: string): Promise<void> {
    // Redis pattern deletion
    const keys = await this.l2.keys(pattern);

    if (keys.length > 0) {
      await this.l2.del(...keys);
    }

    // Invalidate L1 cache for affected keys
    await this.l1.clear();

    console.log(`Invalidated ${keys.length} keys matching ${pattern}`);
  }
}

class CacheWarmer {
  private cache: MultiLevelCache;

  constructor(cache: MultiLevelCache) {
    this.cache = cache;
  }

  async warmCache(keys: string[]): Promise<void> {
    console.log(`Warming cache for ${keys.length} keys`);

    // Parallel warming
    await Promise.all(
      keys.map(async key => {
        try {
          await this.cache.get(key);  // Will populate cache
        } catch (error) {
          console.error(`Failed to warm cache for key ${key}:`, error);
        }
      })
    );

    console.log('Cache warming complete');
  }

  // Warm based on access patterns
  async warmPopularKeys(limit: number = 1000): Promise<void> {
    // Get most accessed keys from analytics
    const popularKeys = await this.getPopularKeys(limit);

    await this.warmCache(popularKeys);
  }

  private async getPopularKeys(limit: number): Promise<string[]> {
    // Query analytics or logs for popular keys
    // This is implementation-specific

    return [
      'user:12345',
      'product:67890',
      // ... more keys
    ];
  }
}

Kubernetes Native Scaling

yaml
# Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-api
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Percent
        value: 50
        periodSeconds: 60
      - type: Pods
        value: 2
        periodSeconds: 60
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60

Serverless Auto-Scaling

typescript
class ServerlessScaler {
  // AWS Lambda automatically scales
  // No configuration needed for concurrency

  async processEvent(event: Event): Promise<void> {
    // Lambda handles scaling automatically
    // Up to 1000 concurrent executions per region

    await Promise.all(
      event.records.map(record =>
        this.processRecord(record)
      )
    );
  }

  // Provisioned Concurrency for cold start reduction
  async configureProvisionedConcurrency(
    functionName: string,
    concurrency: number
  ): Promise<void> {
    const lambda = new AWS.Lambda();

    await lambda.putProvisionedConcurrencyConfig({
      FunctionName: functionName,
      ProvisionedConcurrentExecutions: concurrency
    }).promise();

    console.log(
      `Configured ${concurrency} provisioned concurrency for ${functionName}`
    );
  }

  // Scheduled concurrency warming
  async scheduleConcurrencyWarming(
    functionName: string,
    schedule: string
  ): Promise<void> {
    const lambda = new AWS.Lambda();
    const events = new AWS.EventBridge();

    // Create lambda function for warming
    await lambda.putFunctionCode({
      FunctionName: `${functionName}-warmer`,
      Code: {
        ZipFile: this.createWarmerCode(functionName)
      }
    }).promise();

    // Schedule warming
    await events.putRule({
      Name: `${functionName}-warming-schedule`,
      ScheduleExpression: schedule
    }).promise();
  }
}

Scalability Checklist

1. Define Requirements:
   - Expected traffic patterns
   - Growth projections
   - Performance SLAs
   - Budget constraints

2. Choose Scaling Strategy:
   - Vertical for simple workloads
   - Horizontal for stateless apps
   - Hybrid for complex systems

3. Design for Scale:
   - Stateless application design
   - Database scaling strategy
   - Caching at all levels
   - CDN for static content

4. Implement Auto-Scaling:
   - Configure scaling policies
   - Set up monitoring and alerts
   - Define scaling thresholds
   - Test scaling scenarios

5. Monitor and Optimize:
   - Track scaling events
   - Measure cost per request
   - Identify bottlenecks
   - Continuous optimization

6. Plan for Failures:
   - Circuit breakers
   - Retry logic
   - Fallback mechanisms
   - Graceful degradation

7. Test Scaling:
   - Load testing
   - Chaos engineering
   - Performance testing
   - Disaster recovery

Scale wisely for cost-effective performance!

Related essays

Next essay
Serverless Cost Optimization: Reduce Your Cloud Bills