Skip to Content
🚀 APSO is now in public beta. Get started →

Scaling

APSO-generated backends are designed for horizontal scaling from day one.

Scaling Architecture

Horizontal Scaling

Stateless Application Layer

APSO backends are stateless by design:

  • No in-memory sessions
  • JWT-based authentication
  • External session storage (Redis)
  • No sticky sessions required

Container Deployment

# docker-compose.scale.yml services: api: image: my-api:latest deploy: replicas: 3 resources: limits: cpus: '1' memory: 512M environment: - DATABASE_URL=${DATABASE_URL} - REDIS_URL=${REDIS_URL} nginx: image: nginx:alpine ports: - "80:80" depends_on: - api

Kubernetes Horizontal Pod Autoscaler

apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: apso-api-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: apso-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

Database Scaling

Connection Pooling

Use PgBouncer for connection management:

# pgbouncer.ini [databases] myapp = host=postgres port=5432 dbname=myapp [pgbouncer] pool_mode = transaction max_client_conn = 1000 default_pool_size = 20 min_pool_size = 5 reserve_pool_size = 5

TypeORM Configuration

// src/config/database.config.ts export const databaseConfig = { type: 'postgres', url: process.env.DATABASE_URL, extra: { max: 20, // Max pool size per instance idleTimeoutMillis: 30000, // Close idle connections connectionTimeoutMillis: 2000, }, logging: process.env.NODE_ENV === 'development', };

Read Replicas

// Multiple database connections @Module({ imports: [ TypeOrmModule.forRoot({ name: 'default', // Primary for writes url: process.env.DATABASE_URL, }), TypeOrmModule.forRoot({ name: 'replica', // Replica for reads url: process.env.DATABASE_REPLICA_URL, }), ], }) export class DatabaseModule {} // Use replica for read operations @Injectable() export class ReportingService { constructor( @InjectRepository(Order, 'replica') private readonly orderRepo: Repository<Order>, ) {} }

Caching

Redis Cache Layer

// src/cache/cache.module.ts import { CacheModule } from '@nestjs/cache-manager'; import * as redisStore from 'cache-manager-redis-store'; @Module({ imports: [ CacheModule.register({ store: redisStore, host: process.env.REDIS_HOST, port: process.env.REDIS_PORT, ttl: 300, // 5 minutes default }), ], }) export class CacheConfigModule {}

Cache Decorator

// Cache at controller level @Get() @UseInterceptors(CacheInterceptor) @CacheTTL(60) // 1 minute findAll(@Query() query: FindAllQuery) { return this.service.findAll(query); } // Cache at service level @Injectable() export class ProjectsService { constructor( @Inject(CACHE_MANAGER) private cacheManager: Cache, ) {} async findOne(id: string): Promise<Project> { const cacheKey = `project:${id}`; let project = await this.cacheManager.get<Project>(cacheKey); if (!project) { project = await this.repository.findOne({ where: { id } }); await this.cacheManager.set(cacheKey, project, 300); } return project; } }

Cache Invalidation

// Invalidate on updates async update(id: string, dto: UpdateProjectDto): Promise<Project> { const project = await this.repository.save({ id, ...dto }); await this.cacheManager.del(`project:${id}`); await this.cacheManager.del(`projects:list`); return project; }

Query Optimization

Eager Loading

// Avoid N+1 queries async findAllWithTasks(): Promise<Project[]> { return this.repository.find({ relations: ['tasks'], // Join in single query }); }

Query Builder Optimization

// Select only needed fields const projects = await this.repository .createQueryBuilder('project') .select(['project.id', 'project.name']) .where('project.organizationId = :orgId', { orgId }) .getMany();

Indexes

Generated entities include appropriate indexes:

@Entity('projects') @Index(['organizationId']) @Index(['createdAt']) @Index(['organizationId', 'status']) export class Project { // ... }

Performance Monitoring

Metrics Collection

// Prometheus metrics import { PrometheusModule, makeHistogramProvider } from '@willsoto/nestjs-prometheus'; @Module({ imports: [ PrometheusModule.register({ path: '/metrics', defaultMetrics: { enabled: true }, }), ], providers: [ makeHistogramProvider({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'route', 'status'], }), ], }) export class MetricsModule {}

Request Timing

// Logging middleware with timing @Injectable() export class LoggingMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; console.log({ method: req.method, url: req.url, status: res.statusCode, duration: `${duration}ms`, }); }); next(); } }

Scaling Guidelines

UsersInstancesDatabaseCache
0-1K1-2SingleOptional
1K-10K2-4+ ReplicaRedis
10K-100K4-10+ Read replicasRedis Cluster
100K+10+ShardingRedis Cluster

Performance Targets

MetricTarget
API Response (p50)< 50ms
API Response (p99)< 200ms
Throughput1000+ RPS per instance
Database queries< 10ms average
Cache hit rate> 90%
Last updated on