Scaling
Chapter 40: Scaling Strategies
Section titled “Chapter 40: Scaling Strategies”Scaling TypeORM Applications for High Traffic
Section titled “Scaling TypeORM Applications for High Traffic”40.1 Scaling Overview
Section titled “40.1 Scaling Overview”Scaling ensures your application can handle increased load while maintaining performance.
Scaling Dimensions ================================================================================
Vertical Scaling (Scale Up): +------------------+ +------------------+ | Server | --> | Bigger Server | | 4 CPU, 8GB | | 16 CPU, 64GB | +------------------+ +------------------+
Horizontal Scaling (Scale Out): +------------------+ +------------------+ +------------------+ | Server 1 | | Server 2 | | Server 3 | | 4 CPU, 8GB | | 4 CPU, 8GB | | 4 CPU, 8GB | +------------------+ +------------------+ +------------------+ \ | / \ | / +------------------------------------------+ | Load Balancer | +------------------------------------------+
Database Scaling: - Read Replicas - Connection Pooling - Sharding - Caching
================================================================================40.2 Horizontal Scaling
Section titled “40.2 Horizontal Scaling”Stateless Application Design
Section titled “Stateless Application Design”// BAD: In-memory state (doesn't scale)@Injectable()export class UsersService { private sessionCache = new Map<string, User>(); // Local state
async setSession(sessionId: string, user: User) { this.sessionCache.set(sessionId, user); // Lost on restart/scale }}
// GOOD: External state (scales horizontally)@Injectable()export class UsersService { constructor( @Inject(CACHE_MANAGER) private cacheManager: Cache, // Redis-backed cache ) {}
async setSession(sessionId: string, user: User) { await this.cacheManager.set(`session:${sessionId}`, user, 3600000); }}Load Balancer Configuration
Section titled “Load Balancer Configuration”# nginx.conf for load balancingupstream backend { least_conn; # Route to least connected server server backend1:3000 weight=3; server backend2:3000 weight=2; server backend3:3000 weight=1; keepalive 32;}
server { listen 80;
location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}40.3 Database Scaling
Section titled “40.3 Database Scaling”Read Replicas
Section titled “Read Replicas”import { Module } from '@nestjs/common';import { TypeOrmModule } from '@nestjs/typeorm';import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({ imports: [ TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => ({ type: 'postgres',
// Write replica (primary) replication: { master: { host: configService.get('DB_PRIMARY_HOST'), port: configService.get('DB_PORT', 5432), username: configService.get('DB_USERNAME'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_DATABASE'), }, slaves: [ { host: configService.get('DB_REPLICA1_HOST'), port: configService.get('DB_PORT', 5432), username: configService.get('DB_USERNAME'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_DATABASE'), }, { host: configService.get('DB_REPLICA2_HOST'), port: configService.get('DB_PORT', 5432), username: configService.get('DB_USERNAME'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_DATABASE'), }, ], },
poolSize: 20, entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: false, }), inject: [ConfigService], }), ],})export class AppModule {}
// TypeORM automatically routes:// - SELECT queries to replicas// - INSERT/UPDATE/DELETE to masterManual Read/Write Splitting
Section titled “Manual Read/Write Splitting”import { Injectable, InjectDataSource } from '@nestjs/common';import { DataSource } from 'typeorm';
@Injectable()export class ReadWriteService { constructor( @InjectDataSource('write') private writeDataSource: DataSource, @InjectDataSource('read') private readDataSource: DataSource, ) {}
// Use for reads get readManager() { return this.readDataSource.manager; }
// Use for writes get writeManager() { return this.writeDataSource.manager; }
// Execute read query async read<T>(query: (manager: any) => Promise<T>): Promise<T> { return query(this.readManager); }
// Execute write query async write<T>(query: (manager: any) => Promise<T>): Promise<T> { return query(this.writeManager); }}
// Usage@Injectable()export class UsersService { constructor(private readWrite: ReadWriteService) {}
async findAll(): Promise<User[]> { return this.readWrite.read(manager => manager.find(User) ); }
async create(dto: CreateUserDto): Promise<User> { return this.readWrite.write(manager => manager.save(User, dto) ); }}40.4 Connection Pool Scaling
Section titled “40.4 Connection Pool Scaling”Dynamic Pool Sizing
Section titled “Dynamic Pool Sizing”import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';import { InjectDataSource } from '@nestjs/typeorm';import { DataSource } from 'typeorm';
@Injectable()export class PoolScalerService implements OnModuleInit, OnModuleDestroy { private interval: NodeJS.Timeout; private minPoolSize = 5; private maxPoolSize = 50; private targetUtilization = 0.7;
constructor( @InjectDataSource() private dataSource: DataSource, ) {}
onModuleInit() { // Check every minute this.interval = setInterval(() => this.adjustPoolSize(), 60000); }
onModuleDestroy() { clearInterval(this.interval); }
private async adjustPoolSize() { const stats = this.getPoolStats(); const utilization = stats.active / stats.total;
if (utilization > this.targetUtilization + 0.1) { // Scale up const newSize = Math.min(stats.total + 5, this.maxPoolSize); await this.setPoolSize(newSize); } else if (utilization < this.targetUtilization - 0.1) { // Scale down const newSize = Math.max(stats.total - 5, this.minPoolSize); await this.setPoolSize(newSize); } }
private getPoolStats() { const driver = this.dataSource.driver as any; const pool = driver.pool;
return { total: pool.totalCount, idle: pool.idleCount, active: pool.totalCount - pool.idleCount, waiting: pool.waitingCount, }; }
private async setPoolSize(size: number) { // Note: This requires pool library support const driver = this.dataSource.driver as any; if (driver.pool?.options) { driver.pool.options.max = size; } }}40.5 Caching for Scale
Section titled “40.5 Caching for Scale”Distributed Caching
Section titled “Distributed Caching”import { Injectable, Inject } from '@nestjs/common';import { CACHE_MANAGER } from '@nestjs/cache-manager';import { Cache } from 'cache-manager';
@Injectable()export class DistributedCacheService { constructor( @Inject(CACHE_MANAGER) private cacheManager: Cache, ) {}
async getOrSet<T>( key: string, factory: () => Promise<T>, ttl: number = 60000, ): Promise<T> { // Try cache const cached = await this.cacheManager.get<T>(key); if (cached !== undefined) { return cached; }
// Use distributed lock to prevent cache stampede const lockKey = `lock:${key}`; const locked = await this.acquireLock(lockKey, 5000);
if (!locked) { // Wait and retry await this.sleep(100); return this.getOrSet(key, factory, ttl); }
try { // Double-check after acquiring lock const cachedAfterLock = await this.cacheManager.get<T>(key); if (cachedAfterLock !== undefined) { return cachedAfterLock; }
// Execute factory const value = await factory();
// Store in cache await this.cacheManager.set(key, value, ttl);
return value; } finally { await this.releaseLock(lockKey); } }
private async acquireLock(key: string, ttl: number): Promise<boolean> { const store = this.cacheManager.store as any; if (store.setnx) { return store.setnx(key, '1', ttl); } return true; // Fallback if no distributed lock support }
private async releaseLock(key: string): Promise<void> { await this.cacheManager.del(key); }
private sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); }}40.6 Database Sharding
Section titled “40.6 Database Sharding”Sharding Strategy
Section titled “Sharding Strategy” Database Sharding ================================================================================
Horizontal Partitioning by Key:
+------------------+ +------------------+ +------------------+ | Shard 1 | | Shard 2 | | Shard 3 | | (users 1-1M) | | (users 1M-2M) | | (users 2M-3M) | +------------------+ +------------------+ +------------------+
Sharding Key Selection: - User ID (most common) - Geographic region - Tenant ID (multi-tenant) - Date range (time-series)
================================================================================Sharding Implementation
Section titled “Sharding Implementation”import { Injectable } from '@nestjs/common';import { DataSource } from 'typeorm';
interface ShardConfig { id: string; host: string; port: number; rangeStart: number; rangeEnd: number;}
@Injectable()export class ShardingService { private shards: Map<string, DataSource> = new Map(); private shardConfigs: ShardConfig[] = [];
async initializeShards(configs: ShardConfig[]) { this.shardConfigs = configs;
for (const config of configs) { const dataSource = new DataSource({ type: 'postgres', host: config.host, port: config.port, username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, entities: ['src/**/*.entity{.ts,.js}'], poolSize: 10, });
await dataSource.initialize(); this.shards.set(config.id, dataSource); } }
getShardByKey(key: number): DataSource { for (const config of this.shardConfigs) { if (key >= config.rangeStart && key < config.rangeEnd) { return this.shards.get(config.id)!; } }
throw new Error(`No shard found for key: ${key}`); }
getShardForUser(userId: number): DataSource { return this.getShardByKey(userId); }
async closeAll() { for (const dataSource of this.shards.values()) { if (dataSource.isInitialized) { await dataSource.destroy(); } } this.shards.clear(); }}
// Usage@Injectable()export class UsersService { constructor(private sharding: ShardingService) {}
async findOne(userId: number): Promise<User> { const shard = this.sharding.getShardForUser(userId); return shard.manager.findOne(User, { where: { id: userId } }); }
async create(user: Partial<User>): Promise<User> { const shard = this.sharding.getShardForUser(user.id); return shard.manager.save(User, user); }}40.7 Auto-Scaling
Section titled “40.7 Auto-Scaling”Kubernetes Horizontal Pod Autoscaler
Section titled “Kubernetes Horizontal Pod Autoscaler”apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: myapp-hpaspec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp minReplicas: 3 maxReplicas: 20 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: 100 periodSeconds: 15 - type: Pods value: 4 periodSeconds: 15 selectPolicy: Max scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 10 periodSeconds: 60Custom Metrics Autoscaling
Section titled “Custom Metrics Autoscaling”import { Injectable } from '@nestjs/common';import { PrometheusMetricsService } from './prometheus.service';
@Injectable()export class CustomMetricsService { constructor(private prometheus: PrometheusMetricsService) {}
// Track for autoscaling decisions async recordRequestMetrics(duration: number, success: boolean) { this.prometheus.recordRequest(duration, success); }
async recordQueueLength(length: number) { this.prometheus.recordQueueLength(length); }
async recordActiveConnections(count: number) { this.prometheus.recordConnections(count); }}40.8 Scaling Checklist
Section titled “40.8 Scaling Checklist” Scaling Checklist ================================================================================
Application: [ ] Stateless design (no local state) [ ] External session storage [ ] Connection pooling configured [ ] Caching implemented [ ] Rate limiting enabled
Database: [ ] Read replicas configured [ ] Connection pool sized appropriately [ ] Indexes optimized [ ] Query performance monitored [ ] Sharding strategy (if needed)
Infrastructure: [ ] Load balancer configured [ ] Auto-scaling enabled [ ] Health checks implemented [ ] Monitoring and alerting [ ] Disaster recovery plan
Performance: [ ] Response time targets defined [ ] Throughput targets defined [ ] Load testing completed [ ] Bottlenecks identified and resolved
================================================================================40.9 Summary
Section titled “40.9 Summary” Scaling Quick Reference +------------------------------------------------------------------+ | | | Strategy | Use Case | | -------------------|------------------------------------------| | Vertical | Simple, limited scale | | Horizontal | High availability, unlimited scale | | Read Replicas | Read-heavy workloads | | Sharding | Very large datasets | | Caching | Reduce database load | | | | Components | Description | | -------------------|------------------------------------------| | Load Balancer | Distribute traffic | | Connection Pool | Reuse database connections | | Cache Layer | Store frequently accessed data | | Auto-scaler | Automatic capacity adjustment | | | | Best Practices | Description | | -------------------|------------------------------------------| | Stateless | No local state in application | | External state | Use Redis for sessions | | Monitor | Track key metrics | | Test at scale | Load test before production | | | +------------------------------------------------------------------+Next Chapter
Section titled “Next Chapter”Last Updated: February 2026