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:
- apiKubernetes 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: 80Database 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 = 5TypeORM 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
| Users | Instances | Database | Cache |
|---|---|---|---|
| 0-1K | 1-2 | Single | Optional |
| 1K-10K | 2-4 | + Replica | Redis |
| 10K-100K | 4-10 | + Read replicas | Redis Cluster |
| 100K+ | 10+ | Sharding | Redis Cluster |
Performance Targets
| Metric | Target |
|---|---|
| API Response (p50) | < 50ms |
| API Response (p99) | < 200ms |
| Throughput | 1000+ RPS per instance |
| Database queries | < 10ms average |
| Cache hit rate | > 90% |
Related
Last updated on