Performance-Optimierung ist ein kritischer Aspekt bei der Entwicklung skalierbarer NestJS-Anwendungen. Dieses Kapitel behandelt systematische Ansätze zur Identifikation von Bottlenecks, Optimierung von Datenbankzugriffen, effizientes Memory Management und verschiedene Strategien zur Verbesserung der Gesamtperformance von NestJS-Anwendungen.
Profiling ist der erste Schritt zur Performance-Optimierung und ermöglicht es, Bottlenecks präzise zu identifizieren.
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { performance, PerformanceObserver } from 'perf_hooks';
import * as fs from 'fs';
@Injectable()
export class PerformanceProfiler implements OnModuleInit, OnModuleDestroy {
private observer: PerformanceObserver;
private metrics: Map<string, number[]> = new Map();
onModuleInit() {
// Performance Observer für automatisches Tracking
this.observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
this.recordMetric(entry.name, entry.duration);
});
});
this.observer.observe({ entryTypes: ['measure', 'function'] });
}
onModuleDestroy() {
this.observer?.disconnect();
this.exportMetrics();
}
// Manuelle Performance-Messungen
startTiming(label: string): () => number {
const startTime = performance.now();
performance.mark(`${label}-start`);
return () => {
const endTime = performance.now();
performance.mark(`${label}-end`);
performance.measure(label, `${label}-start`, `${label}-end`);
const duration = endTime - startTime;
this.recordMetric(label, duration);
return duration;
};
}
// Decorator für automatisches Method Profiling
profile(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const className = target.constructor.name;
descriptor.value = async function (...args: any[]) {
const label = `${className}.${propertyKey}`;
const endTiming = this.startTiming(label);
try {
const result = await originalMethod.apply(this, args);
return result;
} finally {
endTiming();
}
};
return descriptor;
}
private recordMetric(name: string, duration: number): void {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}
this.metrics.get(name)!.push(duration);
}
getMetrics(): Record<string, {
count: number;
average: number;
min: number;
max: number;
p95: number;
p99: number;
}> {
const result: Record<string, any> = {};
this.metrics.forEach((durations, name) => {
const sorted = durations.sort((a, b) => a - b);
const count = durations.length;
const sum = durations.reduce((a, b) => a + b, 0);
result[name] = {
count,
average: sum / count,
min: sorted[0],
max: sorted[count - 1],
p95: sorted[Math.floor(count * 0.95)],
p99: sorted[Math.floor(count * 0.99)],
};
});
return result;
}
private exportMetrics(): void {
const metrics = this.getMetrics();
const timestamp = new Date().toISOString();
fs.writeFileSync(
`performance-metrics-${timestamp}.json`,
JSON.stringify(metrics, null, 2)
);
}
// Memory Usage Tracking
getMemoryUsage(): NodeJS.MemoryUsage & {
external: number;
arrayBuffers: number;
} {
return process.memoryUsage();
}
// CPU Usage (approximiert über Event Loop Lag)
measureEventLoopLag(): Promise<number> {
return new Promise((resolve) => {
const start = process.hrtime.bigint();
setImmediate(() => {
const lag = Number(process.hrtime.bigint() - start) / 1e6; // Convert to ms
resolve(lag);
});
});
}
}
// Usage Example
@Injectable()
export class UserService {
constructor(private profiler: PerformanceProfiler) {}
@profile
async findUsers(filters: any): Promise<User[]> {
// Automatisches Profiling durch Decorator
return await this.userRepository.find(filters);
}
async complexOperation(): Promise<any> {
const endTiming = this.profiler.startTiming('complex-operation');
try {
// Komplexe Operation
const result = await this.performComplexCalculation();
return result;
} finally {
const duration = endTiming();
if (duration > 1000) { // Warnung bei >1s
console.warn(`Slow operation detected: ${duration}ms`);
}
}
}
private async performComplexCalculation(): Promise<any> {
// Simulierte komplexe Berechnung
await new Promise(resolve => setTimeout(resolve, 500));
return { result: 'computed' };
}
}import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AdvancedProfiler {
private isProfilingEnabled: boolean;
constructor(private configService: ConfigService) {
this.isProfilingEnabled = this.configService.get('PROFILING_ENABLED', false);
}
// Heap Snapshot für Memory Analysis
async createHeapSnapshot(): Promise<string> {
const v8 = require('v8');
const fs = require('fs').promises;
const heapSnapshot = v8.getHeapSnapshot();
const fileName = `heap-${Date.now()}.heapsnapshot`;
const chunks: Buffer[] = [];
return new Promise((resolve, reject) => {
heapSnapshot.on('data', (chunk) => chunks.push(chunk));
heapSnapshot.on('end', async () => {
try {
await fs.writeFile(fileName, Buffer.concat(chunks));
resolve(fileName);
} catch (error) {
reject(error);
}
});
heapSnapshot.on('error', reject);
});
}
// CPU Profiling
startCpuProfiling(): void {
if (!this.isProfilingEnabled) return;
const inspector = require('inspector');
const session = new inspector.Session();
session.connect();
session.post('Profiler.enable', () => {
session.post('Profiler.start');
});
// Auto-stop nach 30 Sekunden
setTimeout(() => {
this.stopCpuProfiling(session);
}, 30000);
}
private stopCpuProfiling(session: any): void {
session.post('Profiler.stop', (err: any, { profile }: any) => {
if (err) {
console.error('CPU Profiling error:', err);
return;
}
const fs = require('fs');
const fileName = `cpu-profile-${Date.now()}.cpuprofile`;
fs.writeFileSync(fileName, JSON.stringify(profile));
session.disconnect();
console.log(`CPU profile saved to ${fileName}`);
});
}
// Real-time Performance Monitoring
startRealTimeMonitoring(): void {
const monitoringInterval = setInterval(() => {
const metrics = {
timestamp: new Date().toISOString(),
memory: process.memoryUsage(),
cpu: process.cpuUsage(),
uptime: process.uptime(),
activeHandles: process._getActiveHandles().length,
activeRequests: process._getActiveRequests().length,
};
this.sendMetricsToMonitoring(metrics);
}, 5000); // Alle 5 Sekunden
// Cleanup bei Process-Ende
process.on('SIGTERM', () => clearInterval(monitoringInterval));
process.on('SIGINT', () => clearInterval(monitoringInterval));
}
private sendMetricsToMonitoring(metrics: any): void {
// Integration mit Monitoring-System (z.B. Prometheus, DataDog)
console.log('Performance Metrics:', metrics);
// Beispiel: Prometheus Metrics
// this.prometheusService.recordMetrics(metrics);
}
// Bottleneck Detection
async detectBottlenecks(): Promise<{
slowQueries: any[];
memoryLeaks: any[];
highCpuOperations: any[];
}> {
const slowQueries = await this.identifySlowQueries();
const memoryLeaks = await this.detectMemoryLeaks();
const highCpuOperations = await this.identifyHighCpuOperations();
return {
slowQueries,
memoryLeaks,
highCpuOperations,
};
}
private async identifySlowQueries(): Promise<any[]> {
// Query Performance Analysis
// Integration mit Database-spezifischen Tools
return [];
}
private async detectMemoryLeaks(): Promise<any[]> {
// Memory Leak Detection
const memUsage = process.memoryUsage();
const threshold = 500 * 1024 * 1024; // 500MB
if (memUsage.heapUsed > threshold) {
return [{
type: 'high-memory-usage',
heapUsed: memUsage.heapUsed,
threshold,
timestamp: new Date(),
}];
}
return [];
}
private async identifyHighCpuOperations(): Promise<any[]> {
// CPU-intensive Operations Detection
const cpuUsage = process.cpuUsage();
const highCpuThreshold = 80; // 80% CPU
// Vereinfachte CPU-Berechnung
const cpuPercent = (cpuUsage.user + cpuUsage.system) / 1000000; // Convert to seconds
if (cpuPercent > highCpuThreshold) {
return [{
type: 'high-cpu-usage',
cpuPercent,
threshold: highCpuThreshold,
timestamp: new Date(),
}];
}
return [];
}
}import { Injectable, OnModuleInit } from '@nestjs/common';
import { NodeSDK } from '@opentelemetry/auto-instrumentations-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
@Injectable()
export class TelemetryService implements OnModuleInit {
private sdk: NodeSDK;
onModuleInit() {
// OpenTelemetry SDK Konfiguration
this.sdk = new NodeSDK({
instrumentations: [getNodeAutoInstrumentations({
// HTTP-Instrumentierung aktivieren
'@opentelemetry/instrumentation-http': {
enabled: true,
requestHook: (span, request) => {
span.setAttributes({
'http.route': this.extractRoute(request.url),
'http.user_agent': request.headers['user-agent'],
});
},
},
// Database-Instrumentierung
'@opentelemetry/instrumentation-mysql': { enabled: true },
'@opentelemetry/instrumentation-postgres': { enabled: true },
'@opentelemetry/instrumentation-redis': { enabled: true },
})],
metricReader: new PeriodicExportingMetricReader({
exporter: new PrometheusExporter({
port: 9464,
}),
exportIntervalMillis: 1000,
}),
});
this.sdk.start();
}
private extractRoute(url: string): string {
// Route-Extraktion für bessere Gruppierung
return url.replace(/\/\d+/g, '/:id')
.replace(/\?.*/, '');
}
// Custom Spans für Business Logic
async traceOperation<T>(
operationName: string,
operation: () => Promise<T>,
attributes?: Record<string, string | number>
): Promise<T> {
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('nestjs-app');
return tracer.startActiveSpan(operationName, async (span) => {
try {
if (attributes) {
span.setAttributes(attributes);
}
const result = await operation();
span.setStatus({ code: 1 }); // SUCCESS
return result;
} catch (error) {
span.setStatus({
code: 2, // ERROR
message: error.message
});
span.recordException(error);
throw error;
} finally {
span.end();
}
});
}
// Performance Decorator mit Tracing
trace(operationName?: string) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const originalMethod = descriptor.value;
const spanName = operationName || `${target.constructor.name}.${propertyKey}`;
descriptor.value = async function (...args: any[]) {
return this.telemetryService.traceOperation(
spanName,
() => originalMethod.apply(this, args),
{
'method.class': target.constructor.name,
'method.name': propertyKey,
'args.count': args.length,
}
);
};
return descriptor;
};
}
}
// Usage Example
@Injectable()
export class TracedUserService {
constructor(private telemetryService: TelemetryService) {}
@trace('user.find.complex')
async findUsersWithComplexLogic(filters: any): Promise<User[]> {
return await this.telemetryService.traceOperation(
'database.query.users',
async () => {
// Database-Operation
return await this.userRepository.find(filters);
},
{
'query.type': 'find',
'filters.count': Object.keys(filters).length,
}
);
}
}Datenbankzugriffe sind oft der größte Performance-Bottleneck in Webanwendungen.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, SelectQueryBuilder } from 'typeorm';
import { User } from './entities/user.entity';
@Injectable()
export class OptimizedQueryService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>
) {}
// N+1 Problem vermeiden mit Eager Loading
async findUsersWithPostsOptimized(): Promise<User[]> {
return await this.userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'posts')
.leftJoinAndSelect('posts.comments', 'comments')
.getMany();
}
// Selective Field Loading
async findUsersWithLimitedFields(): Promise<Partial<User>[]> {
return await this.userRepository
.createQueryBuilder('user')
.select(['user.id', 'user.email', 'user.name'])
.getMany();
}
// Pagination mit Cursor-based Navigation
async findUsersWithCursorPagination(
cursor?: string,
limit: number = 20
): Promise<{
users: User[];
nextCursor?: string;
hasMore: boolean;
}> {
const queryBuilder = this.userRepository
.createQueryBuilder('user')
.orderBy('user.createdAt', 'DESC')
.limit(limit + 1); // +1 um hasMore zu bestimmen
if (cursor) {
const cursorDate = new Date(Buffer.from(cursor, 'base64').toString());
queryBuilder.where('user.createdAt < :cursor', { cursor: cursorDate });
}
const users = await queryBuilder.getMany();
const hasMore = users.length > limit;
if (hasMore) {
users.pop(); // Letzten Eintrag entfernen
}
const nextCursor = hasMore && users.length > 0
? Buffer.from(users[users.length - 1].createdAt.toISOString()).toString('base64')
: undefined;
return {
users,
nextCursor,
hasMore,
};
}
// Aggregation mit Caching
async getUserStatistics(): Promise<{
totalUsers: number;
activeUsers: number;
averagePostsPerUser: number;
}> {
const [totalUsers, activeUsers, avgPosts] = await Promise.all([
this.userRepository.count(),
this.userRepository.count({ where: { isActive: true } }),
this.userRepository
.createQueryBuilder('user')
.leftJoin('user.posts', 'posts')
.select('AVG(subquery.postCount)', 'average')
.from(subQuery => {
return subQuery
.select('user.id', 'userId')
.addSelect('COUNT(posts.id)', 'postCount')
.from(User, 'user')
.leftJoin('user.posts', 'posts')
.groupBy('user.id');
}, 'subquery')
.getRawOne(),
]);
return {
totalUsers,
activeUsers,
averagePostsPerUser: parseFloat(avgPosts?.average || '0'),
};
}
// Bulk Operations für bessere Performance
async bulkUpdateUserStatus(userIds: string[], status: string): Promise<void> {
await this.userRepository
.createQueryBuilder()
.update(User)
.set({ status })
.where('id IN (:...userIds)', { userIds })
.execute();
}
async bulkInsertUsers(users: Partial<User>[]): Promise<void> {
// Batch-Insert in Chunks für bessere Performance
const chunkSize = 1000;
for (let i = 0; i < users.length; i += chunkSize) {
const chunk = users.slice(i, i + chunkSize);
await this.userRepository
.createQueryBuilder()
.insert()
.into(User)
.values(chunk)
.execute();
}
}
// Query Performance Monitoring
async executeWithMonitoring<T>(
queryBuilder: SelectQueryBuilder<T>,
queryName: string
): Promise<T[]> {
const startTime = performance.now();
try {
const result = await queryBuilder.getMany();
const duration = performance.now() - startTime;
// Performance-Logging
if (duration > 1000) { // Langsame Queries loggen
console.warn(`Slow query detected: ${queryName}`, {
duration: `${duration.toFixed(2)}ms`,
sql: queryBuilder.getSql(),
parameters: queryBuilder.getParameters(),
});
}
return result;
} catch (error) {
const duration = performance.now() - startTime;
console.error(`Query failed: ${queryName}`, {
duration: `${duration.toFixed(2)}ms`,
error: error.message,
sql: queryBuilder.getSql(),
});
throw error;
}
}
// Index Optimization Hints
async analyzeQueryPerformance(): Promise<{
recommendations: string[];
slowQueries: Array<{
query: string;
avgDuration: number;
executionCount: number;
}>;
}> {
// Vereinfachte Analyse - in der Praxis würde man
// Database-spezifische EXPLAIN-Befehle verwenden
return {
recommendations: [
'Add index on user.email for faster lookups',
'Consider partitioning large tables',
'Use covering indexes for frequent SELECT queries',
],
slowQueries: [
{
query: 'SELECT * FROM users WHERE status = ?',
avgDuration: 1250,
executionCount: 1500,
},
],
};
}
}import { Injectable } from '@nestjs/common';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class DatabaseOptimizationService {
constructor(private configService: ConfigService) {}
// Optimierte Database Configuration
getOptimizedDatabaseConfig(): TypeOrmModuleOptions {
const isProduction = this.configService.get('NODE_ENV') === 'production';
return {
type: 'postgres',
host: this.configService.get('DB_HOST'),
port: this.configService.get('DB_PORT'),
username: this.configService.get('DB_USERNAME'),
password: this.configService.get('DB_PASSWORD'),
database: this.configService.get('DB_NAME'),
// Connection Pool Optimization
extra: {
// Maximale Anzahl Verbindungen
max: isProduction ? 20 : 10,
// Minimale Anzahl Verbindungen
min: isProduction ? 5 : 2,
// Connection Timeout
connectionTimeoutMillis: 30000,
// Idle Timeout
idleTimeoutMillis: 30000,
// Statement Timeout
statement_timeout: 30000,
// Query Timeout
query_timeout: 30000,
// SSL Configuration für Production
ssl: isProduction ? { rejectUnauthorized: false } : false,
// Application Name für besseres Monitoring
application_name: 'nestjs-app',
},
// Query Optimization
cache: {
duration: 30000, // 30 Sekunden Query Cache
type: 'redis',
options: {
host: this.configService.get('REDIS_HOST'),
port: this.configService.get('REDIS_PORT'),
},
},
// Logging für Development
logging: !isProduction ? ['query', 'error', 'warn'] : ['error'],
// Schema Synchronization nur in Development
synchronize: !isProduction,
// Migration Settings
migrationsRun: isProduction,
migrations: ['dist/migrations/*.js'],
// Entity Loading
entities: ['dist/**/*.entity.js'],
// Retry Logic
retryAttempts: isProduction ? 3 : 1,
retryDelay: 3000,
};
}
// Read Replicas für Read-Heavy Workloads
getReadReplicaConfig(): TypeOrmModuleOptions {
return {
...this.getOptimizedDatabaseConfig(),
name: 'readReplica',
host: this.configService.get('DB_READ_REPLICA_HOST'),
// Nur Read-Operations
extra: {
...this.getOptimizedDatabaseConfig().extra,
// Read-Only Connection
readonly: true,
},
};
}
// Connection Health Monitoring
async monitorConnectionHealth(): Promise<{
activeConnections: number;
idleConnections: number;
totalConnections: number;
connectionErrors: number;
}> {
// Implementation würde Database-spezifische Monitoring-Queries verwenden
// Hier ein vereinfachtes Beispiel
return {
activeConnections: 15,
idleConnections: 5,
totalConnections: 20,
connectionErrors: 0,
};
}
// Automatic Connection Pool Scaling
async adjustConnectionPool(metrics: {
avgResponseTime: number;
activeConnections: number;
queuedRequests: number;
}): Promise<void> {
const { avgResponseTime, activeConnections, queuedRequests } = metrics;
// Scale up wenn:
// - Response Time > 1s
// - Queue > 10 Requests
// - Connection Usage > 80%
if (avgResponseTime > 1000 || queuedRequests > 10) {
console.log('Scaling up database connections');
// Implementation für Pool-Skalierung
}
// Scale down wenn alle Metriken niedrig sind
if (avgResponseTime < 200 && queuedRequests === 0 && activeConnections < 5) {
console.log('Scaling down database connections');
// Implementation für Pool-Reduzierung
}
}
}
// Query Builder mit automatischer Optimization
@Injectable()
export class OptimizedQueryBuilder {
constructor(
@InjectRepository(User) private userRepo: Repository<User>
) {}
createOptimizedQuery(): SelectQueryBuilder<User> {
return this.userRepo
.createQueryBuilder('user')
.cache(30000) // 30s Cache
.setQueryRunner(this.getOptimalQueryRunner());
}
private getOptimalQueryRunner(): any {
// Read-Heavy Operations zu Read Replica routen
const operationType = this.determineOperationType();
if (operationType === 'read') {
// Read Replica verwenden
return this.getReadReplicaQueryRunner();
}
// Write Operations zur Master DB
return this.getMasterQueryRunner();
}
private determineOperationType(): 'read' | 'write' {
// Query-Typ-Detection basierend auf Call Stack
const stack = new Error().stack;
if (stack?.includes('find') || stack?.includes('get')) {
return 'read';
}
return 'write';
}
private getReadReplicaQueryRunner(): any {
// Read Replica Connection
return null; // Implementation abhängig von Setup
}
private getMasterQueryRunner(): any {
// Master DB Connection
return null; // Implementation abhängig von Setup
}
}Effizientes Memory Management ist entscheidend für die Stabilität und Performance von Node.js-Anwendungen.
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { Interval } from '@nestjs/schedule';
@Injectable()
export class MemoryManager implements OnModuleInit, OnModuleDestroy {
private memoryBaseline: NodeJS.MemoryUsage;
private memoryHistory: Array<{
timestamp: Date;
usage: NodeJS.MemoryUsage;
gcCount: number;
}> = [];
private gcCount = 0;
onModuleInit() {
this.memoryBaseline = process.memoryUsage();
this.setupGCMonitoring();
this.setupMemoryMonitoring();
}
onModuleDestroy() {
this.logMemoryReport();
}
private setupGCMonitoring(): void {
// GC Event Monitoring
const { PerformanceObserver } = require('perf_hooks');
const gcObserver = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.entryType === 'gc') {
this.gcCount++;
this.logGCEvent(entry);
}
});
});
gcObserver.observe({ entryTypes: ['gc'] });
}
@Interval(30000) // Alle 30 Sekunden
private setupMemoryMonitoring(): void {
const currentUsage = process.memoryUsage();
this.memoryHistory.push({
timestamp: new Date(),
usage: currentUsage,
gcCount: this.gcCount,
});
// Nur letzte 100 Einträge behalten
if (this.memoryHistory.length > 100) {
this.memoryHistory.shift();
}
// Memory Leak Detection
this.detectMemoryLeaks(currentUsage);
}
private detectMemoryLeaks(currentUsage: NodeJS.MemoryUsage): void {
const heapGrowth = currentUsage.heapUsed - this.memoryBaseline.heapUsed;
const heapGrowthMB = heapGrowth / (1024 * 1024);
// Warnung bei Heap-Wachstum > 100MB
if (heapGrowthMB > 100) {
console.warn('Potential memory leak detected', {
heapGrowthMB: heapGrowthMB.toFixed(2),
currentHeapMB: (currentUsage.heapUsed / (1024 * 1024)).toFixed(2),
baselineHeapMB: (this.memoryBaseline.heapUsed / (1024 * 1024)).toFixed(2),
});
// Heap Snapshot für Analyse erstellen
this.createHeapSnapshot();
}
// RSS Memory Check
const rssGrowth = currentUsage.rss - this.memoryBaseline.rss;
const rssGrowthMB = rssGrowth / (1024 * 1024);
if (rssGrowthMB > 200) { // 200MB RSS Wachstum
console.warn('High RSS memory usage detected', {
rssGrowthMB: rssGrowthMB.toFixed(2),
currentRssMB: (currentUsage.rss / (1024 * 1024)).toFixed(2),
});
}
}
private async createHeapSnapshot(): Promise<void> {
try {
const v8 = require('v8');
const fs = require('fs').promises;
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `heap-snapshot-${timestamp}.heapsnapshot`;
const heapSnapshot = v8.getHeapSnapshot();
const chunks: Buffer[] = [];
heapSnapshot.on('data', (chunk) => chunks.push(chunk));
heapSnapshot.on('end', async () => {
await fs.writeFile(filename, Buffer.concat(chunks));
console.log(`Heap snapshot saved: ${filename}`);
});
} catch (error) {
console.error('Failed to create heap snapshot:', error);
}
}
private logGCEvent(entry: any): void {
console.log('GC Event:', {
kind: entry.detail?.kind,
duration: `${entry.duration.toFixed(2)}ms`,
timestamp: new Date(entry.startTime).toISOString(),
});
}
// Memory-optimized Object Pool
createObjectPool<T>(
factory: () => T,
reset: (obj: T) => void,
maxSize: number = 100
): ObjectPool<T> {
return new ObjectPool(factory, reset, maxSize);
}
// Memory-efficient Data Processing
async processLargeDataset<T, R>(
data: T[],
processor: (item: T) => Promise<R>,
batchSize: number = 1000
): Promise<R[]> {
const results: R[] = [];
for (let i = 0; i < data.length; i += batchSize) {
const batch = data.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(item => processor(item))
);
results.push(...batchResults);
// Garbage Collection nach jedem Batch
if (global.gc) {
global.gc();
}
// Kurze Pause für Event Loop
await new Promise(resolve => setImmediate(resolve));
}
return results;
}
private logMemoryReport(): void {
if (this.memoryHistory.length === 0) return;
const latest = this.memoryHistory[this.memoryHistory.length - 1];
const growth = {
heapUsed: latest.usage.heapUsed - this.memoryBaseline.heapUsed,
heapTotal: latest.usage.heapTotal - this.memoryBaseline.heapTotal,
rss: latest.usage.rss - this.memoryBaseline.rss,
external: latest.usage.external - this.memoryBaseline.external,
};
console.log('Memory Report:', {
baseline: this.formatMemoryUsage(this.memoryBaseline),
current: this.formatMemoryUsage(latest.usage),
growth: this.formatMemoryUsage(growth as NodeJS.MemoryUsage),
totalGCCount: this.gcCount,
});
}
private formatMemoryUsage(usage: NodeJS.MemoryUsage): Record<string, string> {
return {
heapUsed: `${(usage.heapUsed / (1024 * 1024)).toFixed(2)} MB`,
heapTotal: `${(usage.heapTotal / (1024 * 1024)).toFixed(2)} MB`,
rss: `${(usage.rss / (1024 * 1024)).toFixed(2)} MB`,
external: `${(usage.external / (1024 * 1024)).toFixed(2)} MB`,
};
}
// Memory-aware Caching
shouldCacheBasedOnMemory(): boolean {
const usage = process.memoryUsage();
const heapUsagePercent = (usage.heapUsed / usage.heapTotal) * 100;
// Caching stoppen bei >85% Heap-Nutzung
return heapUsagePercent < 85;
}
// Force Garbage Collection (nur für Development/Testing)
forceGC(): boolean {
if (global.gc) {
global.gc();
return true;
}
return false;
}
}
// Object Pool Implementation
class ObjectPool<T> {
private pool: T[] = [];
private maxSize: number;
constructor(
private factory: () => T,
private reset: (obj: T) => void,
maxSize: number = 100
) {
this.maxSize = maxSize;
}
acquire(): T {
if (this.pool.length > 0) {
return this.pool.pop()!;
}
return this.factory();
}
release(obj: T): void {
if (this.pool.length < this.maxSize) {
this.reset(obj);
this.pool.push(obj);
}
// Objektreferenz entfernen falls Pool voll
}
size(): number {
return this.pool.length;
}
clear(): void {
this.pool.length = 0;
}
}
// Memory-efficient Service Example
@Injectable()
export class MemoryEfficientService {
private readonly objectPool: ObjectPool<Buffer>;
constructor(private memoryManager: MemoryManager) {
// Buffer Pool für häufige Allokationen
this.objectPool = this.memoryManager.createObjectPool(
() => Buffer.alloc(1024), // 1KB Buffer
(buffer) => buffer.fill(0), // Reset Buffer
50 // Max 50 Buffers im Pool
);
}
async processData(data: any[]): Promise<any[]> {
// Memory-aware Processing
if (!this.memoryManager.shouldCacheBasedOnMemory()) {
console.warn('Skipping cache due to high memory usage');
}
return await this.memoryManager.processLargeDataset(
data,
async (item) => this.processItem(item),
500 // Kleinere Batch-Größe
);
}
private async processItem(item: any): Promise<any> {
const buffer = this.objectPool.acquire();
try {
// Buffer für Processing verwenden
const result = this.performOperation(item, buffer);
return result;
} finally {
// Buffer zurück zum Pool
this.objectPool.release(buffer);
}
}
private performOperation(item: any, buffer: Buffer): any {
// Verarbeitung mit wiederverwendetem Buffer
return { processed: item.id };
}
}Connection Pooling ist entscheidend für die Skalierbarkeit von Datenbankzugriffen und externen API-Calls.
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Pool, PoolClient, PoolConfig } from 'pg';
@Injectable()
export class DatabaseConnectionPool implements OnModuleInit, OnModuleDestroy {
private pool: Pool;
private connectionMetrics = {
activeConnections: 0,
totalConnections: 0,
connectionErrors: 0,
queryCount: 0,
avgQueryTime: 0,
};
constructor(private configService: ConfigService) {}
async onModuleInit(): Promise<void> {
await this.initializePool();
this.setupMonitoring();
}
async onModuleDestroy(): Promise<void> {
await this.pool?.end();
}
private async initializePool(): Promise<void> {
const poolConfig: PoolConfig = {
host: this.configService.get('DB_HOST'),
port: this.configService.get('DB_PORT'),
database: this.configService.get('DB_NAME'),
user: this.configService.get('DB_USER'),
password: this.configService.get('DB_PASSWORD'),
// Pool Configuration
min: this.configService.get('DB_POOL_MIN', 2),
max: this.configService.get('DB_POOL_MAX', 20),
// Connection Timeouts
connectionTimeoutMillis: 30000,
idleTimeoutMillis: 30000,
// Keep-alive
keepAlive: true,
keepAliveInitialDelayMillis: 10000,
// SSL Configuration
ssl: this.configService.get('NODE_ENV') === 'production' ? {
rejectUnauthorized: false
} : false,
// Application Name für Monitoring
application_name: 'nestjs-app',
// Statement Timeout
statement_timeout: 30000,
};
this.pool = new Pool(poolConfig);
// Event Listeners für Monitoring
this.pool.on('connect', (client) => {
this.connectionMetrics.activeConnections++;
this.connectionMetrics.totalConnections++;
console.log('New database connection established');
});
this.pool.on('remove', (client) => {
this.connectionMetrics.activeConnections--;
console.log('Database connection removed from pool');
});
this.pool.on('error', (error, client) => {
this.connectionMetrics.connectionErrors++;
console.error('Database pool error:', error);
});
}
async executeQuery<T = any>(
query: string,
params?: any[],
options?: { timeout?: number }
): Promise<T[]> {
const startTime = performance.now();
let client: PoolClient | undefined;
try {
client = await this.pool.connect();
if (options?.timeout) {
client.query('SET statement_timeout = $1', [options.timeout]);
}
const result = await client.query(query, params);
// Metrics aktualisieren
const duration = performance.now() - startTime;
this.updateQueryMetrics(duration);
return result.rows;
} catch (error) {
this.connectionMetrics.connectionErrors++;
console.error('Query execution failed:', {
query,
params,
error: error.message,
duration: performance.now() - startTime,
});
throw error;
} finally {
if (client) {
client.release();
}
}
}
async executeTransaction<T>(
operations: (client: PoolClient) => Promise<T>
): Promise<T> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
const result = await operations(client);
await client.query('COMMIT');
return result;
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}
private updateQueryMetrics(duration: number): void {
this.connectionMetrics.queryCount++;
// Moving Average für Query Time
const alpha = 0.1; // Gewichtung für exponentiellen Durchschnitt
this.connectionMetrics.avgQueryTime =
(alpha * duration) + ((1 - alpha) * this.connectionMetrics.avgQueryTime);
}
private setupMonitoring(): void {
setInterval(() => {
this.logPoolStatistics();
this.checkPoolHealth();
}, 30000); // Alle 30 Sekunden
}
private logPoolStatistics(): void {
const poolStats = {
totalCount: this.pool.totalCount,
idleCount: this.pool.idleCount,
waitingCount: this.pool.waitingCount,
...this.connectionMetrics,
};
console.log('Database Pool Statistics:', poolStats);
}
private checkPoolHealth(): void {
const { totalCount, idleCount, waitingCount } = this.pool;
// Warnung bei hoher Queue
if (waitingCount > 5) {
console.warn('High connection queue detected', {
waiting: waitingCount,
total: totalCount,
idle: idleCount,
});
}
// Warnung bei niedriger Idle-Rate
const idleRate = idleCount / totalCount;
if (idleRate < 0.1 && totalCount > 5) {
console.warn('Low idle connection rate', {
idleRate: (idleRate * 100).toFixed(1) + '%',
suggestion: 'Consider increasing pool size',
});
}
}
// Pool-Resize basierend auf Load
async adjustPoolSize(metrics: {
avgResponseTime: number;
queueLength: number;
cpuUsage: number;
}): Promise<void> {
const currentMax = this.pool.options.max || 20;
// Scale up bei hoher Last
if (metrics.avgResponseTime > 1000 && metrics.queueLength > 3) {
const newMax = Math.min(currentMax + 5, 50);
await this.resizePool(newMax);
console.log(`Scaled up pool to ${newMax} connections`);
}
// Scale down bei niedriger Last
if (metrics.avgResponseTime < 200 && metrics.queueLength === 0 && metrics.cpuUsage < 30) {
const newMax = Math.max(currentMax - 2, 5);
await this.resizePool(newMax);
console.log(`Scaled down pool to ${newMax} connections`);
}
}
private async resizePool(newMaxSize: number): Promise<void> {
// Pool-Resize würde spezifische Implementation benötigen
// da pg.Pool keine direkte Resize-Methode hat
console.log(`Pool resize to ${newMaxSize} connections requested`);
}
getPoolMetrics(): any {
return {
pool: {
totalCount: this.pool.totalCount,
idleCount: this.pool.idleCount,
waitingCount: this.pool.waitingCount,
},
metrics: this.connectionMetrics,
};
}
}import { Injectable, OnModuleInit } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import * as https from 'https';
import * as http from 'http';
@Injectable()
export class HttpConnectionPool implements OnModuleInit {
private httpAgent: http.Agent;
private httpsAgent: https.Agent;
constructor(
private httpService: HttpService,
private configService: ConfigService
) {}
onModuleInit() {
this.initializeAgents();
this.configureHttpService();
}
private initializeAgents(): void {
const agentConfig = {
// Connection Pool Konfiguration
keepAlive: true,
keepAliveMsecs: 30000,
maxSockets: this.configService.get('HTTP_MAX_SOCKETS', 50),
maxFreeSockets: this.configService.get('HTTP_MAX_FREE_SOCKETS', 10),
timeout: this.configService.get('HTTP_TIMEOUT', 30000),
freeSocketTimeout: this.configService.get('HTTP_FREE_SOCKET_TIMEOUT', 15000),
};
this.httpAgent = new http.Agent(agentConfig);
this.httpsAgent = new https.Agent({
...agentConfig,
// SSL/TLS Konfiguration
rejectUnauthorized: this.configService.get('NODE_ENV') === 'production',
secureProtocol: 'TLSv1_2_method',
});
}
private configureHttpService(): void {
// Axios mit Connection Pooling konfigurieren
this.httpService.axiosRef.defaults.httpAgent = this.httpAgent;
this.httpService.axiosRef.defaults.httpsAgent = this.httpsAgent;
// Request/Response Interceptors für Monitoring
this.httpService.axiosRef.interceptors.request.use((config) => {
config.metadata = { startTime: performance.now() };
return config;
});
this.httpService.axiosRef.interceptors.response.use(
(response) => {
const duration = performance.now() - response.config.metadata.startTime;
this.logHttpMetrics(response.config.url!, duration, 'success');
return response;
},
(error) => {
const duration = performance.now() - error.config?.metadata?.startTime || 0;
this.logHttpMetrics(error.config?.url || 'unknown', duration, 'error');
return Promise.reject(error);
}
);
}
private logHttpMetrics(url: string, duration: number, status: string): void {
if (duration > 5000) { // Langsame Requests loggen
console.warn('Slow HTTP request detected:', {
url,
duration: `${duration.toFixed(2)}ms`,
status,
});
}
}
// Connection Pool Statistiken
getConnectionStats(): {
http: any;
https: any;
} {
return {
http: {
sockets: Object.keys(this.httpAgent.sockets).length,
freeSockets: Object.keys(this.httpAgent.freeSockets).length,
requests: Object.keys(this.httpAgent.requests).length,
},
https: {
sockets: Object.keys(this.httpsAgent.sockets).length,
freeSockets: Object.keys(this.httpsAgent.freeSockets).length,
requests: Object.keys(this.httpsAgent.requests).length,
},
};
}
// Circuit Breaker Pattern für externe Services
async makeResilientRequest<T>(
url: string,
options: any = {},
circuitBreakerConfig?: {
failureThreshold?: number;
resetTimeout?: number;
}
): Promise<T> {
const config = {
failureThreshold: 5,
resetTimeout: 60000,
...circuitBreakerConfig,
};
// Vereinfachte Circuit Breaker Implementation
return await this.httpService.axiosRef({
url,
...options,
timeout: options.timeout || 10000,
retry: 3,
retryDelay: 1000,
}).then(response => response.data);
}
// Batch HTTP Requests mit Connection Sharing
async batchRequests<T>(
requests: Array<{
url: string;
method?: string;
data?: any;
headers?: any;
}>,
options?: {
batchSize?: number;
delay?: number;
}
): Promise<T[]> {
const { batchSize = 10, delay = 100 } = options || {};
const results: T[] = [];
for (let i = 0; i < requests.length; i += batchSize) {
const batch = requests.slice(i, i + batchSize);
const batchPromises = batch.map(request =>
this.httpService.axiosRef(request)
.then(response => response.data)
.catch(error => ({ error: error.message }))
);
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
// Kurze Pause zwischen Batches
if (i + batchSize < requests.length && delay > 0) {
await new Promise(resolve => setTimeout(resolve, delay));
}
}
return results;
}
// Connection Pool Cleanup
async cleanup(): Promise<void> {
this.httpAgent.destroy();
this.httpsAgent.destroy();
}
}Asynchrone Operationen richtig zu optimieren ist entscheidend für die Performance von Node.js-Anwendungen.
import { Injectable } from '@nestjs/common';
@Injectable()
export class AsyncOptimizationService {
// SCHLECHT: Sequential Processing
async processUsersSequential(userIds: string[]): Promise<any[]> {
const results = [];
for (const userId of userIds) {
const user = await this.fetchUser(userId);
const profile = await this.fetchUserProfile(userId);
const permissions = await this.fetchUserPermissions(userId);
results.push({ user, profile, permissions });
}
return results; // Sehr langsam bei vielen Benutzern
}
// BESSER: Parallel Processing
async processUsersParallel(userIds: string[]): Promise<any[]> {
const results = await Promise.all(
userIds.map(async (userId) => {
// Parallele Requests für jeden Benutzer
const [user, profile, permissions] = await Promise.all([
this.fetchUser(userId),
this.fetchUserProfile(userId),
this.fetchUserPermissions(userId),
]);
return { user, profile, permissions };
})
);
return results;
}
// OPTIMAL: Batch + Parallel mit Concurrency Control
async processUsersBatched(
userIds: string[],
batchSize: number = 10,
concurrency: number = 5
): Promise<any[]> {
const results: any[] = [];
// Batches mit begrenzter Concurrency
for (let i = 0; i < userIds.length; i += batchSize) {
const batch = userIds.slice(i, i + batchSize);
const batchResults = await this.processBatchWithConcurrency(
batch,
concurrency
);
results.push(...batchResults);
}
return results;
}
private async processBatchWithConcurrency(
userIds: string[],
concurrency: number
): Promise<any[]> {
const semaphore = new Semaphore(concurrency);
const promises = userIds.map(async (userId) => {
await semaphore.acquire();
try {
const [user, profile, permissions] = await Promise.all([
this.fetchUser(userId),
this.fetchUserProfile(userId),
this.fetchUserPermissions(userId),
]);
return { user, profile, permissions };
} finally {
semaphore.release();
}
});
return Promise.all(promises);
}
// Streaming für große Datenmengen
async *processUsersStream(userIds: string[]): AsyncGenerator<any, void, unknown> {
const concurrency = 5;
const semaphore = new Semaphore(concurrency);
const processingQueue = userIds.map(async (userId) => {
await semaphore.acquire();
try {
const [user, profile, permissions] = await Promise.all([
this.fetchUser(userId),
this.fetchUserProfile(userId),
this.fetchUserPermissions(userId),
]);
return { userId, user, profile, permissions };
} finally {
semaphore.release();
}
});
// Ergebnisse streamen sobald sie verfügbar sind
for await (const result of this.processAsTheyComplete(processingQueue)) {
yield result;
}
}
private async *processAsTheyComplete<T>(
promises: Promise<T>[]
): AsyncGenerator<T, void, unknown> {
const pending = new Set(promises);
while (pending.size > 0) {
const race = Promise.race(
Array.from(pending).map(async (promise) => {
const result = await promise;
pending.delete(promise);
return result;
})
);
yield await race;
}
}
// Optimierte Error Handling
async processWithErrorRecovery<T>(
operations: (() => Promise<T>)[],
options: {
maxRetries?: number;
retryDelay?: number;
failFast?: boolean;
} = {}
): Promise<Array<T | Error>> {
const { maxRetries = 3, retryDelay = 1000, failFast = false } = options;
const results = await Promise.allSettled(
operations.map(async (operation) => {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (failFast) {
throw error;
}
if (attempt < maxRetries) {
await this.delay(retryDelay * attempt); // Exponential backoff
}
}
}
throw lastError!;
})
);
return results.map(result =>
result.status === 'fulfilled' ? result.value : result.reason
);
}
// Promise Timeout Utility
withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
return Promise.race([
promise,
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs)
),
]);
}
// Memory-efficient async processing
async processLargeDatasetAsync<T, R>(
data: T[],
processor: (item: T) => Promise<R>,
options: {
batchSize?: number;
concurrency?: number;
memoryThreshold?: number;
} = {}
): Promise<R[]> {
const { batchSize = 1000, concurrency = 10, memoryThreshold = 100 * 1024 * 1024 } = options;
const results: R[] = [];
for (let i = 0; i < data.length; i += batchSize) {
// Memory Check
const memUsage = process.memoryUsage();
if (memUsage.heapUsed > memoryThreshold) {
console.warn('High memory usage, forcing GC');
if (global.gc) global.gc();
await this.delay(100); // Kurze Pause
}
const batch = data.slice(i, i + batchSize);
const semaphore = new Semaphore(concurrency);
const batchResults = await Promise.all(
batch.map(async (item) => {
await semaphore.acquire();
try {
return await processor(item);
} finally {
semaphore.release();
}
})
);
results.push(...batchResults);
// Progress Logging
const progress = ((i + batch.length) / data.length * 100).toFixed(1);
console.log(`Processing progress: ${progress}%`);
}
return results;
}
private async delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Mock-Methoden für Beispiele
private async fetchUser(userId: string): Promise<any> {
await this.delay(Math.random() * 100);
return { id: userId, name: `User ${userId}` };
}
private async fetchUserProfile(userId: string): Promise<any> {
await this.delay(Math.random() * 150);
return { userId, profile: 'profile data' };
}
private async fetchUserPermissions(userId: string): Promise<any> {
await this.delay(Math.random() * 80);
return { userId, permissions: ['read', 'write'] };
}
}
// Semaphore Implementation für Concurrency Control
class Semaphore {
private permits: number;
private waitQueue: Array<() => void> = [];
constructor(permits: number) {
this.permits = permits;
}
async acquire(): Promise<void> {
return new Promise((resolve) => {
if (this.permits > 0) {
this.permits--;
resolve();
} else {
this.waitQueue.push(resolve);
}
});
}
release(): void {
if (this.waitQueue.length > 0) {
const next = this.waitQueue.shift()!;
next();
} else {
this.permits++;
}
}
available(): number {
return this.permits;
}
}Die Optimierung der Bundle-Größe ist wichtig für schnelle Startup-Zeiten und reduzierten Memory-Footprint.
import { Injectable, OnModuleInit } from '@nestjs/common';
@Injectable()
export class LazyLoadingService implements OnModuleInit {
private heavyLibraries: Map<string, any> = new Map();
async onModuleInit() {
// Kritische Module vorladen
await this.preloadCriticalModules();
}
private async preloadCriticalModules(): Promise<void> {
// Nur wirklich kritische Module beim Start laden
const criticalModules = ['lodash/pick', 'date-fns/format'];
await Promise.all(
criticalModules.map(async (moduleName) => {
try {
const module = await import(moduleName);
this.heavyLibraries.set(moduleName, module);
} catch (error) {
console.warn(`Failed to preload ${moduleName}:`, error.message);
}
})
);
}
// Lazy Loading für schwere Bibliotheken
async getImageProcessingLibrary(): Promise<any> {
const cacheKey = 'sharp';
if (this.heavyLibraries.has(cacheKey)) {
return this.heavyLibraries.get(cacheKey);
}
try {
const sharp = await import('sharp');
this.heavyLibraries.set(cacheKey, sharp);
return sharp;
} catch (error) {
console.error('Failed to load image processing library:', error);
throw new Error('Image processing not available');
}
}
async getPdfLibrary(): Promise<any> {
const cacheKey = 'pdf-lib';
if (this.heavyLibraries.has(cacheKey)) {
return this.heavyLibraries.get(cacheKey);
}
const pdfLib = await import('pdf-lib');
this.heavyLibraries.set(cacheKey, pdfLib);
return pdfLib;
}
// Feature-basiertes Lazy Loading
async loadFeatureModule(featureName: string): Promise<any> {
const moduleMap: Record<string, () => Promise<any>> = {
'email': () => import('../features/email/email.module'),
'analytics': () => import('../features/analytics/analytics.module'),
'reporting': () => import('../features/reporting/reporting.module'),
'export': () => import('../features/export/export.module'),
};
const loader = moduleMap[featureName];
if (!loader) {
throw new Error(`Unknown feature module: ${featureName}`);
}
return await loader();
}
// Conditional Loading basierend auf Environment
async loadEnvironmentSpecificModules(): Promise<void> {
const environment = process.env.NODE_ENV;
if (environment === 'development') {
// Development-spezifische Module
await Promise.all([
import('morgan').then(morgan => this.heavyLibraries.set('morgan', morgan)),
import('@nestjs/swagger').then(swagger => this.heavyLibraries.set('swagger', swagger)),
]);
}
if (environment === 'production') {
// Production-spezifische Module
await Promise.all([
import('compression').then(compression => this.heavyLibraries.set('compression', compression)),
import('helmet').then(helmet => this.heavyLibraries.set('helmet', helmet)),
]);
}
}
// Tree-shaking freundliche Utilities
async getOptimizedUtilities(): Promise<{
pick: Function;
omit: Function;
formatDate: Function;
}> {
// Nur spezifische Funktionen importieren, nicht die ganze Bibliothek
const [pick, omit, formatDate] = await Promise.all([
import('lodash/pick').then(m => m.default),
import('lodash/omit').then(m => m.default),
import('date-fns/format').then(m => m.default),
]);
return { pick, omit, formatDate };
}
// Memory-optimiertes Module Loading
async loadWithMemoryCheck(moduleName: string): Promise<any> {
const beforeMemory = process.memoryUsage();
const module = await import(moduleName);
const afterMemory = process.memoryUsage();
const memoryIncrease = afterMemory.heapUsed - beforeMemory.heapUsed;
console.log(`Module ${moduleName} loaded, memory increase: ${(memoryIncrease / 1024 / 1024).toFixed(2)} MB`);
return module;
}
// Cleanup nicht genutzter Module
cleanupUnusedModules(): void {
const unusedThreshold = 5 * 60 * 1000; // 5 Minuten
const now = Date.now();
for (const [key, value] of this.heavyLibraries.entries()) {
if (value.lastUsed && (now - value.lastUsed) > unusedThreshold) {
console.log(`Cleaning up unused module: ${key}`);
this.heavyLibraries.delete(key);
}
}
}
// Module Usage Tracking
markModuleUsed(moduleName: string): void {
const module = this.heavyLibraries.get(moduleName);
if (module) {
module.lastUsed = Date.now();
}
}
}
// Optimierte Service mit Lazy Loading
@Injectable()
export class OptimizedImageService {
constructor(private lazyLoader: LazyLoadingService) {}
async processImage(imageBuffer: Buffer): Promise<Buffer> {
const sharp = await this.lazyLoader.getImageProcessingLibrary();
this.lazyLoader.markModuleUsed('sharp');
return await sharp.default(imageBuffer)
.resize(800, 600)
.jpeg({ quality: 80 })
.toBuffer();
}
async generatePdf(data: any): Promise<Buffer> {
const pdfLib = await this.lazyLoader.getPdfLibrary();
this.lazyLoader.markModuleUsed('pdf-lib');
const pdfDoc = await pdfLib.PDFDocument.create();
// PDF-Generierung...
return await pdfDoc.save();
}
}import { Injectable } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
@Injectable()
export class BundleAnalysisService {
// Bundle-Größen analysieren
async analyzeBundleSize(): Promise<{
totalSize: number;
moduleBreakdown: Array<{
name: string;
size: number;
percentage: number;
}>;
recommendations: string[];
}> {
const packagePath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
const dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
const moduleBreakdown = await this.getModuleSizes(dependencies);
const totalSize = moduleBreakdown.reduce((sum, mod) => sum + mod.size, 0);
const recommendations = this.generateOptimizationRecommendations(moduleBreakdown);
return {
totalSize,
moduleBreakdown: moduleBreakdown.map(mod => ({
...mod,
percentage: (mod.size / totalSize) * 100,
})),
recommendations,
};
}
private async getModuleSizes(dependencies: Record<string, string>): Promise<Array<{
name: string;
size: number;
}>> {
const moduleSizes: Array<{ name: string; size: number }> = [];
for (const [moduleName] of Object.entries(dependencies)) {
try {
const moduleSize = await this.calculateModuleSize(moduleName);
moduleSizes.push({ name: moduleName, size: moduleSize });
} catch (error) {
console.warn(`Could not calculate size for ${moduleName}`);
}
}
return moduleSizes.sort((a, b) => b.size - a.size);
}
private async calculateModuleSize(moduleName: string): Promise<number> {
const modulePath = path.join(process.cwd(), 'node_modules', moduleName);
if (!fs.existsSync(modulePath)) {
return 0;
}
return this.getDirectorySize(modulePath);
}
private getDirectorySize(dirPath: string): number {
let totalSize = 0;
const items = fs.readdirSync(dirPath);
for (const item of items) {
const itemPath = path.join(dirPath, item);
const stats = fs.statSync(itemPath);
if (stats.isDirectory()) {
totalSize += this.getDirectorySize(itemPath);
} else {
totalSize += stats.size;
}
}
return totalSize;
}
private generateOptimizationRecommendations(modules: Array<{ name: string; size: number }>): string[] {
const recommendations: string[] = [];
// Große Module identifizieren
const largeModules = modules.filter(mod => mod.size > 5 * 1024 * 1024); // > 5MB
if (largeModules.length > 0) {
recommendations.push(
`Consider lazy loading these large modules: ${largeModules.map(m => m.name).join(', ')}`
);
}
// Potentielle Alternativen vorschlagen
const alternatives: Record<string, string> = {
'moment': 'date-fns (smaller and tree-shakeable)',
'lodash': 'individual lodash functions or native JS methods',
'axios': 'native fetch API',
'express': 'fastify (lighter alternative)',
};
for (const module of modules) {
if (alternatives[module.name]) {
recommendations.push(`Consider replacing ${module.name} with ${alternatives[module.name]}`);
}
}
// Tree-shaking Empfehlungen
recommendations.push('Use named imports instead of default imports for better tree-shaking');
recommendations.push('Configure your bundler to eliminate dead code');
return recommendations;
}
// Runtime Bundle Monitoring
monitorRuntimeMemory(): {
heapUsed: number;
heapTotal: number;
external: number;
moduleCount: number;
} {
const memUsage = process.memoryUsage();
// Geladene Module zählen
const moduleCount = Object.keys(require.cache).length;
return {
heapUsed: memUsage.heapUsed,
heapTotal: memUsage.heapTotal,
external: memUsage.external,
moduleCount,
};
}
// Code Splitting Empfehlungen
generateCodeSplittingStrategy(): {
criticalModules: string[];
lazyLoadableModules: string[];
vendorModules: string[];
} {
return {
criticalModules: [
'@nestjs/core',
'@nestjs/common',
'express',
'reflect-metadata',
],
lazyLoadableModules: [
'sharp',
'pdf-lib',
'@nestjs/swagger',
'nodemailer',
],
vendorModules: [
'lodash',
'date-fns',
'axios',
'typeorm',
],
};
}
}Load Testing ist essentiell, um die Performance-Grenzen der Anwendung zu verstehen und zu optimieren.
import { Injectable } from '@nestjs/common';
import axios from 'axios';
interface LoadTestConfig {
baseUrl: string;
endpoints: Array<{
path: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
payload?: any;
weight: number; // Gewichtung für Request-Verteilung
}>;
duration: number; // Testdauer in Sekunden
concurrency: number; // Anzahl gleichzeitiger Verbindungen
rampUp: number; // Ramp-up Zeit in Sekunden
}
interface LoadTestResult {
totalRequests: number;
successfulRequests: number;
failedRequests: number;
averageResponseTime: number;
p95ResponseTime: number;
p99ResponseTime: number;
requestsPerSecond: number;
errorRate: number;
errors: Array<{
endpoint: string;
error: string;
count: number;
}>;
}
@Injectable()
export class LoadTestingService {
async runLoadTest(config: LoadTestConfig): Promise<LoadTestResult> {
console.log('Starting load test...', config);
const startTime = Date.now();
const endTime = startTime + (config.duration * 1000);
const results: Array<{
endpoint: string;
responseTime: number;
success: boolean;
error?: string;
}> = [];
// Ramp-up Phase
await this.rampUpPhase(config, results, startTime, endTime);
// Haupt-Load-Phase
await this.mainLoadPhase(config, results, endTime);
return this.analyzeResults(results, config.duration);
}
private async rampUpPhase(
config: LoadTestConfig,
results: any[],
startTime: number,
endTime: number
): Promise<void> {
const rampUpEnd = startTime + (config.rampUp * 1000);
const concurrencyStep = config.concurrency / (config.rampUp * 10); // 10 Schritte pro Sekunde
let currentConcurrency = 1;
while (Date.now() < rampUpEnd && Date.now() < endTime) {
const promises: Promise<void>[] = [];
for (let i = 0; i < Math.floor(currentConcurrency); i++) {
promises.push(this.executeRequest(config, results));
}
await Promise.all(promises);
currentConcurrency = Math.min(
currentConcurrency + concurrencyStep,
config.concurrency
);
await this.delay(100); // 100ms zwischen Batches
}
}
private async mainLoadPhase(
config: LoadTestConfig,
results: any[],
endTime: number
): Promise<void> {
while (Date.now() < endTime) {
const promises: Promise<void>[] = [];
for (let i = 0; i < config.concurrency; i++) {
promises.push(this.executeRequest(config, results));
}
await Promise.all(promises);
await this.delay(10); // Kurze Pause zwischen Batches
}
}
private async executeRequest(
config: LoadTestConfig,
results: any[]
): Promise<void> {
const endpoint = this.selectEndpoint(config.endpoints);
const startTime = performance.now();
try {
await axios({
method: endpoint.method,
url: `${config.baseUrl}${endpoint.path}`,
data: endpoint.payload,
timeout: 30000,
});
const responseTime = performance.now() - startTime;
results.push({
endpoint: endpoint.path,
responseTime,
success: true,
});
} catch (error) {
const responseTime = performance.now() - startTime;
results.push({
endpoint: endpoint.path,
responseTime,
success: false,
error: error.message,
});
}
}
private selectEndpoint(endpoints: LoadTestConfig['endpoints']): LoadTestConfig['endpoints'][0] {
// Gewichtete Zufallsauswahl
const totalWeight = endpoints.reduce((sum, ep) => sum + ep.weight, 0);
let random = Math.random() * totalWeight;
for (const endpoint of endpoints) {
random -= endpoint.weight;
if (random <= 0) {
return endpoint;
}
}
return endpoints[0]; // Fallback
}
private analyzeResults(results: any[], duration: number): LoadTestResult {
const totalRequests = results.length;
const successfulRequests = results.filter(r => r.success).length;
const failedRequests = totalRequests - successfulRequests;
const responseTimes = results
.filter(r => r.success)
.map(r => r.responseTime)
.sort((a, b) => a - b);
const averageResponseTime = responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length;
const p95ResponseTime = responseTimes[Math.floor(responseTimes.length * 0.95)];
const p99ResponseTime = responseTimes[Math.floor(responseTimes.length * 0.99)];
const requestsPerSecond = totalRequests / duration;
const errorRate = (failedRequests / totalRequests) * 100;
// Fehler gruppieren
const errorGroups = new Map<string, { endpoint: string; error: string; count: number }>();
results.filter(r => !r.success).forEach(r => {
const key = `${r.endpoint}:${r.error}`;
const existing = errorGroups.get(key);
if (existing) {
existing.count++;
} else {
errorGroups.set(key, {
endpoint: r.endpoint,
error: r.error,
count: 1,
});
}
});
return {
totalRequests,
successfulRequests,
failedRequests,
averageResponseTime: averageResponseTime || 0,
p95ResponseTime: p95ResponseTime || 0,
p99ResponseTime: p99ResponseTime || 0,
requestsPerSecond,
errorRate,
errors: Array.from(errorGroups.values()),
};
}
// Stress Testing - Grenzen finden
async runStressTest(baseConfig: LoadTestConfig): Promise<{
maxConcurrency: number;
breakingPoint: {
concurrency: number;
errorRate: number;
avgResponseTime: number;
};
results: LoadTestResult[];
}> {
const results: LoadTestResult[] = [];
let currentConcurrency = 10;
let maxConcurrency = 0;
let breakingPoint: any = null;
while (currentConcurrency <= 500) { // Max 500 concurrent users
const testConfig = {
...baseConfig,
concurrency: currentConcurrency,
duration: 60, // 1 Minute pro Test
};
console.log(`Testing with ${currentConcurrency} concurrent users...`);
const result = await this.runLoadTest(testConfig);
results.push(result);
// Breaking Point Detection
if (result.errorRate > 5 || result.averageResponseTime > 5000) {
breakingPoint = {
concurrency: currentConcurrency,
errorRate: result.errorRate,
avgResponseTime: result.averageResponseTime,
};
break;
}
maxConcurrency = currentConcurrency;
currentConcurrency = Math.floor(currentConcurrency * 1.5); // 50% Steigerung
// Pause zwischen Tests
await this.delay(30000); // 30 Sekunden Erholung
}
return {
maxConcurrency,
breakingPoint,
results,
};
}
// Spike Testing - Plötzliche Lastspitzen
async runSpikeTest(config: LoadTestConfig): Promise<LoadTestResult> {
console.log('Starting spike test...');
const results: any[] = [];
const testDuration = 300000; // 5 Minuten
const spikeDuration = 30000; // 30 Sekunden Spike
const normalConcurrency = Math.floor(config.concurrency * 0.3);
const spikeConcurrency = config.concurrency;
const startTime = Date.now();
const spikeStart = startTime + 120000; // Spike nach 2 Minuten
const spikeEnd = spikeStart + spikeDuration;
const endTime = startTime + testDuration;
while (Date.now() < endTime) {
const currentTime = Date.now();
let currentConcurrency = normalConcurrency;
// Spike-Phase
if (currentTime >= spikeStart && currentTime < spikeEnd) {
currentConcurrency = spikeConcurrency;
console.log('SPIKE PHASE: High load');
}
const promises: Promise<void>[] = [];
for (let i = 0; i < currentConcurrency; i++) {
promises.push(this.executeRequest(config, results));
}
await Promise.all(promises);
await this.delay(100);
}
return this.analyzeResults(results, testDuration / 1000);
}
// Performance Regression Detection
async detectPerformanceRegression(
config: LoadTestConfig,
baseline: LoadTestResult
): Promise<{
hasRegression: boolean;
regressions: Array<{
metric: string;
baseline: number;
current: number;
degradation: number;
}>;
}> {
const currentResult = await this.runLoadTest(config);
const regressions: any[] = [];
const metrics = [
{
name: 'averageResponseTime',
threshold: 0.2, // 20% Verschlechterung
baseline: baseline.averageResponseTime,
current: currentResult.averageResponseTime,
},
{
name: 'p95ResponseTime',
threshold: 0.25, // 25% Verschlechterung
baseline: baseline.p95ResponseTime,
current: currentResult.p95ResponseTime,
},
{
name: 'requestsPerSecond',
threshold: -0.15, // 15% Abfall
baseline: baseline.requestsPerSecond,
current: currentResult.requestsPerSecond,
},
{
name: 'errorRate',
threshold: 2, // 2% absolute Steigerung
baseline: baseline.errorRate,
current: currentResult.errorRate,
},
];
for (const metric of metrics) {
let degradation: number;
if (metric.name === 'errorRate') {
degradation = metric.current - metric.baseline; // Absolute Differenz
} else if (metric.name === 'requestsPerSecond') {
degradation = (metric.current - metric.baseline) / metric.baseline; // Negative für Verschlechterung
} else {
degradation = (metric.current - metric.baseline) / metric.baseline; // Relative Differenz
}
if (
(metric.name === 'errorRate' && degradation > metric.threshold) ||
(metric.name === 'requestsPerSecond' && degradation < metric.threshold) ||
(metric.name !== 'errorRate' && metric.name !== 'requestsPerSecond' && degradation > metric.threshold)
) {
regressions.push({
metric: metric.name,
baseline: metric.baseline,
current: metric.current,
degradation: degradation * 100, // Als Prozent
});
}
}
return {
hasRegression: regressions.length > 0,
regressions,
};
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Load Test Beispiel-Konfiguration
getDefaultLoadTestConfig(): LoadTestConfig {
return {
baseUrl: 'http://localhost:3000',
endpoints: [
{ path: '/api/users', method: 'GET', weight: 40 },
{ path: '/api/users/1', method: 'GET', weight: 30 },
{ path: '/api/products', method: 'GET', weight: 20 },
{ path: '/api/auth/login', method: 'POST', payload: { email: 'test@test.com', password: 'password' }, weight: 10 },
],
duration: 300, // 5 Minuten
concurrency: 50,
rampUp: 30, // 30 Sekunden
};
}
}Performance-Optimierung ist ein kontinuierlicher Prozess, der systematisches Monitoring, Profiling und iterative Verbesserungen erfordert. Die in diesem Kapitel vorgestellten Techniken bilden eine solide Grundlage für die Entwicklung hochperformanter NestJS-Anwendungen. Wichtig ist, immer zu messen bevor optimiert wird, und die Optimierungen regelmäßig zu validieren, um sicherzustellen, dass sie den gewünschten Effekt haben.